2013-03-15 32 views
7

我试过下面的代码。但在使用BigDecimal进行减法时会得到不同的结果。java bigdecimal subraction失败

double d1 = 0.1; 
    double d2 = 0.1; 
    System.out.println("double result: "+ (d2-d1)); 

    float f1 = 0.1F; 
    float f2 = 0.1F; 
    System.out.println("float result: "+ (f2-f1)); 

    BigDecimal b1 = new BigDecimal(0.01); 
    BigDecimal b2 = new BigDecimal(0.01); 

    b1 = b1.subtract(b2); 
    System.out.println("BigDecimal result: "+ b1); 

结果:

double result: 0.0 
float result: 0.0 
BigDecimal result: 0E-59 

我还在这方面的工作。任何人都可以请澄清。

+0

0E-59仍然是零,并且规模是从输入的刻度的。 – EJP 2013-03-16 00:24:05

+0

答案涵盖了原因,下面是一个简单的提示:如果您使用BigDecimal,则需要一些有用的缩放比例,因此,当您调用具有某个double值的C-tor时,始终需要包含MathContext。 – bestsss 2013-03-17 11:05:38

回答

4

有意思的是,这些值看起来是相等的,减法确实会给你零,但它似乎只是打印代码的一个问题。下面的代码:

import java.math.BigDecimal; 
public class Test { 
    public static void main(String args[]) { 
     BigDecimal b1 = new BigDecimal(0.01); 
     BigDecimal b2 = new BigDecimal(0.01); 
     BigDecimal b3 = new BigDecimal(0); 
     if (b1.compareTo(b2) == 0) System.out.println("equal 1"); 
     b1 = b1.subtract(b2); 
     if (b1.compareTo(b3) == 0) System.out.println("equal 2"); 
     System.out.println("BigDecimal result: "+ b1); 
    }       
} 

输出equal消息,表明该值相同的,你得到当你减去。

您可以尝试将其作为错误提示并查看Oracle回来的内容。很可能他们会说0e-59仍然是零,所以不是错误,或者BigDecimal documentation page上描述的相当复杂的行为是按照预期工作的。具体而言,指出:

在可区分的BigDecimal值和此转换的结果之间存在一对一映射。也就是说,作为使用toString的结果,每个可区分的BigDecimal值(非标度值和标度)都具有唯一的字符串表示形式。如果使用BigDecimal(String)构造函数将该字符串表示转换回BigDecimal,则原始值将被恢复。

这一事实,原来的值需要收回意味着toString()需要为每一个规模,这就是为什么你得到0e-59唯一字符串。否则,将字符串转换回BigDecimal可能会给你一个不同的值(非缩放值/缩放元组)。

如果你真的零显示为“0”,无论规模,你可以使用类似:

if (b1.compareTo(BigDecimal.ZERO) == 0) b1 = new BigDecimal(0); 
+1

我不认为这是一个错误; 'BigDecimal.toString'打印标准表示,所以它必须以明确的方式表示比例因子和非标度值。比例因子是59,因为减法输入的比例因子是59 ... – 2013-03-15 09:52:19

+0

@Oli,实际上你是对的,这种行为是每个doco有意识的。 – paxdiablo 2013-03-15 10:05:02

2

你必须得到的返回值:

BigDecimal b3 = b1.subtract(b2); 
System.out.println("BigDecimal result: "+ b3); 
+0

他正在做'b1 = b1.subtract(b2);' – Austin 2013-03-15 09:24:15

+0

刚刚编辑。对不起 – user1514499 2013-03-15 09:24:43

+0

没问题!在编辑问题后,您遇到了浮点数精度有限的问题,这个答案显然不能解决问题:) – ghdalum 2013-03-15 09:35:38

7

从字符串中使用构造函数:b1 = new BigDecimal("0.01");

Java loss of precision

(幻灯片23) http://strangeloop2010.com/system/talks/presentations/000/014/450/BlochLee-JavaPuzzlers.pdf

+1

确实。但是这并没有回答这个问题。为什么差异打印奇怪? – 2013-03-15 09:28:19

+0

你是说b1和b2会被设置为不同的值,尽管它们的创建方式完全一样吗? – paxdiablo 2013-03-15 09:28:24

+0

这就是BigDecimal的工作方式。减法的结果是BigDecimal,它存储0,但它具有从“0.01”构造的“bad”BigDecimal的“继承”尺度(e-59)。 – 2013-03-15 09:39:36

2

所以真正的问题是:用下面的代码,

BigDecimal b1 = new BigDecimal(0.01); 
BigDecimal b2 = new BigDecimal(0.01); 
b1 = b1.subtract(b2); 

为什么b1.toString()评估为"0E-59",而不是像"0.0""0E0"或者只是"0"


其原因是toString()打印BigDecimal规范格式。有关更多信息,请参阅BigDecimal.toString()

