在 Java 中舍入一个双精度数

Posted

技术标签:

【中文标题】在 Java 中舍入一个双精度数【英文标题】:Round a double in Java 【发布时间】:2014-02-26 09:22:24 【问题描述】:

我找到了一个很好的舍入解决方案:

static Double round(Double d, int precise) 
    BigDecimal bigDecimal = new BigDecimal(d);
    bigDecimal = bigDecimal.setScale(precise, RoundingMode.HALF_UP);
    return bigDecimal.doubleValue();

但是,结果令人困惑:

System.out.println(round(2.655d,2)); // -> 2.65
System.out.println(round(1.655d,2)); // -> 1.66

为什么要给出这个输出?我正在使用 jre 1.7.0_45。

【问题讨论】:

可以打印round方法中BigDecimal的值吗? 相关:***.com/questions/3730019/… 没有错。这就是floating point works。 2.655d 可能是2.654999... 是的,我想知道 java 文档中的邻居是什么意思。下一个内部位表示还是下一个字符串? 你从 double 创建一个 BigDecimal... 这就像试图从黄铁矿中制造黄金:它可能看起来像,但事实并非如此(即,不要那样做,使用黄金开始) 【参考方案1】:

你必须替换

BigDecimal bigDecimal = new BigDecimal(d);

BigDecimal bigDecimal = BigDecimal.valueOf(d);

你会得到预期的结果:

2.66
1.66

来自 Java API 的解释:

BigDecimal.valueOf(double val) - 使用 Double.toString() 方法提供的 double 的规范字符串表示。这是将双精度(或浮点)转换为 BigDecimal 的首选方法。

new BigDecimal(double val) - 使用 double 的二进制浮点值的精确十进制表示,因此此构造函数的结果可能有些不可预测。

【讨论】:

+1,我不知道。但是你应该解释一下why,这会有所作为。 @zapl:- 我试图在我的回答中解释这一点!如果我错过了什么,请告诉我 user3301492,你可以编辑你的答案,如果你把它放在那里而不是评论看起来会更好:) - @RahulTripathi +1 也没有遗漏,也许更多关于字符串转换的解释和然后,字符串构造函数通过不使用普通的 double 作为内部表示来创建精确的表示。 让我补充一点,在处理数字时,使用valuesOf 总是比使用new 更好。原因是,拥有BigDecimal first = new BigDecimal(x)BigDecimal second = new BigDecimal(x)first == second 可能会输出false,而使用valueOf 而不是new 会导致与原始类型的行为更加一致(如果first = BigDecimal.valueOf(x) 和@987654334 @,那么 first == second 将永远是 true)。注意:我不太确定这对BigDecimals 是否有效,但它对其他Numbers 有效 @mardavi 实际上不能保证valueOf(x) == valueOf(x) 用于标准库Number任何 类型。 IntegerLong 的当前 OpenJDK 实现缓存了一些常用值(但不是所有值,因为这将花费更多的内存而不是它的价值),但是 FloatDouble valueOf 方法只是调用构造函数。我仍然同意一般情况下最好使用valueOf【参考方案2】:

您可以尝试像这样更改您的程序:-

static Double round(Double d, int precise) 

BigDecimal bigDecimal = BigDecimal.valueOf(d);
bigDecimal = bigDecimal.setScale(precise, RoundingMode.HALF_UP);
return bigDecimal.doubleValue();

Sample Ideone

Success  time: 0.07 memory: 381184 signal:0
Rounded: 2.66
Rounded: 1.66

Success  time: 0.07 memory: 381248 signal:0
Rounded: 2.66
Rounded: 1.66

BigDecimal.valueOf 而不是new BigDecimal 获得预期结果的原因,用Joachim Sauer 的话来说:

BigDecimal.valueOf(double) 将使用传入的 double 值的 canonical String representation 来实例化 BigDecimal 对象。换句话说:BigDecimal 对象的值将是您在执行System.out.println(d) 时看到的值。

但是,如果您使用new BigDecimal(d),那么 BigDecimal 将尝试尽可能准确地表示双精度值。这通常会导致存储的数字比您想要的多。

因此导致您在节目中观看的一些混乱

来自 Java 文档:

BigDecimal.valueOf(double val) - 使用 double 的规范字符串表示形式将 double 转换为 BigDecimal 由 Double.toString(double) 方法提供。

new BigDecimal(double val) -

将双精度数转换为精确小数的 BigDecimal double 的二进制浮点值的表示。规模 返回的 BigDecimal 是满足 (10scale × val) 是一个整数。备注:

