简体   繁体   English

舍入可以避免Java浮点运算的危害吗?

[英]Can Java floating point hazards be avoided with rounding?

It is known that java floating point primitive values are not to be used when arbitrary precision is required. 众所周知,当需要任意精度时,将不使用Java浮点基元值。 Goetz explained the problem in his excellent article . 格茨在他的精彩文章中解释了这个问题。

Imagine we need to achieve arbitrary precision in a certain project and we don't have a BigDecimal class (because it is not available in the API, eg: JavaME) nor have time to develop a custom implementation. 想象一下,我们需要在某个项目中实现任意精度,并且我们没有BigDecimal类(因为该类在API中不可用,例如:JavaME),也没有时间开发自定义实现。 Provided we know in advance that only a relatively small precision is required (2 to 4 decimals), would it be possible to implement a 100% reliable emergency workaround using float and double types and a rounding function? 如果我们事先知道只需要相对较小的精度(2到4个小数位),是否可以使用浮点和双精度类型以及舍入功能来实现100%可靠的紧急解决方法? And if so, which function in the API could be used? 如果可以,可以使用API​​中的哪个函数? In case this function were not available, but still you think it could address the problem, how complex would it be to implement it? 如果此功能不可用,但您仍然认为它可以解决问题,实现该功能有多复杂?

No, it wouldn't be possible because some values can't be represented using floating point arithmetic. 不,这是不可能的,因为某些值不能使用浮点算术表示。 0.1 is the simplest example. 0.1是最简单的示例。

Define "100% reliable". 定义“ 100%可靠”。 IEEE 754 floating point values (which are used in nearly all languages; this is by no means a Java-specific problem) actually do the things they are designed to do very reliably. 实际上,IEEE 754浮点值(几乎在所有语言中都使用;这绝不是Java特定的问题)实际上可以非常可靠地完成它们设计的工作。 They just don't always behave the way people expect (decimal) fractional numbers to behave. 它们只是不总是按照人们期望的(小数)分数形式表现。

If you want something that solves a problem you have with floating-point numbers you first have to specify exactly what the problem is and how this new format should behave in those instances. 如果您想使用浮点数来解决问题,则首先必须确切说明问题所在以及这种新格式在这些情况下的行为。

No. 没有。

What's half of 0.15, rounded to the nearest hundredth? 0.15的一半,四舍五入到最接近的百分之一?

In exact arithmetic, 0.15/2 = 0.075, which rounds up to 0.08 (assuming either round-half-up or round-half-even rules). 在精确算术中,0.15 / 2 = 0.075,该数值向上舍入为0.08(假设舍入为一半或舍入为一半-偶数规则)。

In IEEE 754 arithmetic, 0.15/2 = 0.07499999999999999722444243843710864894092082977294921875, which rounds down to 0.07. 在IEEE 754算术中,0.15 / 2 = 0.07499999999999999722444243843710864894092082977294921875( 向下取整为0.07)。

In this case, why bother with floating-point arithmetic at all? 在这种情况下,为什么还要烦恼浮点运算呢? Just use an Integer multiplied by your precision factor. 只需使用Integer乘以您的精度系数即可。

final int PRECISION = 4;
Integer yourFloatingValue = Integer.valueOf("467.8142") * Math.pow(10, PRECISION);

A small precision value, such as 467.8142 will be represented by 4,678,142 and calculated using standard Integer operations. 一个小的精确度值,如467.8142将被表示为4,678,142 ,并使用标准计算Integer操作。 No loss of precision. 不损失精度。

But, then again, like @TomaszNurkiewicz mentioned, this is exactly what BigDecimal does. 但是,再说一次,就像@TomaszNurkiewicz提到的,这正是BigDecimal所做的。 So your question doesn't really make any sense. 因此,您的问题实际上没有任何意义。 Floating point arithmetic is perfectly fine, and can handle even the cases you mentioned, granted the programmer knows what she's doing. 浮点算术非常好,并且只要程序员知道自己在做什么,就可以处理您提到的情况。

I think no, unless you can fully define and control all the maths to such an extent that you exclude all rounding. 我认为不会,除非您可以完全定义和控制所有数学,以至于排除所有舍入的程度。

An alternative could be, perhaps, using Rationals. 另一种可能是使用Rational。 Here's one I knocked up just as an experiment. 这是我作为实验而提出的。 I doubt if it is optimal, or even efficient, but it is certainly a possibility. 我怀疑它是否是最优的,甚至是有效的,但肯定是可能的。

class Rational {

  private int n; // Numerator.
  private int d; // Denominator.

