如何克服Java中的不准确性 [英] How to overcome inaccuracy in Java

查看:86
本文介绍了如何克服Java中的不准确性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我执行以下程序时,我了解了准确性问题:

public static void main(String args[])
{
    double table[][] = new double[5][4];
    int i, j;
    for(i = 0, j = 0; i <= 90; i+= 15)
    {
        if(i == 15 || i == 75)
            continue;
        table[j][0] = i;
        double theta = StrictMath.toRadians((double)i);
        table[j][1] = StrictMath.sin(theta);
        table[j][2] = StrictMath.cos(theta);
        table[j++][3] = StrictMath.tan(theta);
    }
    System.out.println("angle#sin#cos#tan");
    for(i = 0; i < table.length; i++){
        for(j = 0; j < table[i].length; j++)
            System.out.print(table[i][j] + "\t");
        System.out.println();
    }
}

输出为:

angle#sin#cos#tan
0.0 0.0 1.0 0.0 
30.0    0.49999999999999994 0.8660254037844387  0.5773502691896257  
45.0    0.7071067811865475  0.7071067811865476  0.9999999999999999  
60.0    0.8660254037844386  0.5000000000000001  1.7320508075688767  
90.0    1.0 6.123233995736766E-17   1.633123935319537E16    

(请原谅无组织的输出). 我注意到了几件事:

  • sin 30,即0.5存储为0.49999999999999994.
  • tan 45即1.0存储为0.9999999999999999.
  • tan 90,即infinityundefined存储为1.633123935319537E16(这是一个很大的数字).

自然地,我很困惑地看到输出(即使在解密输出之后).

所以我读了这篇帖子,最佳答案告诉我:

这些准确性问题是由于解决方案

对于浮点数,您必须采取zen *方法:不要消除错误,而要学会忍受它.

在实践中,这通常意味着要执行以下操作:

  • 显示数字时,使用String.format指定要显示的精度(它将为您进行适当的舍入)
  • 与期望值进行比较时,请勿寻找相等性(==).相反,请寻找一个足够小的增量:Math.abs(myValue - expectedValue) <= someSmallError

编辑:对于无穷大,适用相同的原理,但要进行一些调整:您必须选择一些数字以足够大"以将其视为无穷大.再次是因为您必须学会忍受而不是解决不精确的价值观.在诸如tan(90度)之类的情况下,双精度不能以无限的精度存储π/2,因此您的输入值非常接近(但不完全是90度),因此结果是大,但不是无限.您可能会问:当您将最接近的双精度数传递给π/2时,为什么它们不返回Double.POSITIVE_INFINITY呢?"但这可能会导致模棱两可:如果您真的想要那个数字的正切而不是90度,该怎么办?或者,如果(由于先前的浮点错误)您所拥有的东西距π/2的距离稍远而不是最接近的可能值,但是对于您的需求仍然为π/2? JDK不会为您做出任意决定,而是会以您的票面价值来对待您接近但并非完全是π/2的数字,从而为您带来一个巨大但并非无限的结果.

对于某些操作,尤其是与货币相关的操作,可以使用BigDecimal消除浮点错误:您可以真正表示0.1之类的值(而不是真正接近0.1的值,这是最好的浮点数)或加倍即可).但这要慢得多,并且对于sin/cos之类的东西(至少对于内置库而言)无济于事.

*这实际上可能不是禅宗,但从口语上来说

I came to know about the accuracy issues when I executed the following following program:

public static void main(String args[])
{
    double table[][] = new double[5][4];
    int i, j;
    for(i = 0, j = 0; i <= 90; i+= 15)
    {
        if(i == 15 || i == 75)
            continue;
        table[j][0] = i;
        double theta = StrictMath.toRadians((double)i);
        table[j][1] = StrictMath.sin(theta);
        table[j][2] = StrictMath.cos(theta);
        table[j++][3] = StrictMath.tan(theta);
    }
    System.out.println("angle#sin#cos#tan");
    for(i = 0; i < table.length; i++){
        for(j = 0; j < table[i].length; j++)
            System.out.print(table[i][j] + "\t");
        System.out.println();
    }
}

And the output is:

angle#sin#cos#tan
0.0 0.0 1.0 0.0 
30.0    0.49999999999999994 0.8660254037844387  0.5773502691896257  
45.0    0.7071067811865475  0.7071067811865476  0.9999999999999999  
60.0    0.8660254037844386  0.5000000000000001  1.7320508075688767  
90.0    1.0 6.123233995736766E-17   1.633123935319537E16    

(Please forgive the unorganised output). I've noted several things:

  • sin 30 i.e. 0.5 is stored as 0.49999999999999994.
  • tan 45 i.e. 1.0 is stored as 0.9999999999999999.
  • tan 90 i.e. infinity or undefined is stored as 1.633123935319537E16 (which is a very big number).

Naturally, I was quite confused to see the output (even after deciphering the output).

So I've read this post, and the best answer tells me:

These accuracy problems are due to the internal representation of floating > point numbers and there's not much you can do to avoid it.

By the way, printing these values at run-time often still leads to the correct results, at >least using modern C++ compilers. For most operations, this isn't much of an issue.

answered Oct 7 '08 at 7:42

Konrad Rudolph

So, my question is:

Is there any way to prevent such inaccurate results (in Java)?

Should I round-off the results? In that case, how would I store infinity i.e. Double.POSITIVE_INFINITY?

解决方案

You have to take a bit of a zen* approach to floating-point numbers: rather than eliminating the error, learn to live with it.

In practice this usually means doing things like:

  • when displaying the number, use String.format to specify the amount of precision to display (it'll do the appropriate rounding for you)
  • when comparing against an expected value, don't look for equality (==). Instead, look for a small-enough delta: Math.abs(myValue - expectedValue) <= someSmallError

EDIT: For infinity, the same principle applies, but with a tweak: you have to pick some number to be "large enough" to treat as infinity. This is again because you have to learn to live with, rather than solve, imprecise values. In the case of something like tan(90 degrees), a double can't store π/2 with infinite precision, so your input is something very close to, but not exactly, 90 degrees -- and thus, the result is something very big, but not quite infinity. You may ask "why don't they just return Double.POSITIVE_INFINITY when you pass in the closest double to π/2," but that could lead to ambiguity: what if you really wanted the tan of that number, and not 90 degrees? Or, what if (due to previous floating-point error) you had something that was slightly farther from π/2 than the closest possible value, but for your needs it's still π/2? Rather than make arbitrary decisions for you, the JDK treats your close-to-but-not-exactly π/2 number at face value, and thus gives you a big-but-not-infinity result.

For some operations, especially those relating to money, you can use BigDecimal to eliminate floating-point errors: you can really represent values like 0.1 (instead of a value really really close to 0.1, which is the best a float or double can do). But this is much slower, and doesn't help you for things like sin/cos (at least with the built-in libraries).

* this probably isn't actually zen, but in the colloquial sense

这篇关于如何克服Java中的不准确性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