Java 浮点危险可以通过舍入来避免吗?

发布于 2024-12-21 07:58:18 字数 371 浏览 2 评论 0原文

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

Hello爱情风 2024-12-28 07:58:18

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

No, it wouldn't be possible because some values can't be represented using floating point arithmetic. 0.1 is the simplest example.

清风疏影 2024-12-28 07:58:18

定义“100% 可靠”。 IEEE 754 浮点值(几乎在所有语言中使用;这绝不是 Java 特有的问题)实际上非常可靠地完成了它们设计的任务。它们只是并不总是像人们期望的(十进制)小数那样表现。

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

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.

若言繁花未落 2024-12-28 07:58:18

不。

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

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

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

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.

山人契 2024-12-28 07:58:18

在这种情况下,为什么还要费心浮点运算呢?只需使用Integer乘以您的精度系数即可。

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

小精度值(例如 467.8142)将由 4,678,142 表示,并使用标准Integer 运算进行计算。没有精度损失。

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

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.

一江春梦 2024-12-28 07:58:18

我认为不会,除非您可以完全定义和控制所有数学,达到排除所有舍入的程度。

也许,另一种选择是使用有理数。这是我作为实验而敲出的一个。我怀疑它是否是最佳的,甚至是有效的,但它肯定是有可能的。

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 代码。它们可能还可以改进。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文