在 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
)。注意:我不太确定这对BigDecimal
s 是否有效,但它对其他Number
s 有效
@mardavi 实际上不能保证valueOf(x) == valueOf(x)
用于标准库Number
的任何 类型。 Integer
和 Long
的当前 OpenJDK 实现缓存了一些常用值(但不是所有值,因为这将花费更多的内存而不是它的价值),但是 Float
和 Double
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】:舍入 double
和 Double
本身并没有多大意义,因为 double
数据类型不能舍入(很容易,或根本?)。
你正在做的是:
-
将
Double d
作为输入,在分隔符后面输入int precise
位数。
从d
创建一个BigDecimal
。
将BigDecimal
正确舍入。
返回 BigDecimal
的 double
值,该值不再有四舍五入应用于它。
你可以有两种方式:
-
您可以返回一个代表圆角双精度的
BigDecimal
,然后再决定如何处理它。
您可以返回一个String
,代表四舍五入的BigDecimal
。
这两种方法中的任何一种都有意义。
【讨论】:
+1 因为这是最正确的答案。一个挑剔的问题:你当然可以四舍五入,实际上它们是隐式四舍五入的 - 但二进制,数字,而不是十进制。【参考方案6】:十进制数不能精确地用双精度表示。
所以 2.655 最终是这样的: 2.65499999999999980460074766597
而 1.655 最终是这样的: 1.655000000000000026645352591
【讨论】:
为什么第一个是499999..
,第二个是500000..
?
这不准确; 一些/许多十进制数无法表示。
嗯,是的,确切地说。由于 double 是二进制格式,所有具有精确二进制表示(适合 double)的十进制数都可以表示,而其他所有数字都不能。 Maroun,看看这个:binaryconvert.com/convert_double.html以上是关于在 Java 中舍入一个双精度数的主要内容,如果未能解决你的问题,请参考以下文章