【问题标题】:How to overcome inaccuracy in Java如何克服 Java 中的不准确性
【发布时间】:2014-04-06 23:58:41
【问题描述】:

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

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(这是一个非常大的数字)。

当然,看到输出我很困惑(即使在破译输出之后)。

所以我读过this 的帖子,最好的答案告诉我:

这些accuracy problems 是由浮点数的internal representation 引起的,您无能为力。

顺便说一句,在运行时打印这些值通常仍然会产生正确的结果,至少使用现代 C++ 编译器。对于大多数操作来说,这不是什么大问题。

2008 年 10 月 7 日 7:42 回答

康拉德·鲁道夫

所以,我的问题是:

有没有办法防止这种不准确的结果(在 Java 中)?

我应该对结果进行四舍五入吗?在这种情况下,我将如何存储infinity,即Double.POSITIVE_INFINITY

【问题讨论】:

  • theta 不完全是半 pi,这就是为什么输出中没有 Infinity - NaN/InF “存储为”1.633123935319537E16,它仍然是一个有限的数字。
  • 请参阅tan(PI/2),了解为什么您没有获得 Infinity。但是,我不知道如何将棕褐色“接受接近 PI/2 的值”作为 PI/2,并且所有环境似乎都会产生 a 值。
  • @user2864740 谢谢,这告诉了我为什么...但是你能告诉我如何解决它吗?
  • 我能想到的唯一直接解决方案是在调用 tan 之前对输入进行特殊处理。如果您喜欢它“足够接近 PI/2”,只需返回 Inf/-Inf(或任何您期望的)而不是调用 tan。如果你需要做“三角正确”数学,可能有一个库可以处理精确的数学概念。 (我认为“符号数学”可能是一个有用的搜索词)。
  • @user2864740 谢谢,我会注意的。

标签: java floating-accuracy


【解决方案1】:

您必须使用BigDecimal 而不是double。不幸的是,StrictMath 不支持BigDecimal,因此您将不得不使用另一个库,或者您自己的sin/cos/tan 实现。

【讨论】:

  • 也许进一步阐述?
【解决方案2】:

这是在任何语言中使用浮点数所固有的。实际上,它是使用任何具有固定最大精度的表示所固有的。

有几种解决方案。一种是使用扩展精度数学包——BigDecimal 通常建议用于 Java。 BigDecimal 可以处理更多位的精度,而且——因为它是十进制表示而不是 2 的补码表示——倾向于以对习惯于以 10 为基数工作的人来说不那么令人惊讶的方式四舍五入。 (这并不一定会让它们更正确,请注意。二进制不能准确表示 1/3,但十进制也不能。)

还有扩展精度 2 的补码浮点表示。 Java 直接支持浮点和双精度(硬件通常也支持),但也可以编写支持更多位数精度的版本。

当然,任何扩展精度包都会减慢您的计算速度。所以除非你真的需要它们,否则你不应该求助于它们。

另一个可能使用定点二进制而不是浮点。例如,大多数财务计算的标准解决方案是简单地以最小的货币单位——美分,在美国——以整数计算,转换为仅用于 I 的显示格式(例如美元和美分)。 /O。这也是 Java 中用于时间的方法 - 内部时钟报告整数毫秒(或纳秒,如果您使用 nanotime 调用),这对于大多数实用程序来说提供了足够的精度和足够的值范围目的。同样,这意味着舍入往往会以符合人类期望的方式发生……再说一次,这不是关于准确性,而是关于不让用户感到惊讶。而这些表示,因为它们处理为整数或长整数,允许快速计算——实际上比浮点更快。

还有其他解决方案涉及以有理数或其他变体进行计算,以尝试在计算成本和精度之间进行折衷。

但我还要问...您真的需要比 float 提供的精度更高吗?我知道舍入令人惊讶,但在许多情况下,让它发生是完全可以接受的,当您向用户显示结果时,可能会舍入到不那么令人惊讶的小数位数。在许多情况下,浮点数或双精度数对于实际使用来说都很好。这就是硬件支持它们的原因,这就是它们在语言中的原因。

【讨论】:

  • 这很有启发性... :-) 嗯,你是对的:我不需要在这里需要精确,我只需要使其可以接受 对人类。但是infinity的问题仍然存在。
  • 浮点有一个保留值,意思是infinity,还有一个保留值,意思是“不是一个有意义的可计算数字”(NAN)。其他一些可能;您必须查看他们的文档。当然,整数和缩放整数以及相关的整数不会。但通常现实世界的问题实际上并不需要 INF 或 NAN,除非是错误情况......除非你正在编写一个符号数学包,在这种情况下,你根本不会使用数字进行计算。跨度>
【解决方案3】:

您必须对浮点数采取一些禅宗*的方法:与其消除错误,不如学会忍受它。

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

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

编辑:对于无穷大,同样的原则也适用,但需要稍作调整:您必须选择一些“足够大”的数字以将其视为无穷大。这又是因为你必须学会​​接受而不是解决不精确的价值观。在 tan(90 度) 之类的情况下,双精度无法以无限精度存储 π/2,因此您的输入非常接近但不完全是 90 度——因此,结果非常大,但不是无穷大。你可能会问“为什么当你传入最接近 π/2 的双精度时,他们不直接返回 Double.POSITIVE_INFINITY”,但这可能会导致歧义:如果你真的想要那个数字的棕褐色而不是 90 度怎么办?或者,如果(由于之前的浮点错误)您的某个值与 π/2 的距离稍微远于最接近的可能值,但根据您的需要,它仍然是 π/2? JDK 不会为您做出任意决定,而是按面值对待您接近但不完全是 π/2 的数字,从而为您提供一个大但不是无穷大的结果。

对于某些操作,尤其是与金钱有关的操作,您可以使用BigDecimal 来消除浮点错误:您可以真正表示像 0.1 这样的值(而不是非常接近 0.1 的值,这是最好的浮点数或双可以做)。但这要慢得多,并且对诸如 sin/cos 之类的事情没有帮助(至少对于内置库而言)。

* 这可能不是真正的禅,而是通俗意义上的

【讨论】:

  • 嗯,这解决了 _0.5 存储为 `0.49999999999999994` 的问题。我该怎么办infinity
  • @ambigram_maker 同样,您必须选择一些“足够大”的数字,在该数字上您说“好的,足够接近无穷大”。双打实际上 可以 存储无穷大,但你不会得到那个结果,因为 StrictMath.toRadians(90) 返回一个非常接近但不完全是 pi/2 的数字(你不能用有限的数量来表示位)。
  • 那么,如果我将表示系统更改为弧度而不是度数会怎样?会有帮助吗?
  • 不,因为没有办法用浮点数或双精度数精确表示 π/2。如果您有一个采用度数(而不是弧度)的库,您可以这样做——因为它可以精确地表示 90.0。但是 afaik,StrictMath 不会那样做。
  • 我猜你是对的......我必须忍受它。 :-) 所以,我猜在某些 特定 情况下,我们必须返回 expected 值,而不是 看似错误但非常接近 计算值
猜你喜欢
  • 2010-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-24
  • 2010-09-28
  • 2010-11-14
  • 1970-01-01
相关资源
最近更新 更多