7

我尝试了以下代码。但是使用 BigDecimal 进行减法时会得到不同的结果。

    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

我仍在努力。谁能澄清一下。

4

8 回答 8

11

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


答案是“0E-59”等于0。回想一下,aBigDecimal是未缩放的值和十进制比例因子的组合:

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

下一个明显的问题是,为方便起见,为什么不BigDecimal.toString直接显示b1为“ 0”?答案是字符串表示必须是明确的。来自Javadoc 的toString

BigDecimal可区分值与此转换的结果之间存在一对一的映射。也就是说,每个可区分的BigDecimal值(未缩放的值和比例)都有一个唯一的字符串表示作为使用的结果toStringBigDecimal如果使用构造函数将该字符串表示转换回 a BigDecimal(String),则将恢复原始值。

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

于 2013-03-15T09:35:24.293 回答
7

使用 String 中的构造函数:b1 = new BigDecimal("0.01");

Java 精度损失

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

于 2013-03-15T09:24:37.727 回答
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 文档页面上描述的相当复杂的行为正在按预期工作。具体来说,这一点指出:

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

原始值需要可恢复的事实意味着toString()需要为每个比例生成一个唯一的字符串,这就是你得到0e-59. 否则,将字符串转换aBigDecimal可能会给您一个不同的值(未缩放值/缩放元组)。

如果您真的希望零无论比例如何都显示为“0”,您可以使用以下内容:

if (b1.compareTo(BigDecimal.ZERO) == 0) b1 = new BigDecimal(0);
于 2013-03-15T09:23:18.970 回答
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.toString()BigDecimal

最后,0E-59 0.0- 它在0*10^59数学上计算为 0。因此,意想不到的结果是BigDecimal.

要获取浮点数或双精度值,请使用

b1.floatValue());

或者

b1.doubleValue());

两者都评估为0.0

于 2013-03-15T09:26:37.897 回答
2

你必须得到返回值:

BigDecimal b3 = b1.subtract(b2);
System.out.println("BigDecimal result: "+ b3);
于 2013-03-15T09:23:51.220 回答
2

BigDecimal(双值)

1.此构造函数的结果可能有些不可预测。有人可能会假设用 Java 编写 new BigDecimal(0.1) 会创建一个正好等于 0.1 的 BigDecimal(未缩放的值 1,缩放为 1),但它实际上等于 0.10000000000000000555511151231257827021181583404541015625。这是因为 0.1 不能完全表示为双精度数(或者,就此而言,不能表示为任何有限长度的二进制分数)。因此,传递给构造函数的值并不完全等于 0.1,尽管看起来如此。

2. 另一方面,String 构造函数是完全可预测的:编写 new BigDecimal("0.1") 会创建一个 BigDecimal,它完全等于 0.1,正如人们所期望的那样。因此,一般建议优先使用 String 构造函数。

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

于 2013-03-15T09:31:48.443 回答
1

这是一个已知问题,BigDecimal(double val) API此构造函数的结果可能有些不可预测。虽然在这种交互中看起来真的很奇怪。实际原因是 new 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));
于 2013-03-15T09:28:12.217 回答
1

像这样使用:

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

b1 = b1.subtract(b2);
System.out.println("BigDecimal result: "+ b1);
于 2016-10-20T07:08:10.290 回答