浮点算法不能产生精确的结果

时间:2009-11-02 13:15:30

标签: java floating-point

我需要在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 spinner widget作为输出给出的值范围。

Java中的浮点运算似乎会引入一些意外错误。例如,我得到2.180000000000001而不是2.18。浮点数的用途是什么,你不能相信对它们执行算术的结果?我怎样才能解决这个问题?

7 个答案:

答案 0 :(得分:33)

如果您需要精确的十进制值,则应使用java.math.BigDecimal。然后阅读"What Every Computer Scientist Should Know About Floating-Point Arithmetic",了解获得这些结果的背景。

(我有一个.NET-centric article,您可能会发现它更容易阅读 - 当然也更短。为了理解这个问题,Java和.NET之间的差异大多不相关。)

答案 1 :(得分:11)

浮点数使用二进制分数而不是小数分数。也就是说,你习惯于由十分位数,百分位数,千分位数等组成的小数分数.d1 / 10 + d2 / 100 + d3 / 1000 ......但是浮点数是二进制的,所以他们有半位数,四分之一位数,第八位数等.d1 / 2 + d2 / 4 + d3 / 8 ...

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

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

答案 2 :(得分:3)

浮点数是不精确的,特别是因为它们以二进制分数(1 / 2,1 / 4,1 / 8,1 / 16,1 / 32,...)而不是小数分数(1/10)工作,1 / 100,1 / 1000,......)。只需定义您认为“足够接近”的内容并使用类似Math.abs(a-b) < 0.000001的内容。

答案 3 :(得分:2)

在哲学上,我想知道:今天大多数计算机CPU都内置支持整数运算和浮点运算,但不支持十进制运算。为什么不?由于这种四舍五入的问题,我没有在浮动数可用的年份编写申请。您当然不能将它们用于金额:没有人想在“$ 42.3200003”的销售收据上打印价格。没有会计师会接受“我们可能会在这里和那里一分钱,因为我们使用二进制分数并且有舍入错误”。

浮子可用于测量,例如距离或温度,其中没有“精确答案”这样的东西,你必须在某些时候完成你的乐器的精确度。我想对于在化学实验室中编程计算机的人来说,常规使用浮子。但对于我们这些在商业领域的人来说,他们几乎没用。

回到我在大型机上编程的古代,IBM 360系列CPU内置了对压缩十进制算术的支持。它们存储了每个字节保存两个十进制数字的字符串,即前四位的值为0到9,而后两位为4,CPU具有操作它们的算术函数。为什么英特尔不能做那样的事情?然后Java可以添加“十进制”数据类型,我们不需要所有额外的垃圾。

当然,我不是要废除花车。只需添加小数。

哦,好吧,随着伟大的社交活动的开展,我不认为这会在街头引起很多人们的兴奋或骚乱。

答案 4 :(得分:1)

您可以使用格式化输出使程序的输出看起来更像您期望的那样。

http://java.sun.com/javase/6/docs/api/java/util/Formatter.html

显然,底层浮点运算仍然有效,但至少输出会更具可读性。

例如,要将结果舍入到两位小数:

System.out.print(String.format(".2f", i) + ","); 

答案 5 :(得分:0)

您可以编写一些代码来计算机器上的Epsilon。我相信std :: C ++定义它,它根据你正在使用的东西以其他方式定义。

private static float calcEpsilonFloat() {
    float epsi = 1.0f;


    while ((float) (1.0 + (epsi / 2.0)) != 1.0)
    {
       epsi /= 2.0f;
    }

    return epsi;
}

我唯一一次担心Epsilon是在比较信号进行阈值处理的时候。即便如此,我也不确定我是否真的需要担心它,根据我的经验,如果你担心Epsilon 你可能首先要考虑其他问题。

答案 6 :(得分:-1)

顺便说一句,你可以尝试使用这个函数来确保你的数字被重新格式化(不会有太多十进制数字),只保留你需要的小数。

http://pastebin.com/CACER0xK

n是否包含大量小数(例如Math.PI), numberOfDecimals是您需要的最大小数位数(例如,3.14为2或3.151为3)。

理论上,将负值置于numberOfDecmals,它也会切断数字的低整数位。例如,放置n=1588.22numberOfDecimals=-2,函数将返回1500.0

让我知道这是不是错了。