浮点算术未产生精确结果

2022-09-01 06:49:10

我需要在Java中做一些浮点算术,如下面的代码所示:

public class TestMain {
    private static Map<Integer, Double> ccc = new HashMap<Integer, Double>() {
      { put(1, 0.01); put(2, 0.02); put(3, 0.05); put(4, 0.1); put(6, 0.2);
        put(10, 0.5); put(20, 1.0); put(30, 2.0); put(50, 5.0); put(100, 10.0);
      }
    };

    Double increment(Double i, boolean up) {
        Double inc = null;

        while (inc == null) {
            inc = ccc.get(i.intValue());

            if (up)
                --i;
            else
                ++i;
        }
        return inc;
    }

    public static void main(String[] args) {
        TestMain tt = new TestMain();

        for (double i = 1; i < 1000; i += tt.increment(i, true)) {
            System.out.print(i + ",");
        }
    }
}

这是为了模拟 Betfair 微调器小部件输出的值范围。

Java中的浮点算术似乎引入了一些意想不到的错误。例如,我得到2.1800000000000001而不是2.18。浮点数有什么用是你不能相信对它们执行的算术结果?如何解决此问题?


答案 1

如果需要精确的十进制值,则应使用 。然后阅读“每个计算机科学家都应该知道的关于浮点算术的知识”,了解为什么你会得到这些结果的背景。java.math.BigDecimal

(我有一个.以 NET 为中心的文章,您可能会发现它更容易阅读 - 当然也更短。Java和.NET之间的差异对于理解这个问题来说大多无关紧要。


答案 2

浮点数使用二进制分数,而不是小数分数。也就是说,您习惯于由十分之一位,百分之一位,千分之一位等组成的小数点。但是浮点数是二进制的,所以它们有一个半位数,一个四分之一数字,一个八分之一数字,依此类推。

许多小数分数不能用任何有限数量的二进制数字精确表示。例如,1/2是没有问题的:在十进制中它是.5,在二进制中它是.1。3/4 是十进制 .75,二进制 .11。但是1/10在十进制中是一个干净的.1,但在二进制中它是.0001100110011...随着“0011”永远重复。由于计算机只能存储有限数量的数字,因此在某些时候必须将其砍掉,因此答案并不精确。当我们在输出上转换回十进制时,我们得到一个看起来很奇怪的数字。

正如Jon Skeet所说,如果你需要精确的小数,请使用BigDecimal。如果性能有问题,您可以滚动自己的小数部分。例如,如果您知道您总是想要正好3个小数位并且数字不会超过一百万左右,则可以简单地使用带有假设3个小数位的int,在进行算术时根据需要进行调整,并编写输出格式函数以将小数点插入正确的位置。但是,99%的时间性能并不是一个足够大的问题,不值得麻烦。