为什么Scala中乘法0.3和3的结果是0.89999999999999?
5 回答
浮点计算是相当复杂的主题。
这与浮点数的二进制表示有关,它不能保证每个可能的数字(显然)都有精确的表示,这可能导致操作错误,是的,这些错误可以传播。
这是有关该主题的链接,虽然它不是最简单的东西,但如果您想了解该主题,它可以为您提供一个很好的视角。
http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
底线问题是,对于给定的浮点数,一个有理数,该数字的二进制表示可能需要比给定语言中可用的精度更高的精度。
例如,如果您使用 24 位来存储双精度数,但浮点值的二进制表示需要 25 位才能准确表示,则会出现舍入误差。
编辑:
正如 Péter Török 在评论中指出的那样,大多数已知的编程语言对常见的数据类型使用相同的表示,float
-> 32 位,double
-> 64 位,因此无论语言如何,通常都可以考虑数据类型来计算精度。
不仅在 Scala 中如此,在任何使用 IEEE 浮点数标准的语言/平台中都是如此。
红宝石中的示例:
0.1.9.2p320 :001 > 0.3 * 3
=> 0.8999999999999999
或 Python:
>>> 0.3 * 3
0.8999999999999999
这几乎与 Scala 无关!
原因与计算机如何表示浮点数有关。计算机使用二进制表示,而不是十进制表示,因此诸如 0.3 之类的数字不能精确表示,而是使用可以简洁地以 2 为基数表示的近似值。结果是对这些数字执行的操作可能会稍微超出您期望的答案。有关更多详细信息,请参阅浮点数。
如有必要,您可以通过实现类之Fraction
类的东西来解决这个问题,该类使用自己的算法来精确地执行这些计算。
如果需要精确值,请使用 BigDecimal。
BigDecimal(3)*0.3
会给你0.9
这是常用浮点值表示的副作用。
我经常遇到这个问题并制作一个简单的函数,将数字“四舍五入”到最接近的大于期望精度的值。它在 C 中为:
int64_t __pow10_arr[18] = { 1l, 10l, 100l, 1000l, 10000l, 100000l,
1000000l, 10000000, 100000000l, 1000000000l, 10000000000l, 100000000000l,
1000000000000l, 10000000000000l, 100000000000000l, 1000000000000000l, 10000000000000000l, 100000000000000000l };
double roundToNfractions ( double val, uint8_t n )
{
val *= __pow10_arr[n];
val += 0.5;
val = (uint64_t) val;
val /= __pow10_arr[n];
return val;
}
效率不是很高,但可以解决问题,因为您可以安全地编写
printf("%.2d", val);
,并且您总是会得到类似的结果,3.00
而不是2.99
;