我今天遇到了一个奇怪的浮点错误(已在JavaScript和Python中验证)。
> 15 * 19.22
288.29999999999995
这种情况的真正怪异之处在于这些数字完全在浮点可表示的数字范围内。我们不会处理太大或很小的数字。
实际上,如果我只移动小数点,就可以得到正确的答案而不会舍入错误。
> 15 * 19.22
288.29999999999995
> 1.5 * 192.2
288.29999999999995
> .15 * 1922.
288.3
> 150 * 1.922
288.3
> 1500 * .1922
288.3
> 15 * 1922 / 100
288.3
> 1.5 * 1.922 * 100
288.3
> 1.5 * .1922 * 1000
288.3
> .15 * .1922 * 10000
288.3
很明显,必须有一些中间数字不能用浮点数表示,但这怎么可能呢?
是否存在“安全”的乘以浮点数的方法来防止此问题?我认为,如果数字的数量级相同,那么浮点乘法将最准确地工作,但显然这是一个错误的假设。
答案 0 :(得分:3)
为什么浮点数误差会根据小数点的位置而变化?
因为您使用的是基数10。IEEE-754双精度二进制浮点以二进制(基数2)工作。例如,在这种表示形式中,1
可以精确表示,而0.1
则不能。¹
这种情况的真正怪异之处在于这些数字完全在浮点可表示的数字范围内。我们不会处理太大或很小的数字。
从我上面的陈述中可以看到,即使只是十分之一,您也遇到了不精确的数字,而不必求得离谱的值(就像您得到不可代表的整数一样,例如9,007,199,254,740,993)。因此,著名的0.1 + 0.2 = 0.30000000000000004
事物:
console.log(0.1 + 0.2);
是否存在一种“安全”的乘以浮点数的方式来防止出现此问题?
不使用内置浮点。您可能只使用整数(因为从-9,007,199,254,740,992到9,007,199,254,740,992是可靠的),然后在输出时插入相关的小数。您可能会发现此问题的答案很有用:How to deal with floating point number precision in JavaScript?。
¹您可能想知道为什么如果0.1
不能正确表示,console.log(0.1)
输出"0.1"
。这是因为通常在使用浮点数转换为字符串时,仅输出足够的数字来区分该数字与其最接近的可表示邻居。对于0.1
,只需要"0.1"
。将二进制浮点数转换为可表示的十进制数非常复杂,请参见the spec中的各种注释和引用。 :-)
答案 1 :(得分:1)
这是因为 binary 浮点表示方式。将值0.8、0.4、0.2和0.1设为两倍。它们的实际存储值是:
0.8 --> 0.8000000000000000444089209850062616169452667236328125
0.4 --> 0.40000000000000002220446049250313080847263336181640625
0.2 --> 0.200000000000000011102230246251565404236316680908203125
0.1 --> 0.1000000000000000055511151231257827021181583404541015625
您可以轻松地看到,每次将数字减半时,精确的十进制值之差就会减半。这是因为所有这些都具有完全相同的有效位数,并且只有不同的指数。如果您看一下它们的十六进制表示,就会更清楚:
0.8 --> 0x1.999999999999ap-1
0.4 --> 0x1.999999999999ap-2
etc...
因此,实际值,数学值和实际存储的值之间的差异在最后一位的中间和下方。指数越低,该位的值越小。然后以另一种方式上升:1.6是0x1.999999999999ap+0
,依此类推。您越走越高,由于该指数,该差异的值将变得越大。这就是为什么将其称为相对误差。
如果您移动十进制点,实际上您也在更改 binary 指数。由于我们处理的是不同的数字基数,因此不是完全成比例的,而是“等效地”(如果这是一个适当的词)。数字越大,指数越高,因此数学和浮点值之间的差值也就越大。
答案 2 :(得分:0)
不是答案,而是长评论。
由于浮点表示会将数字标准化为1到2之间的尾数,因此数量级不是有罪的。
15 = 1.875 x 2^3
19.22 = 1.20125 x 2^4
150 = 1.171875 x 2^7
0.1922 = 1.5376 x 2^-3
和指数分别处理。