Java 浮点运算不能产生准确的结果

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1661273/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 18:49:40  来源:igfitidea点击:

Floating point arithmetic not producing exact results

javafloating-point

提问by

I need to do some floating point arithmetic in Java as shown in the code below:

我需要在 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 + ",");
        }
    }
}

This is to simulate the range of values given as output by the Betfair spinner widget.

这是为了模拟Betfair spinner 小部件作为输出给出的值范围。

Floating point arithmetic in Java seems to introduce some unexpected errors. For example, I get 2.180000000000001 instead of 2.18. What use are floating point numbers is you can't trust the results of arithmetic performed on them? How can I get around this issue?

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

采纳答案by Jon Skeet

If you need exact decimalvalues, you should use java.math.BigDecimal. Then read "What Every Computer Scientist Should Know About Floating-Point Arithmetic"for the background of why you're getting those results.

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

(I have a .NET-centric articlewhich you may find easier to read - and certainly shorter. The differences between Java and .NET are mostly irrelevant for the purposes of understanding this issue.)

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

回答by Anthony Mills

Floating-point numbers are imprecise, especially since they work in binary fractions (1/2, 1/4, 1/8, 1/16, 1/32, ...) instead of decimal fractions (1/10, 1/100, 1/1000, ...). Just define what you feel is "close enough" and use something like Math.abs(a-b) < 0.000001.

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

回答by hallidave

You can make the output of your program look more like you expect by using formatted output.

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

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

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

Obviously the underlying floating point arithmetic still works the same, but at least the output will be more readable.

显然,底层的浮点运算仍然相同,但至少输出将更具可读性。

For example, to round your results to two decimal places:

例如,要将结果四舍五入到小数点后两位:

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

回答by Jay

Floating point numbers use binary fractions and not decimal fractions. That is, you're used to decimal fractions made up of a tenths digit, a hundredths digit, a thousandths digit, etc. d1/10 + d2/100 + d3/1000 ... But floating point numbers are in binary, so they have a half digit, a quarter digit, an eighth digit, etc. d1/2 + d2/4 + d3/8 ...

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

Many decimal fractions cannot be expressed exactly in any finite number of binary digits. For example, 1/2 is no problem: in decimal it's .5, in binary it's .1. 3/4 is decimal .75, binary .11. But 1/10 is a clean .1 in decimal, but in binary it's .0001100110011... with the "0011" repeating forever. As the computer can store only a finite number of digits, at some point this has to get chopped off, so the answer is not precise. When we convert back to decimal on output, we get a strange-looking number.

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

As Jon Skeet says, if you need exact decimal fractions, use BigDecimal. If performance is an issue, you could roll your own decimal fractions. Like, if you know you always want exactly 3 decimal places and that the numbers will not be more than a million or so, you could simply use int's with an assumed 3 decimal places, making adjustments as necessary when you do arithmetic and writing an output format function to insert the decimal point in the right place. But 99% of the time performance isn't a big enough issue to be worth the trouble.

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

回答by Jay

On a philosophical note, I wonder: Most computer CPUs today have built-in support for integer arithmetic and floating-point arithmetic, but no support for decimal arithmetic. Why not? I haven't written an application in years where floats were useable because of this rounding problem. You certainly can't use them for money amounts: No one wants to print a price on a sales receipt of "$42.3200003". No accountant is going to accept "we might be off by a penny here and there because we're using binary fractions and had rounding errors".

在哲学上,我想知道:今天的大多数计算机 CPU 都内置了对整数算术和浮点算术的支持,但不支持十进制算术。为什么不?由于这个四舍五入问题,我已经好几年没有写过可以使用浮点数的应用程序了。您当然不能将它们用于金额:没有人愿意在“$42.3200003”的销售收据上打印价格。没有会计师会接受“我们可能会在这里和那里差一分钱,因为我们使用的是二进制分数并且有四舍五入错误”。

Floats are fine for measurements, like distance or temperature, where there's no such thing as an "exact answer" and you have to round off to the precision of your instruments at some point anyway. I suppose for people who are programming the computer in the chemistry lab, floats are used routinely. But for those of us in the business world, they're pretty much useless.

浮点数适用于距离或温度等测量,在这些测量中没有“确切答案”之类的东西,无论如何您都必须在某个时候四舍五入到仪器的精度。我想对于在化学实验室为计算机编程的人来说,通常会使用浮标。但是对于我们这些在商业世界中的人来说,它们几乎毫无用处。

Back in those ancient days when I programmed on mainframes, the IBM 360 family of CPUs had built-in support for packed decimal arithmetic. They stored strings where each byte held two decimal digits, i.e. the first four bits had values from 0 to 9 and ditto the second four bits, and the CPU had arithmetic functions to manipulate them. Why can't Intel do something like that? Then Java could add a "decimal" data type and we wouldn't need all the extra junk.

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

I'm not saying to abolish floats, of course. Just add decimals.

当然,我并不是说要取消浮动。只需添加小数。

Oh well, as great social movements go, I don't suppose this is one that is going to generate a lot of popular excitement or rioting in the streets.

哦,随着伟大的社会运动的发展,我认为这不会在街头引起很多流行的兴奋或骚乱。

回答by acauan

By the way, you can try to use this function to be sure (for not too much number of decimal digits) that your number will be reformatted to keep only the decimals you need.

顺便说一下,您可以尝试使用此功能来确保(不要有太多的十进制数字)您的号码将被重新格式化以仅保留您需要的小数。

http://pastebin.com/CACER0xK

http://pastebin.com/CACER0xK

nis you number with lots of decimals (e.g. Math.PI), numberOfDecimalsis the maximum number of decimals you need (e.g. 2 for 3.14 or 3 for 3.151).

n您是否有很多小数(例如Math.PI), numberOfDecimals是您需要的最大小数位数(例如 2 表示 3.14 或 3 表示 3.151)。

By theory, putting a negative value to numberOfDecmals, it will cut off the lower integer digits of the number too. E.g putting n=1588.22and numberOfDecimals=-2, the function will return 1500.0.

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

Let me know if it is something wrong.

如果有什么问题,请告诉我。

回答by don_q

You can write some code to compute Epsilon on your machine. I believe std:: C++ defines it, its defined in other ways depending on what you are using.

您可以编写一些代码来在您的机器上计算 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;
}

The only time I worried about Epsilon was when I was comparing signals for thresholding. Even then I'm not sure I really needed to worry about it, in my experience if you are concerned about Epsilon you mayhave some other consideration to deal with first.

我唯一一次担心 Epsilon 是在比较阈值信号时。即便如此,我也不确定我是否真的需要担心它,根据我的经验,如果您担心 Epsilon,您可能首先需要考虑其他一些事项。