当使用双精度浮点数时,如何解决Java在使用模运算符时出现的奇怪行为?
例如,您期望3.9 - (3.9 % 0.1)
的结果应该是3.9
(事实上,谷歌也证明了这一点),但是当我在Java中运行它时,我得到的结果是3.8000000000000003
。
我知道这是由于Java存储和处理双精度浮点数的方式导致的,但是是否有一种方法可以解决这个问题?
如果您需要精确的结果,请使用精确类型:
double val = 3.9 - (3.9 % 0.1);
System.out.println(val); // 3.8000000000000003
BigDecimal x = new BigDecimal( "3.9" );
BigDecimal bdVal = x.subtract( x.remainder( new BigDecimal( "0.1" ) ) );
System.out.println(bdVal); // 3.9
为什么是3.8000...003?因为Java使用FPU来计算结果。在IEEE双精度表示法中,3.9无法准确存储,所以它会存储3.89999...而不是3.9。而3.8999%0.01的结果为0.09999...因此结果比3.8略大一点。
((3.9D / 0.1D) % 1D) = 0.0
(基本上是稍微重新组织了一下同样的问题),而由于舍入误差,它认为(3.9D % 0.1D) = 0.0999999999999997
? - Dave JohnsonBigDecimal#valueOf(double)
的实现方式是new BigDecimal(Double.toString(x))
。原因显然是Java语言规范要求浮点数转字符串的精度比IEEE对于一般浮点数运算的要求更高。 - David Foerster来自Java语言规范:
%操作符计算的浮点数余数结果与IEEE 754定义的余数操作结果不同。IEEE 754余数操作通过四舍五入除法计算余数,而不是截断除法,因此其行为不能类比于通常的整数余数操作。相反,Java编程语言将%用于浮点运算,其行为类似于整数余数操作符;这可与C库函数fmod进行比较。 IEEE 754余数操作可以由库例程Math.IEEEremainder计算。
换句话说,这是因为Java对计算余数所涉及的除法结果四舍五入,而IEEE754则指定截断除法的答案。这个特定的情况似乎非常清楚地展示了这种差异。
您可以使用Math.IEEEremainder获得您期望的答案:
System.out.println(3.9 - (3.9 % 0.1));
System.out.println(3.9 - Math.IEEEremainder(3.9, 0.1));
如果您知道要处理的小数位数,可以尝试先转换为整数。这只是浮点不精确的经典案例。您所做的不是 3.9 % 0.1
,而是类似于 3.899 % 0.0999