  Rational(int n, int d) {
    int gcd = gcd(n, d);
    this.n = n / gcd;
    this.d = d / gcd;
  }

  Rational add(Rational r) {
    int lcm = lcm(d, r.d);
    return new Rational((n * lcm) / d + (r.n * lcm) / r.d, lcm);
  }

  Rational sub(Rational r) {
    int lcm = lcm(d, r.d);
    return new Rational((n * lcm) / d - (r.n * lcm) / r.d, lcm);
  }

  Rational mul(Rational r) {
    return new Rational(n * r.n, d * r.d);
  }

  Rational div(Rational r) {
    return new Rational(n * r.d, d * r.n);
  }

  @Override
  public String toString() {
    return n + "/" + d;
  }

  /**
   * Returns the least common multiple between two integer values.
   * 
   * @param a the first integer value.
   * @param b the second integer value.
   * @return the least common multiple between a and b.
   * @throws ArithmeticException if the lcm is too large to store as an int
   * @since 1.1
   */
  public static int lcm(int a, int b) {
    return Math.abs(mulAndCheck(a / gcd(a, b), b));
  }

  /**
   * Multiply two integers, checking for overflow.
   * 
   * @param x a factor
   * @param y a factor
   * @return the product <code>x*y</code>
   * @throws ArithmeticException if the result can not be represented as an
   *         int
   * @since 1.1
   */
  public static int mulAndCheck(int x, int y) {
    long m = ((long) x) * ((long) y);
    if (m < Integer.MIN_VALUE || m > Integer.MAX_VALUE) {
      throw new ArithmeticException("overflow: mul");
    }
    return (int) m;
  }

  /**
   * <p>
   * Gets the greatest common divisor of the absolute value of two numbers,
   * using the "binary gcd" method which avoids division and modulo
   * operations. See Knuth 4.5.2 algorithm B. This algorithm is due to Josef
   * Stein (1961).
   * </p>
   * 
   * @param u a non-zero number
   * @param v a non-zero number
   * @return the greatest common divisor, never zero
   * @since 1.1
   */
  public static int gcd(int u, int v) {
    if (u * v == 0) {
      return (Math.abs(u) + Math.abs(v));
    }
    // keep u and v negative, as negative integers range down to
    // -2^31, while positive numbers can only be as large as 2^31-1
    // (i.e. we can't necessarily negate a negative number without
    // overflow)
      /* assert u!=0 && v!=0; */
    if (u > 0) {
      u = -u;
    } // make u negative
    if (v > 0) {
      v = -v;
    } // make v negative
    // B1. [Find power of 2]
    int k = 0;
    while ((u & 1) == 0 && (v & 1) == 0 && k < 31) { // while u and v are
      // both even...
      u /= 2;
      v /= 2;
      k++; // cast out twos.
    }
    if (k == 31) {
      throw new ArithmeticException("overflow: gcd is 2^31");
    }
    // B2. Initialize: u and v have been divided by 2^k and at least
    // one is odd.
    int t = ((u & 1) == 1) ? v : -(u / 2)/* B3 */;
    // t negative: u was odd, v may be even (t replaces v)
    // t positive: u was even, v is odd (t replaces u)
    do {
      /* assert u<0 && v<0; */
      // B4/B3: cast out twos from t.
      while ((t & 1) == 0) { // while t is even..
        t /= 2; // cast out twos
      }
      // B5 [reset max(u,v)]
      if (t > 0) {
        u = -t;
      } else {
        v = t;
      }
      // B6/B3. at this point both u and v should be odd.
      t = (v - u) / 2;
      // |u| larger: t positive (replace u)
      // |v| larger: t negative (replace v)
    } while (t != 0);
    return -u * (1 << k); // gcd is u*2^k
  }

  static void test() {
    Rational r13 = new Rational(1, 3);
    Rational r29 = new Rational(2, 9);
    Rational r39 = new Rational(3, 9);
    Rational r12 = new Rational(1, 2);
    Rational r59 = r13.add(r29);
    Rational r19 = r29.mul(r12);
    Rational r23 = r39.div(r12);
    Rational r16 = r12.sub(r13);
    System.out.println("1/3 = " + r13);
    System.out.println("2/9 = " + r29);
    System.out.println("1/3 = " + r39);
    System.out.println("5/9 = " + r59);
    System.out.println("1/9 = " + r19);
    System.out.println("2/3 = " + r23);
    System.out.println("1/6 = " + r16);
  }
}

I found the lcm and gcd code at java2 . 我在java2上找到了lcm和gcd代码。 They can probably be improved. 它们可能可以改进。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM