我正在调用truncate方法来截断double值,以便小数点后应该有一个数字(不会四舍五入),
对于Ex。 truncate(123.48574) = 123.4
。
我的截断方法是这样的
public double truncate (double x) {
long y = (long) (x * 10);
double z = (double) (y / 10);
return z;
}
除了这个奇怪的输出外,几乎所有的值都能正常工作。
double d = 0.787456;
d = truncate(d + 0.1); //gives 0.8 as expected. Okay.
但是,
double d = 0.7;
d = truncate(d + 0.1); //should also give 0.8. But its giving 0.7 only.
//Strange I don't know why?
事实上,它适用于所有其他0.1,0.2,0.3,0.4,0.5,0.6, - ,0.8,0.9 我的意思是,例如,
double d = 0.8;
d = truncate(d + 0.1); //gives 0.9 as expected
我也尝试了BigDecimal
。但是一样。没变。
这是代码。
double d = 0.7;
BigDecimal a = new BigDecimal(d + 0.1);
BigDecimal floored = a.setScale(1, BigDecimal.ROUND_DOWN);
double d1 = floored.doubleValue();
System.out.println(d1); //Still gives 0.7
而真正的事实是它与Math.round
一起工作正常。
public double roundUp1d (double d) {
return Math.round(d * 10.0) / 10.0;
}
因此,如果我调用roundUp1d(0.7 + 0.1)
,它会按预期给出0.8。但是我不希望这些值被四舍五入所以我无法使用它。
0.7问题是什么?
答案 0 :(得分:7)
(如果您对理论不感兴趣,请滚动到结束,这是代码的修复程序)
原因很简单:如您所知,二进制系统仅支持0
和1
s
所以,让我们看看你的价值观,以及它们在二进制表示中的含义:
0.1 - 0.0001100110011001100110011001100110011001100110011001101
0.2 - 0.001100110011001100110011001100110011001100110011001101
0.3 - 0.010011001100110011001100110011001100110011001100110011
0.4 - 0.01100110011001100110011001100110011001100110011001101
0.5 - 0.1
0.6 - 0.10011001100110011001100110011001100110011001100110011
0.7 - 0.1011001100110011001100110011001100110011001100110011
0.8 - 0.1100110011001100110011001100110011001100110011001101
0.9 - 0.11100110011001100110011001100110011001100110011001101
这是什么意思? 0.1
是10的第1位。十进制系统没什么大不了的,只需将分隔符移动一个位置即可。但是在二进制中你不能表示0.1 - 因为小数符号的每个移位等于*2
或/2
- 取决于方向。 (并且10不能分为2个X移位)
对于您想要除以2的倍数的值 - 您会得到一个精确的结果:
1/2 - 0.1
1/4 - 0.01
1/8 - 0.001
1/16- 0.0001
and so on.
因此,尝试计算/10
是一个无限长结果,当值用完比特时会被截断。
这就是说,这是对计算机工作方式的限制,这种值永远不能以完全精确的方式存储。
网站注意:这个"事实"已经被爱国者系统忽略,导致它在几个小时的操作时间后变得无法使用,请参见此处:http://sydney.edu.au/engineering/it/~alum/patriot_bug.html
但为什么它适用于除0.7 + 0.1 之外的所有事情 - 你可能会问
如果您使用0.8
测试代码 - 它可以正常运行 - 但不能使用0.7 + 0.1
。
同样,在二进制中,这两个值都已经不精确了。如果你总结两个值,结果 甚至更不精确,导致错误的结果:
如果总结0.7和0.1(小数点分隔符之后),你得到这个:
0.101100110011001100110011001100110011001100110011001 1000
+ 0.000110011001100110011001100110011001100110011001100 1101
---------------------------------------------------------
0.110011001100110011001100110011001100110011001100110 0101
但0.8会是
0.110011001100110011001100110011001100110011001100110 1000
比较最后4位并注意,得到的" 0.8" ADDITION的值小于将0.8
直接转换为二进制值的情况。
猜猜:
System.out.println(0.7 + 0.1 == 0.8); //returns false
使用数字时,你应该为自己设定一个精度限制 - 并且总是相应地舍入数字以避免这样的错误(不截断!):
//compare doubles with 3 decimals
System.out.println((lim(0.7, 3) + lim(0.1, 3)) == lim(0.8, 3)); //true
public static long lim(double d, int t){
return Math.round(d*10*t);
}
修改代码:将其舍入为4位,然后在第一位数后截断:
public static double truncate(double x){
long y = (long)((Math.round(x*10000)/10000.0)*10);
double z = (double)y/10;
return z;
}
System.out.println(truncate(0.7+0.1)); //0.8
System.out.println(truncate(0.8)); //0.8
这仍然会根据需要进行截断,但会确保0.69999
在截断之前将四舍五入为0.7
。您可以根据应用程序的需要设置精度。 10,20,30,40位?
其他值仍然保持正确,因为0.58999
之类的内容只会四舍五入为0.59 - 因此仍然会被截断为0.5
而不会舍入为0.6
答案 1 :(得分:4)
这不是由您的程序也不是由Java引起的。简单地说,浮点数在设计上是不准确的。你不知道哪些数字是不准确的,但有些数字(在你的情况下为0.7)。关于这个主题的许多文章之一:http://effbot.org/pyfaq/why-are-floating-point-calculations-so-inaccurate.htm
底线:绝不相信双0.7真是0.7。
答案 2 :(得分:3)
问题'使用浮点数如double
或float
。这些数字在内部使用基数2分数来近似大范围的数字,从非常小到非常大。
例如,0.5
可以表示为1/2
,0.75
可以表示为1\2 + 1\4
。
但是,正如您所发现的那样,您无法轻松地在基数10分数和基数2分数之间进行转换。
在基数10 0.7
等于7/10
的情况下,在基数2分数中,这变得非常困难。
这就像尝试在基数为10的十进制数中准确表示1/3
一样,只要你有足够的小数位数,你可以得到一个非常接近的近似值。但是,您无法在基数为10的十进制数字中准确表示1/3
。
答案 3 :(得分:3)
浮点因其设计而具有内在的不准确性。这里的其他答案已经解释了这种不准确性背后的理论。强烈建议您改为使用BigDecimal
和BigInteger
。
在我的回答中,我想详细说明你如何错误地使用BigDecimal
以及如何正确使用它。不要错误地将这些类用作浮点计算的包装器。在您目前的代码中:
BigDecimal a = new BigDecimal(d + 0.1);
即使您尝试在此处使用BigDecimal
,您仍然使用常规浮点计算执行添加。这与:
double d_a = d + 0.1; //0.799999999 ad infinitum
BigDecimal a = new BigDecimal(d_a);
要利用BigX
类的准确性,您必须使用自己的计算方法,以及valueOf
静态方法(而不是构造函数):
BigDecimal a = BigDecimal.valueOf(d).add( BigDecimal.valueOf(0.1) );
此处创建了两个BigDecimal
个对象,以完全匹配 0.7 和 0.1 ,然后使用add
方法计算它们的总和产生第三个BigDecimal
对象(完全 0.8 )。
使用valueOf
静态方法而不是构造函数可确保创建的BigDecimal
对象表示转换为字符串时显示的精确double
值(字符串为0.7时为& #34; 0.7"),而不是计算机存储的近似值来代表它(计算机无限制地将0.7存储为0.699999999)。
答案 4 :(得分:3)
关键是float和double设计用于舍入哲学。如果无法准确表示计算结果,则结果将尽可能接近确切答案,无论是否使其小于或大于精确答案。
0.7 + 0.1
的问题是0.79999999999999993338661852249060757458209991455078125,最接近可表示的值为0.6999999999999999555910790149937383830547332763671875和0.1000000000000000055511151231257827021181583404541015625,最接近0.7和0.1的可表示数字略小于0.8。
有几种可能的解决方案。如果十进制截断是问题的核心,并且比性能和空间更重要,请使用BigDecimal。如果没有,请考虑在截断之前添加一个小调整来说明此效果。实际上,将数字略小于0.8,大于或等于0.8。这可以起作用,因为double
算术引入的差异通常远小于现实世界中出现和重要的差异。
答案 5 :(得分:2)
Java使用IEEE 754格式编码双值。
这意味着每个数字都是尽可能准确的近似值。
0.7最好近似为0.699999999999999999999999
检查出来:http://www.binaryconvert.com/result_double.html?decimal=048046055
要解决您的问题,您可以尝试乘以10.0并相应地处理您的值吗?