【问题标题】:Possible error while dividing doubles除法双打时可能出现的错误
【发布时间】:2013-12-22 09:15:08
【问题描述】:

我想检查两个分数是否相等。我认为我写的方法不正确,因为可能会失去准确性。我是对还是错?

public boolean equals(Rational r) {
    return (double) numerator / denominator == (double) r.numerator / r.denominator;
}

【问题讨论】:

  • 分子和分母是整数吗?
  • 试试就知道了。

标签: java double divide


【解决方案1】:

我认为这样会更好:

public boolean equals(Rational r) {
  return numerator * r.denominator == r.numerator * denominator;
}

【讨论】:

  • 谢谢,但是我应该把这两个分开还是只找到共同的分子然后比较它们?
  • @Yoda 没有。想想这个例子:3/5 == 6/10 ?那么 3*10 == 5*6?是的,所以它们是平等的。
  • 不错的方法,我几乎没有发现你正在与彼此的除数相乘。
  • 这种方法的问题是你可以溢出底层整数类型的数值范围,假设有理类型是基于整数的。如果它基于双精度数(这看起来很奇怪),那么如果乘积超过双精度数的有效位精度(53 位),您仍然可能会丢失精度。
  • 在这种情况下,使用 BigIntegers。好消息是它会给出正确的结果,使用除法的解决方案不会出现这种情况,即使使用 BigDecimal。
【解决方案2】:

你是对的,这个检查在很多情况下都会失败。

使其工作的唯一方法是将结果转换为 BigDecimal 或 String,然后进行比较或定义允许的错误 - 即:

public boolean equals(Rational r) {
    double c1 = ((double) numerator) / denominator;
    double c2 = ((double) r.numerator) / r.denominator;
    return c2 > (c1-allowedError) && c2 < (c1+allowedError);
}

【讨论】:

  • 一个常见的模式是写Math.abs(c1 - c2) &lt;= allowedError
  • 是的,我差点把它写下来,但我觉得这样更清楚。
  • 我不确定您是否可以定义一个在整个范围内都有效的allowedError
【解决方案3】:

你可以这样做

public boolean equals(Rational r) {
    return numerator * r.denominator == r.numerator * denominator;
}

【讨论】:

  • -1 3 / 4 = 05 / 9 = 03 / 4 != 5 / 9
  • @Peter Lawrey 我只想提一下,它应该在他的逻辑除法之后进行。我已正确更新我的答案
  • 谢谢彼得,我确实发布了最初的答案,假设这是一个演员问题。我想总是纠正我提出的问题! ;)
【解决方案4】:

如果你的有理数已经减少了分子和分母之外的所有公因数,那么你可以这样做:

return (numerator == r.numerator) && (denominator == r.denominator);

如果你的理性人没有做过分解,那么我建议你实现一个 GCD 函数来让你这样做。然后将缩减后的numeratordenominator 与缩减后的r.numerator 和r.denominator 进行比较。

例如,假设int 是您的有理分子和分母的基础类型:

int gcd_lhs = gcd( numerator,   denominator   );
int gcd_rhs = gcd( r.numerator, r.denominator );

int lhs_red_num = numerator   / gcd_lhs;
int lhs_red_den = denominator / gcd_lhs;
int rhs_red_num = r.numerator   / gcd_rhs;
int rhs_red_den = r.denominator / gcd_rhs;

return (lhs_red_num == rhs_red_num) && (lhs_red_den == rhs_red_den);

这种方法的优点是在所有情况下都准确,并且它永远不会超过您用于分子和分母的基础类型的精度。

依赖乘法的方法,或将某些值从 GCD 以外的值中除以可能会溢出基础类型中可用的精度。 (假设您首先转换为double,则除法可以通过生成重复的二进制分数来“溢出”可用精度,该二进制分数必须四舍五入以适合可用位。除以GCD始终是精确的,并保持为整数除法。)

可以通过使用 BigInteger 类型并应用交叉乘法方法来缓解这种情况。这有其自身的成本。如果您已经在使用 BigInteger 来存储分子和分母,那么这可能是最简单的方法。

【讨论】:

    【解决方案5】:

    一种方法如下:

    boolean eq = Math.abs( (double) numerator / denominator - 
                           (double) r.numerator / r.denominator ) < EPS;
    

    在哪里

    EPS 是一些小的正常数,例如EPS=0.00001.

    如果 eq 变量设置为 true,则表示您的数字“相等”
    除了您想在此处忽略的可能的精度错误。

    当然在 Java 操作中使用浮点
    数字不像许多其他语言那样 100% 精确。

    或者,正如其他人在这里建议的那样,
    你可能决定不为此使用双打
    特定的任务 / 但你不能总是避免它们;) /

    【讨论】:

      【解决方案6】:

      将 == 与双精度一起使用可能很危险,因为二进制浮点系统中的某些分数(包括可以用十进制表示的部分)can't be expressed

      你可以重新排列你的方程,这样你就不需要双精度数了(假设你的分子和分母是整数)

      r.denominator*numerator==denominator*r.numerator
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-07
        • 1970-01-01
        • 2020-01-18
        相关资源
        最近更新 更多