在Android Studio中使用舍入进行观察 - java。并期待一些实际的解释

时间:2017-07-27 12:01:46

标签: java rounding bigdecimal

在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+用户)后的观察结果 以上所有实际解释都非常受欢迎,因此我们可以更好地了解如何避免意外结果。

2 个答案:

答案 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中的floatdoubledouble实际上以他的名字陈述了这一点。与float相比,它具有双精度-但它不能精确表示十进制数。

只需在现有答案中添加一些简化的数学细节即可。这可能有助于了解Java浮点数看似奇怪的行为。

此问题的根本原因是数字的二进制表示与十进制表示。当您在代码中使用浮点文字时,例如,使用十进制表示形式。 double d = 1.5;或字符串值,例如String s = "1.5";

但是JVM使用数字的二进制表示形式。整数的映射很容易(d表示十进制,b表示二进制):1 = 1b, 2d = 10b, 3d = 11b ...。整数没有问题。 intlong的工作方式与您期望的一样。除了溢出...

但是对于浮点数,情况有所不同: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的常数。

一些基本提示:

  • 如果需要精确的十进制数学,请不要使用BigDecimal(double)构造函数,而应使用BigDecimal(String)。不好:new BigDecimal(0.1),很好:new BigDecimal("0.1")
  • 请勿混用BigDecimal和浮点运算,例如不要提取双精度值以进行进一步的计算,例如new BigDecimal("0.1").doubleValue();