将double转换为BigDecimal并设置BigDecimal的精度

103
在Java中,我想将一个double值转换为BigDecimal,并以一定的精度打印其字符串值。
import java.math.BigDecimal;
public class Main {
    public static void main(String[] args) {
        double d=-.00012;
        System.out.println(d+""); //This prints -1.2E-4

        double c=47.48000;
        BigDecimal b = new BigDecimal(c);
        System.out.println(b.toString()); 
        //This prints 47.47999999999999687361196265555918216705322265625 
    }
}

它打印了这个巨大的东西:

47.47999999999999687361196265555918216705322265625

而不是

47.48

我使用 BigDecimal 进行转换的原因是,有时 double 值会包含许多小数位(例如 -.000012),在将 double 转换为字符串时会产生科学计数法 -1.2E-4。我想以非科学计数法存储字符串值。

我希望 BigDecimal 总是具有两个单位的精度,如“47.48”。BigDecimal 可以限制转换为字符串时的精度吗?


1
注意:BigDecimal有时也会使用科学计数法(我相信对于小于10^-6的数字)。 - assylias
9个回答

160

这种行为的原因是打印出来的字符串是精确值——可能不是您预期的,但那是内存中实际存储的真实值——这只是浮点数表示的限制。

根据Javadoc的说明,如果您没有考虑到这个限制,则BigDecimal(double val)构造函数的行为可能会出现意外情况:

此构造函数的结果可能有些不可预测。有人可能会认为在Java中编写new BigDecimal(0.1)会创建一个完全等于0.1的BigDecimal(无标度值为1,具有标度为1),但它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法完全表示为double(或者说任何有限长度的二进制分数)。因此,传入构造函数的值并非完全等于0.1,尽管表面上看起来是这样。

因此,在您的情况下,不要使用

double val = 77.48;
new BigDecimal(val);

使用

BigDecimal.valueOf(val);

BigDecimal.valueOf 返回的值等于调用 Double.toString(double) 后返回的结果。


3
今天我遇到了完全相同的问题,这个回答在我看来是最合适的!谢谢! - PhilDulac
5
我建议在 BigDecimal.valueOf(val) 后面添加 .stripTrailingZeros() 以省略尾随零。例如:BigDecimal.valueOf(15.0).stripTrailingZeros(); -> 15(如果您不调用 .stripTrailingZeros(),则为 15.0)。 - pierrefevrier
这应该是被接受的答案。 - Aayush Goyal

67

如果使用另一个MathContext,它将打印出47.48000:

BigDecimal b = new BigDecimal(d, MathContext.DECIMAL64);

只需选择您需要的上下文。


19
不建议使用双重构造函数。 - assylias
1
https://dev59.com/0HNA5IYBdhLWcg3wL6yx - omnomnom
56
BigDecimal b = BigDecimal.valueOf(d);将double类型的变量d转换为BigDecimal类型的变量b。 - rvazquezglez
1
@rvazquezglez,我喜欢你的解决方案。 - Kurt Shaw
1
仍然存在其他值的缺陷。例如,new BigDecimal(1E-11, MathContext.DECIMAL64).toPlainString() 打印 0.000000000009999999999999999,但是 BigDecimal.valueOf 却能按预期工作。 - cdalxndr

20
你想尝试使用 String.format("%f", d),这将以十进制表示法打印你的双精度数。不要使用 BigDecimal
关于精度问题:首先将 47.48 存储在 double c 中,然后从该 double 创建一个新的 BigDecimal。精度损失是在分配给 c 上发生的。你可以这样做:
BigDecimal b = new BigDecimal("47.48")

为了避免失去任何精度。


11

为什么不呢:

b = b.setScale(2, RoundingMode.HALF_UP);

为什么不使用BigDecimal b = new BigDecimal(c).setScale(2,BigDecimal.ROUND_HALF_UP)呢? - z atef

6
它打印出实际的、精确的double值。 Double.toString()将double转换为String,但它不会打印输入的精确十进制值——如果 x 是您的double值,则它会打印足够多的数字,以使 x 是最接近所打印值的double。 重点是不存在精确的47.48double。双精度浮点数将值存储为二进制小数,而不是十进制小数,因此无法存储精确的十进制值。(这就是 BigDecimal 的用途!)

5

在Java 9中,以下内容已被弃用:

BigDecimal.valueOf(d).setScale(2, BigDecimal.ROUND_HALF_UP);

应使用以下内容替代:

BigDecimal.valueOf(d).setScale(2, RoundingMode.HALF_UP);

例子:

    double d = 47.48111;

    System.out.println(BigDecimal.valueOf(d)); //Prints: 47.48111

    BigDecimal bigDecimal = BigDecimal.valueOf(d).setScale(2, RoundingMode.HALF_UP);
    System.out.println(bigDecimal); //Prints: 47.48

3

String.format语法帮助我们将double和BigDecimal转换为任意精度的字符串。

以下是Java代码示例:

double dennis = 0.00000008880000d;
System.out.println(dennis);
System.out.println(String.format("%.7f", dennis));
System.out.println(String.format("%.9f", new BigDecimal(dennis)));
System.out.println(String.format("%.19f", new BigDecimal(dennis)));

输出:

8.88E-8
0.0000001
0.000000089
0.0000000888000000000

2
BigDecimal b = new BigDecimal(c).setScale(2,BigDecimal.ROUND_HALF_UP);

0

使用 BigDecimal.valueOf() 进行包装:

double d = 47.48111;
BigDecimal.valueOf(d)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接