JavaScript Math.floor:如何确保数字向下舍入?

3

我想将一个数组标准化,使得每个值都在[0-1)范围内,也就是说“最大值永远不会是1,但最小值可以是0”。

这与随机函数返回相同范围内的数字类似。

当我查看这个问题时,我发现.99999999999999999===1true

同样,(1-Number.MIN_VALUE) === 1,但是Math.ceil(Number.MIN_VALUE)的结果是1,正如它应该是的那样。

其他一些例子:Math.floor(.999999999999) 结果为 0,而Math.floor(.99999999999999999) 的结果为 1

好吧,JS中存在舍入问题。

有没有办法将一组数字标准化,使其落在区间[0,1)之间?


"normalize" 的意思是什么?1 的标准化值应该是多少?1.5?2? - Vlad
也许是:number/Number.MAX_VALUE - vlio20
2
没有“四舍五入问题”,但存在精度限制。Javascript数字是双精度64位二进制格式IEEE 754值,因此在15个数字后,由于位数不足以准确表示所有数字,您开始失去精度。 - RobG
你的问题在于,将最大值转换为什么值有点随意。是0.999?还是0.9999?你到底想要实现什么?也许你的问题有更好的解决方案。 - basilikum
谢谢大家,非常好的回答。我稍后会提交另一个更加精确的问题。 - backspaces
3个回答

4
可能有助于检查JavaScript对每个表达式执行的步骤。
.99999999999999999===1中:
  1. 源文本.99999999999999999被转换为数字。最接近的数字是1,因此结果为1。(下一个最接近的数字是0.99999999999999988897769753748434595763683319091796875,即1-2–53。)
  2. 然后将1与1进行比较。结果为true。
(1-Number.MIN_VALUE) === 1中:
  1. Number.MIN_VALUE是2–1074,约为5e–304。
  2. 1-2–1074非常接近于1。精确值不能表示为数字,因此使用最接近的值。再次,最接近的值是1。
  3. 然后将1与1进行比较。结果为true。
Math.ceil(Number.MIN_VALUE)中:
  1. Number.MIN_VALUE是2–1074,约为5e–304。
  2. 该值的ceiling函数是1。
Math.floor(.999999999999)中:
  1. 源文本.999999999999被转换为数字。最接近的数字是0.99999999999900002212172012150404043495655059814453125,因此结果为该数字。
  2. 该值的floor函数是0。
Math.floor(.99999999999999999)中:
  1. 源文本.99999999999999999被转换为数字。最接近的数字是1,因此结果为1。
  2. 1的floor函数是1。
这里最多只有两件令人惊讶的事情。一个是源文本中的数字被转换为内部数字值。但这不应该令人惊讶。当然,文本必须转换为数字的内部表示,并且Number类型无法完美地存储所有无限多个数字。因此它必须四舍五入。当然,非常接近1的数字会四舍五入为1。
另一个可能令人惊讶的事情是1-Number.MIN_VALUE等于1。但这实际上是相同的问题:精确结果不能表示,但它非常接近1,因此使用了1。 Math.floor函数可以正确工作。它从不引入任何错误,并且您不必做任何事情来保证它向下舍入。它总是这样做。

然而,由于您想要将数字标准化,因此您很可能会在某个时候除以数字。当您进行除法运算时,可能会出现舍入问题,因为许多除法的结果并不是完全可表示的,因此必须进行舍入。

但是,这是一个单独的问题,而且您在这个问题中没有提供足够的信息来回答您计划进行的具体计算。您应该为此开设一个单独的问题。


0
请理解一件事:这个

...

与编程有关。
.999999999999999999

...只是一个数字 字面量。就像

.999999999999999998
.999999999999999997
.999999999999999996
...

...你看到这个规律了。

JavaScript如何处理这些文字内容则是另一回事了。是的,这种处理受到可用于存储数字值的位数限制。

根据定义,可能的浮点文字是无限的 - 不管其范围有多小。例如,以上所示的这些数字:你有多少非常接近1的数字可以表示?没错,是无限多:只需将9不断添加到行末即可。

但是每个数字值的容器非常有限:它只有64个位。也就是说,它只能存储2^64个不同的值(其中包括无穷大负无穷大NaN)- 就仅仅是这些了。

你还想继续使用这些字面量吗?使用字符串来存储,而非数字 - 并且使用一些大型数学JS库(随意选择),以字符串的形式处理这些值。

但从你的问题来看,似乎不是这样,因为你谈到了数字数组 - 即为数字。在JavaScript中根本不存在.999999999999999999这样的数字,因此也不可能存储在那里。


你做得很好 - 有时候人们会混淆实际对象和它们的表示。 - georg

0

Javascript会将0.999999999999999994到1之间的任何数字视为1,因此只需减去0.000000000000000006即可。

当然,这并不像听起来那么简单,因为在Javascript中,0.000000000000000006被计算为0,所以你可以做一些类似于:

function trueFloor(x)
{
    x = x * 100;
    if(x > .0000000000000006)
        x = x - .0000000000000006;
    x = Math.floor(x/100);
    return x;
}

编辑:或者你认为你可以。显然,JS在将.99999999999999999传递给函数之前会将其转换为1,因此你需要尝试类似以下的内容:

trueFloor("0.99999999999999999")

function trueFloor(str)
{
    x=str.substring(0,9) + 0;
    return Math.floor(x); //=> 0
}

不确定为什么您需要那种精度级别,但理论上来说,我想它是可行的。您可以在这里查看一个工作的fiddle here

只要将您疯狂精确的float转换为string,那可能是您最好的选择。


这是标准的吗?还是会因为浏览器/JS实现的不同而有所差异?谢谢! - backspaces
这是Chrome、IE和FF实现的标准。如果你感兴趣,可以随意在其他环境中测试fiddle。 - Jason Nichols
注意:要查看实际的“标准”,请查看ECMAScript Standards Page - Section 8.5,定义了number变量类型。精度通常受到公式S x M x 2^E的限制,其中E大于或等于-1074。 - Jason Nichols
Jason:谢谢!非常有趣,但稍微有点奇怪!但绝对是很好的阅读材料。还显示Math.pow略有偏差..我已计算了一些示例数字。 - backspaces

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接