数字输入错误地接受带小数位的浮点数

phi*_*294 9 html chromium

<form>
  <input type="number" step="1" required>
  <input type="submit">
</form>
Run Code Online (Sandbox Code Playgroud)

在上面的代码段中,如果您插入

0.0000001
Run Code Online (Sandbox Code Playgroud)

输入数字后,您将无法提交表单:“ step”属性可以防止这种情况。

但是如果你插入

0.00000001
Run Code Online (Sandbox Code Playgroud)

它可以在Chrome / Chromium中使用!为什么在验证输入时似乎只读取前七个小数位?是否在任何地方对此进行了记录,并且我可以采取什么措施防止这种情况发生?

我测试了Firefox,它不接受任何此类值。它不能是浮点精度问题(例如0.1 + 0.2),仅与更多的十进制数字有关

0.30000000000000004 === 0.1+0.2
Run Code Online (Sandbox Code Playgroud)

Jul*_*ire 3

虽然这肯定是您拥有的数字的错误,但它实际上可以防止其他步骤/值集出现问题。分数舍入存在核心问题,使得完美的步骤验证并不那么容易,至少在这种精度水平下是如此。事实上,如果你查看 Firefox 源代码,你可以找到这个评论https://dxr.mozilla.org/mozilla-central/source/dom/html/HTMLInputElement.cpp#4654

处理小数时可能会出现下面的舍入问题,但在 ECMAScript 为我们提供十进制数字类型之前,我们先忽略它。

所以问题不仅仅存在于 Chrome 上。但让我们从您描述的错误开始。如果你查看 Chromium 源代码,你可以看到在 step_range 处理中,有一个名为AcceptableError的方法,它调整该值与针对步长调整的值之间的差异。请参阅https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/forms/step_range.cc?l=60。这个可接受的误差定义为:

the step value / 2^24 (in the source code UINT64_C(1) << FLT_MANT_DIG) 
Run Code Online (Sandbox Code Playgroud)

步长为 1:

1 / 16777216 = 0.000000059604645
Run Code Online (Sandbox Code Playgroud)

因此,在您的原始示例中,任何具有低于该值的余数的数字都将有效,即使它不应该有效。计算余数的公式为:

value - step * Math.abs(Math.round(value / step));
Run Code Online (Sandbox Code Playgroud)

例如,如果您输入 2.000000059604644,则余数将为:

  2.000000059604644 - 1 * Math.abs(Math.round(2.000000059604644))
= 2.000000059604644 - 2
= 0.000000059604644 
Run Code Online (Sandbox Code Playgroud)

0.000000059604644 低于 0.000000059604645,因此它会验证。2.000000059604646 不会。

the step value / 2^24 (in the source code UINT64_C(1) << FLT_MANT_DIG) 
Run Code Online (Sandbox Code Playgroud)

您可以尝试使用更大的数字,例如采用 167772167 的步长。这给出了可接受的错误:

16777217 / 16777216 = 1.000000059604645
Run Code Online (Sandbox Code Playgroud)

对于值为 16777218 的余数:

  16777218 - 16777217 * Math.abs(Math.round(1.000000059604645 ))
= 16777218 - 16777217
= 1
Run Code Online (Sandbox Code Playgroud)

1 / 16777216 = 0.000000059604645
Run Code Online (Sandbox Code Playgroud)

这是您可接受的误差 1 以下,因此它会验证。

所以这就是错误。作为报复,它允许容忍某些步骤和值,因为小数运算中精度的损失在它们应该验证时无法验证。在 Firefox 等浏览器中,这正是发生的情况。某些值/步骤对在应该验证时却没有验证。

以步长 853.2394 和值 495714162280.48785 为例。这当然应该得到验证,因为:

495714162280.48785 / 853.2394 = 580978987
Run Code Online (Sandbox Code Playgroud)

如果您在 Firefox 中尝试,它不会验证。但在 Chrome 中,它是有效的,因为容忍导致第一种情况的错误。

value - step * Math.abs(Math.round(value / step));
Run Code Online (Sandbox Code Playgroud)

所以最后,这个错误与小数运算的已知问题有关,这些问题并不精确。典型的例子是 0.1 + 0.2 = 0.30000000000000004。后台发生的事情稍微复杂一点,也更精确一些,但是处理十进制计算的问题导致了这些问题,并且使得它们很难在每种情况下预防。