سؤال

It is known that java floating point primitive values are not to be used when arbitrary precision is required. 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, e.g.: JavaME) nor have time to develop a custom implementation. 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? And if so, which function in the API could be used? 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.

نصائح أخرى

Define "100% reliable". 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. 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?

In exact arithmetic, 0.15/2 = 0.075, which rounds up to 0.08 (assuming either round-half-up or round-half-even rules).

In IEEE 754 arithmetic, 0.15/2 = 0.07499999999999999722444243843710864894092082977294921875, which rounds down to 0.07.

In this case, why bother with floating-point arithmetic at all? Just use an Integer multiplied by your precision factor.

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. No loss of precision.

But, then again, like @TomaszNurkiewicz mentioned, this is exactly what BigDecimal does. 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. 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. They can probably be improved.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top