use*_*078 10 java double bigdecimal
我正在调用truncate方法来截断double值,以便在十进制之后应该有一个数字(没有四舍五入),
For Ex.truncate(123.48574) = 123.4.
我的截断方法是这样的
public double truncate (double x) {
long y = (long) (x * 10);
double z = (double) (y / 10);
return z;
}
Run Code Online (Sandbox Code Playgroud)
除了这个奇怪的输出外,几乎所有的值都能正常工作.
double d = 0.787456;
d = truncate(d + 0.1); //gives 0.8 as expected. Okay.
Run Code Online (Sandbox Code Playgroud)
但,
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?
Run Code Online (Sandbox Code Playgroud)
事实上它适用于所有其他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
Run Code Online (Sandbox Code Playgroud)
我也试过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
Run Code Online (Sandbox Code Playgroud)
而真正的事实是,它可以正常工作Math.round.
public double roundUp1d (double d) {
return Math.round(d * 10.0) / 10.0;
}
Run Code Online (Sandbox Code Playgroud)
所以,如果我打电话roundUp1d(0.7 + 0.1),它会按预期给出0.8.但我不希望这些值被舍入,所以我不能使用它.
什么是0.7的问题?
(如果您对理论不感兴趣,请滚动到结束,这是代码的修复)
原因很简单:如你所知,二进制系统只支持0s和1s
那么,让我们看一下你的值,以及它们在二进制表示中的含义:
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
Run Code Online (Sandbox Code Playgroud)
那是什么意思?0.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.
Run Code Online (Sandbox Code Playgroud)
因此,尝试计算a /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
Run Code Online (Sandbox Code Playgroud)
但是0.8会
0.110011001100110011001100110011001100110011001100110 1000
Run Code Online (Sandbox Code Playgroud)
比较最后4位并注意,ADDITION的结果"0.8"小于0.8直接转换为二进制的结果.
你猜怎么着:
System.out.println(0.7 + 0.1 == 0.8); //returns false
Run Code Online (Sandbox Code Playgroud)
使用数字时,您应该为自己设置一个精度限制 - 并且总是相应地舍入数字以避免此类错误(不截断!):
//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);
}
Run Code Online (Sandbox Code Playgroud)
修改代码:将其四舍五入为止,然后在第一个数字后截断:
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
Run Code Online (Sandbox Code Playgroud)
这仍然会根据需要截断但确保在截断之前0.69999将a 四舍五入0.7.您可以根据应用程序的需要设置精度.10,20,30,40位数?
其他值仍将保持正确,因为类似的东西0.58999将被舍入到0.59 - 所以仍然被截断为0.5而不是舍入到0.6
浮点的设计本质上是不准确的。这里的其他答案已经解释了这种不准确背后的理论。强烈建议您使用BigDecimalandBigInteger代替。
在我的回答中,我想详细说明您如何BigDecimal错误使用它以及如何正确使用它。不要错误地简单地使用这些类作为浮点计算的包装器。在您当前的代码中:
BigDecimal a = new BigDecimal(d + 0.1);
Run Code Online (Sandbox Code Playgroud)
即使您尝试BigDecimal在此处使用,您仍然使用常规浮点计算来执行加法。这与执行以下操作完全相同:
double d_a = d + 0.1; //0.799999999 ad infinitum
BigDecimal a = new BigDecimal(d_a);
Run Code Online (Sandbox Code Playgroud)
为了利用类的准确性BigX,您必须使用它们自己的计算方法,以及valueOf静态方法(而不是构造函数):
BigDecimal a = BigDecimal.valueOf(d).add( BigDecimal.valueOf(0.1) );
Run Code Online (Sandbox Code Playgroud)
BigDecimal在这里,创建两个对象以精确匹配0.7和0.1,然后add使用该方法计算它们的总和并生成第三个BigDecimal对象(精确为0.8)。
使用valueOf静态方法而不是构造函数可确保创建的对象表示转换为字符串时显示的BigDecimal精确值(0.7 作为字符串是“0.7”),而不是计算机存储的表示它的近似值(double计算机将 0.7 存储为 0.699999999 无限)。