2

众所周知,当需要任意精度时,不使用 java 浮点原始值。Goetz 在他的优秀文章中解释了这个问题。

想象一下,我们需要在某个项目中实现任意精度,而我们没有BigDecimal类(因为它在 API 中不可用,例如:JavaME),也没有时间开发自定义实现。如果我们事先知道只需要相对较小的精度(2 到 4 位小数),是否可以使用 float 和 double 类型以及舍入函数实现 100% 可靠的紧急解决方法?如果是这样,可以使用 API 中的哪个函数?如果此功能不可用,但您仍然认为它可以解决问题,那么实现它会有多复杂?

4

5 回答 5

3

不,这是不可能的,因为某些值无法使用浮点运算来表示。0.1是最简单的例子。

于 2011-12-14T15:09:36.563 回答
1

定义“100% 可靠”。IEEE 754 浮点值(几乎在所有语言中都使用;这绝不是 Java 特定的问题)实际上可以非常可靠地完成它们被设计用来完成的事情。他们只是并不总是按照人们期望(十进制)小数的方式行事。

如果您想要解决浮点数问题的东西,您首先必须准确指定问题是什么以及这种新格式在这些情况下应该如何表现。

于 2011-12-14T15:13:15.880 回答
1

不。

0.15 的一半是多少,四舍五入到最接近的百分之一?

在精确的算术中,0.15/2 = 0.075,四舍五入0.08(假设向上舍入或四舍五入规则)。

在 IEEE 754 算术中,0.15/2 = 0.07499999999999999722444243843710864894092082977294921875,四舍五入为 0.07。

于 2011-12-16T00:02:41.907 回答
0

在这种情况下,为什么还要考虑浮点运算呢?只需使用Integer乘以您的精度因子。

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

一个小的精度值,例如467.8142将由标准操作表示4,678,142和计算。Integer不损失精度。

但是,话又说回来,就像@TomaszNurkiewicz 提到的那样,这正是BigDecimal它的作用。所以你的问题真的没有任何意义。浮点运算非常好,甚至可以处理您提到的情况,前提是程序员知道她在做什么。

于 2011-12-14T15:05:54.910 回答
0

我认为不可以,除非您可以完全定义和控制所有数学运算,以至于排除所有舍入。

也许,另一种选择是使用 Rationals。这是我作为实验敲击的一个。我怀疑它是否是最优的,甚至是有效的,但这肯定是一种可能性。

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);
  }
}

我在java2找到了 lcm 和 gcd 代码。他们可能会得到改进。

于 2011-12-14T16:16:46.507 回答