为什么这个奇怪的输出有truncate和BigDecimal?

时间:2014-10-11 09:08:48

标签: java double bigdecimal

我正在调用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问题是什么?

6 个答案:

答案 0 :(得分:7)

(如果您对理论不感兴趣,请滚动到结束,这是代码的修复程序)

原因很简单:如您所知,二进制系统仅支持01 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)

问题'使用浮点数如doublefloat。这些数字在内部使用基数2分数来近似大范围的数字,从非常小到非常大。

例如,0.5可以表示为1/20.75可以表示为1\2 + 1\4

但是,正如您所发现的那样,您无法轻松地在基数10分数和基数2分数之间进行转换。

在基数10 0.7等于7/10的情况下,在基数2分数中,这变得非常困难。

这就像尝试在基数为10的十进制数中准确表示1/3一样,只要你有足够的小数位数,你可以得到一个非常接近的近似值。但是,您无法在基数为10的十进制数字中准确表示1/3

答案 3 :(得分:3)

浮点因其设计而具有内在的不准确性。这里的其他答案已经解释了这种不准确性背后的理论。强烈建议您改为使用BigDecimalBigInteger

在我的回答中,我想详细说明你如何错误地使用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并相应地处理您的值吗?