在Android Studio中,由于java轮次的方式,我在计算发票总额方面遇到了问题。我知道有很多解释,但许多人建议不能返回可靠结果的方法。
例如:
1. Math.round((双)4.715 *(双)100)/(双)100 = 4.72(预期4.72)
2. Math.round((双)4.725 *(双)100)/(双)100 = 4.72(但预期4.73)
您无法将此代码放入应用中,以便计算发票的客户。因为,在我的情况下,例如,在另一个系统中计算相同的发票,结果是不同的,分别表示4.72和4.73
我知道双精度不能完全表示,小数与我们看到的不同。但我们需要一种能够按预期返回结果的方法。
另一个例子是:
1. java.math.BigDecimal.valueOf(4.715).setScale(2,java.math.BigDecimal.ROUND_HALF_UP).doubleValue()= 4.72
2. new java.math.BigDecimal(4.715).setScale(2,java.math.BigDecimal.ROUND_HALF_UP).doubleValue()= 4.71
3. new java.math.BigDecimal(String.valueOf(4.715))。setScale(2,java.math.BigDecimal.ROUND_HALF_UP).doubleValue()= 4.72
我认为所有这些方面都可以在Java文档中得到很好的解释,但它们应该指出一种计算轮次的特定方法,这是一种可靠的方法,可以按照我们的预期返回结果。我只想要圆满2次。
总之,我希望能帮助一些初学者,我认为以下方法会恢复稳定和良好的效果:
java.math.BigDecimal.valueOf(4.715).setScale(2,java.math.BigDecimal.ROUND_HALF_UP).doubleValue()= 4.72
或者,至少,这是我经过3年多的密集使用应用程序(每个工作日500+用户)后的观察结果
以上所有实际解释都非常受欢迎,因此我们可以更好地了解如何避免意外结果。
答案 0 :(得分:1)
对于BigDecimal示例,javadoc解释了差异。
BigDecimal(double value) ...是double的二进制浮点值的精确十进制表示。
我们可以通过打印值来检查。
System.out.println(new BigDecimal(4.715));
#4.714999999999999857891452847979962825775146484375
这几乎不到4.715,但足以让它向下舍入。
BigDecimal.valueOf(double value)使用Double.toString(double value)中具有相当多规则的double值的字符串表示形式。
System.out.println(Double.toString(4.715));
#4.715
最安全的方法是使用BigDecimal
进行计算。特别是在处理算术运算时。当值转换为需要更多小数位时,不清楚。例如:
double d = 4.11547;
BigDecimal bd = BigDecimal.valueOf(d);
在这种情况下,d的字符串表示形式为4.11547,因此BigDecimal.valueOf返回写入的值。
BigDecimal s1 = BigDecimal.valueOf(d-3);
BigDecimal s2 = bd.subtract(new BigDecimal(3));
发现s1和s2不同可能会令人惊讶,因为' 3'没有四舍五入。
System.out.println(s1 + ", " + s2);
#1.1154700000000002, 1.11547
因此最好也使用BigDecimal方法进行算术运算。
答案 1 :(得分:0)
它具有二进制浮点数据类型的性质,例如Java中的float
和double
。 double
实际上以他的名字陈述了这一点。与float相比,它具有双精度-但它不能精确表示十进制数。
只需在现有答案中添加一些简化的数学细节即可。这可能有助于了解Java浮点数看似奇怪的行为。
此问题的根本原因是数字的二进制表示与十进制表示。当您在代码中使用浮点文字时,例如,使用十进制表示形式。 double d = 1.5;
或字符串值,例如String s = "1.5";
。
但是JVM使用数字的二进制表示形式。整数的映射很容易(d表示十进制,b表示二进制):1 = 1b, 2d = 10b, 3d = 11b ...
。整数没有问题。 int
和long
的工作方式与您期望的一样。除了溢出...
但是对于浮点数,情况有所不同:0.5d = 0.1b, 0.25d = 0.01b, 0.125d = 0.001b...
。您只能添加1 / 2、1 / 4、1 / 8、1 / 16系列的值。现在想象一下,您想以二进制表示形式显示0.1d。
您从0.0001b = 0.0625d
开始,这是第一个仍小于0.1d
的二进制值。剩余0.0375d
。您继续,下一个结束值是0.03125d
,依此类推。您将永远不会完全到达0.1d
。您得到的只是一个近似值。您会越来越近。
请考虑以下代码。它借助BigDecimal值进行近似:
public void approximate0dot1() {
BigDecimal destVal = new BigDecimal("0.1");
BigDecimal curVal = new BigDecimal("0");
BigDecimal inc = new BigDecimal("1");
BigDecimal div = new BigDecimal("2");
for (int step = 0; step < 20; step++) {
BigDecimal probeVal = curVal.add(inc);
int cmp = probeVal.compareTo(destVal);
if (cmp == 0) {
break;
} else if (cmp < 0) {
curVal = probeVal;
System.out.format("Added: %s, current value: %s, remaining: %s\n", inc, curVal, destVal.subtract(curVal));
}
inc = inc.divide(div);
}
System.out.format("Final value: %s\n", curVal);
}
输出为:
Added: 0.0625, current value: 0.0625, remaining: 0.0375
Added: 0.03125, current value: 0.09375, remaining: 0.00625
Added: 0.00390625, current value: 0.09765625, remaining: 0.00234375
Added: 0.001953125, current value: 0.099609375, remaining: 0.000390625
Added: 0.000244140625, current value: 0.099853515625, remaining: 0.000146484375
Added: 0.0001220703125, current value: 0.0999755859375, remaining: 0.0000244140625
Added: 0.0000152587890625, current value: 0.0999908447265625, remaining: 0.0000091552734375
Added: 0.00000762939453125, current value: 0.09999847412109375, remaining: 0.00000152587890625
Final value: 0.09999847412109375
这只是一个显示基本问题的基本示例。在内部,JVM显然会进行一些优化,以获得可用的64位精度的最佳近似值,例如。
System.out.println(new BigDecimal(0.1));
// prints 0.1000000000000000055511151231257827021181583404541015625
但是此示例显示,十进制数字已经存在一个舍入问题,十进制数字简单地作为一个十进制值为0.1的常数。
一些基本提示:
new BigDecimal(0.1)
,很好:new BigDecimal("0.1")
new BigDecimal("0.1").doubleValue();