在末端,0E-590.0 - 它是0*10^59其数学计算结果为0,所以,意想不到的结果是BigDecimal的内部表示的问题。

要获得float或double值,使用

b1.floatValue()); 

b1.doubleValue()); 

两个评估为0.0

+0

但是,我希望b1和b2完全一样,都不是0.01。所以减法应该完全产生0. – paxdiablo 2013-03-15 09:29:25

+0

正确 - 我承认我没有回答这个部分... – 2013-03-15 09:30:00

1

这是一个已知问题BigDecimal(double val)API 此构造函数的结果可能有点不可预知。虽然它看起来真的很奇怪,在这种interpertation。实际原因是新的BigDecimal(0.01)产生一个BigDecimal与大约值

0.01000000000000000020816681711721685132943093776702880859375 

具有长精度,所以减法的结果有很长的精度也。

无论如何,我们可以解决的 “问题” 这种方式

BigDecimal b1 = new BigDecimal("0.01"); 
BigDecimal b2 = new BigDecimal("0.01"); 

,或者我们可以使用一个构造函数设置精度

BigDecimal b1 = new BigDecimal(0.01, new MathContext(1)); 
BigDecimal b2 = new BigDecimal(0.01, new MathContext(1)); 
+0

什么是已知问题? – 2013-03-15 09:29:44

+0

BigDecimal(double val)API这个构造函数的结果可能会有些不可预知.. – 2013-03-15 09:32:58

+1

通过“不可预测”,我希望它们不意味着“非确定性”... – 2013-03-15 09:33:41

2

BigDecimal(double val)

1.结果这个构造函数可能有点不可预测。有人可能会认为在Java中编写新的BigDecimal(0.1)会创建一个大小等于0.1的012DecimalDecimal(非缩放值为1,其中 的比例为1),但实际上它等于 0.1000000000000000055511151231257827021181583404541015625。这是因为0.1不能完全表示为双精度(或者对于任何有限长度的二元分数)。因此,传递给构造函数的值 并不完全等于 0.1,尽管如此。另一方面,字符串构造函数是完全可预测的:编写新的BigDecimal(“0.1”)会创建一个BigDecimal,正如我们所期望的那样,它等于0.1,即 。因此,一般建议 使用String构造函数而不是此 之一。

3.当double必须用作BigDecimal的源时,请注意,此构造函数提供了精确的转换;它不会给出与使用 Double.toString(double)方法,然后使用BigDecimal(String) 构造函数将double转换为字符串相同的结果 。要获得该结果,请使用静态valueOf(double) 方法。

+2

确实。但是这并没有回答这个问题。为什么差异打印奇怪? – 2013-03-15 09:33:00

11

[有很多答案在这里告诉你,二进制浮点不能完全代表0.01,并暗示你所看到的结果是有点不精确。虽然第一部分是真的,但这不是真正的核心问题。]


答案是 “0E-59” 等于0。回想一下,一个BigDecimal是未测量的值和小数比例因子的组合:

System.out.println(b1.unscaledValue()); 
System.out.println(b1.scale()); 

显示:

0 
59 

的非标度值 0,如预期。该“奇”刻度值是简单地为0.01非精确浮点表示的十进制扩展的伪影:

System.out.println(b2.unscaledValue()); 
System.out.println(b2.scale()); 

显示:

1000000000000000020816681711721685132943093776702880859375 
59 

下一个明显的问题是,为什么没有按't BigDecimal.toString只是为了方便而将b1显示为“0”?答案是字符串表示必须是明确的。从Javadoc for toString

在可区分的BigDecimal值与此转换的结果之间存在一对一映射。也就是说,作为使用toString的结果,每个可区分的BigDecimal值(非标度值和标度)都具有唯一的字符串表示形式。如果该字符串表示使用BigDecimal(String)构造函数转换回BigDecimal,则原始值将被恢复。

如果它刚刚显示“0”,那么你将无法回到这个确切的BigDecimal对象。

+0

我试着用BigDecimal b1 = new BigDecimal(0.05); \t \t BigDecimal b2 = new BigDecimal(0.01);获得BigDecimal结果1:4000000000000000256739074444567449972964823246002197265625 BigDecimal结果2:59.与您的相似... – user1514499 2013-03-15 09:47:30

+0

@ user1514499:好的,那是一个不同的问题!但答案是相关的;您无法在二进制浮点中精确地表示“0.01”或“0.05”。他们近似。 – 2013-03-15 09:49:16

+0

该死的,因为我正在编辑最后一个片段(双向转换的东西)到我的答案中,我想出了空气,发现你已经钉了它:-) – paxdiablo 2013-03-15 10:13:40

1

使用这样的:

BigDecimal b1 = BigDecimal.valueOf(0.01); 
BigDecimal b2 = BigDecimal.valueOf(0.01); 

b1 = b1.subtract(b2); 
System.out.println("BigDecimal result: "+ b1);