此构造函数的结果可能有些难以预测。有人可能会假设用 Java 编写 new BigDecimal(0.1) 会创建一个 BigDecimal 正好等于 0.1(未缩放的值 1, 标度为 1),但实际上等于 0.10000000000000000055511151231257827021181583404541015625。这是因为 0.1 不能完全表示为双精度(或者,对于那个 物质,作为任何有限长度的二进制分数)。因此,价值 传递给构造函数的不完全等于 0.1,尽管出现了。 另一方面,String 构造函数是完全可预测的:编写 new BigDecimal("0.1") 会创建一个 BigDecimal 正如人们所期望的那样,它恰好等于 0.1。因此,它 一般建议使用String构造函数 偏爱这个。 当必须将 double 用作 BigDecimal 的源时,请注意此构造函数提供了精确的转换;它没有给出 与使用 将双精度转换为字符串的结果相同 Double.toString(double) 方法,然后使用 BigDecimal(String) 构造函数。要获得该结果,请使用静态 valueOf(double) 方法。

【讨论】:

【参考方案3】:

This test case 结束时非常不言自明:

public static void main (String[] args) throws java.lang.Exception

    System.out.println("Rounded: " + round(2.655d,2)); // -> 2.65
    System.out.println("Rounded: " + round(1.655d,2)); // -> 1.66


public static Double round(Double d, int precise)
       
    BigDecimal bigDecimal = new BigDecimal(d);
    System.out.println("Before round: " + bigDecimal.toPlainString());
    bigDecimal = bigDecimal.setScale(precise, RoundingMode.HALF_UP);
    System.out.println("After round: " + bigDecimal.toPlainString());
    return bigDecimal.doubleValue();

输出:

Before round: 2.654999999999999804600747665972448885440826416015625
After round: 2.65
Rounded: 2.65

Before round: 1.6550000000000000266453525910037569701671600341796875
After round: 1.66
Rounded: 1.66

一个肮脏的黑客来解决它是round in two steps:

static Double round(Double d, int precise)

    BigDecimal bigDecimal = new BigDecimal(d);
    System.out.println("Before round: " + bigDecimal.toPlainString());
    bigDecimal = bigDecimal.setScale(15, RoundingMode.HALF_UP);
    System.out.println("Hack round: " + bigDecimal.toPlainString());
    bigDecimal = bigDecimal.setScale(precise, RoundingMode.HALF_UP);
    System.out.println("After round: " + bigDecimal.toPlainString());
    return bigDecimal.doubleValue();

这里,15 比 double 可以表示的以 10 为底的最大位数少一点。输出:

Before round: 2.654999999999999804600747665972448885440826416015625
Hack round: 2.655000000000000
After round: 2.66
Rounded: 2.66

Before round: 1.6550000000000000266453525910037569701671600341796875
Hack round: 1.655000000000000
After round: 1.66
Rounded: 1.66

【讨论】:

这似乎是一个 hack,但它与 valueOf 使用的过程非常相似,只是更明确。转换为字符串会进行第一次舍入。【参考方案4】:

如API中所说

    此构造函数的结果可能有些不可预测。有人可能会假设用 Java 编写 new BigDecimal(0.1) 会创建一个 BigDecimal 正好等于 0.1(未缩放的值 1,与 比例为 1),但实际上等于 0.10000000000000000055511151231257827021181583404541015625。这是因为 0.1 不能完全表示为双精度数(或者,对于 物质,作为任何有限长度的二进制分数)。因此,价值 传递给构造函数的不完全等于 0.1,尽管外观。

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

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

这是因为不能准确表示双精度值。所以你必须使用BigDecimal bigDecimal = BigDecimal.valueOf(d); 而不是BigDecimal bigDecimal = new BigDecimal(d);

【讨论】:

【参考方案5】:

舍入 doubleDouble 本身并没有多大意义,因为 double 数据类型不能舍入(很容易,或根本?)。

你正在做的是:

    Double d 作为输入,在分隔符后面输入int precise 位数。 从d 创建一个BigDecimal。 将BigDecimal 正确舍入。 返回 BigDecimaldouble 值,该值不再有四舍五入应用于它。

你可以有两种方式:

    您可以返回一个代表圆角双精度的BigDecimal,然后再决定如何处理它。 您可以返回一个String,代表四舍五入的BigDecimal

这两种方法中的任何一种都有意义。

【讨论】:

+1 因为这是最正确的答案。一个挑剔的问题:你当然可以四舍五入,实际上它们是隐式四舍五入的 - 但二进制,数字,而不是十进制。【参考方案6】:

十进制数不能精确地用双精度表示。

所以 2.655 最终是这样的: 2.65499999999999980460074766597

而 1.655 最终是这样的: 1.655000000000000026645352591

【讨论】:

为什么第一个是499999..,第二个是500000.. 这不准确; 一些/许多十进制数无法表示。 嗯,是的,确切地说。由于 double 是二进制格式,所有具有精确二进制表示(适合 double)的十进制数都可以表示,而其他所有数字都不能。 Maroun,看看这个:binaryconvert.com/convert_double.html

以上是关于在 Java 中舍入一个双精度数的主要内容,如果未能解决你的问题,请参考以下文章

将双精度数舍入到小数点后一位(去掉小数位)

将双精度数舍入到 x 有效数字

如何舍入双精度值但保持尾随零

在 Objective-C 中舍入双数

在java中将双精度值舍入为两位有效数字[重复]

如何在 Java 中舍入整数除法并获得 int 结果? [复制]