2011-12-14 54 views
2

已知当需要任意精度时,不会使用java浮点基元值。戈茨在他的excellent article中解释了这个问题。四舍五入可以避免Java浮点危害吗?

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

+2

你还没有说明你正试图解决的问题,几乎足够的细节。 – antlersoft 2011-12-14 15:07:36

+1

假设你想要2位数的精度。你会期望什么:0.01/10 * 10。去做?返回0还是0.01?如果后面的 - 任何有限的位数都不足以实现它。 – amit 2011-12-14 15:08:52

+0

好的,更新问题。 – 2011-12-14 15:29:25

回答

3

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

0

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

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

小的精确度值,如467.8142将由4,678,142来表示,并使用标准Integer操作计算。没有精度损失。

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

1

定义“100%可靠”。 IEEE 754浮点值(几乎在所有语言中都使用;这决不是Java特有的问题)实际上做的是他们设计的非常可靠的事情。他们并不总是像人们期望的那样(十进制)分数表现行为。

如果你想要解决浮点数问题,你首先必须明确指出问题所在,以及这种新格式在这些情况下应该如何表现。

0

我认为不,除非你能完全定义和控制所有的数学,以至于你排除了所有四舍五入。

另一种方法可能是使用Rational。这是我作为一个实验敲定的一个。我怀疑它是否是最佳的,甚至是有效的,但它肯定是一种可能性。

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

1

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

在精确算术中,0.15/2 = 0.075,其将上调至0.08(假设采用半舍去或半舍弃规则)。

在IEEE 754算术中,0.15/2 = 0.07499999999999999722444243843710864894092082977294921875,其将下降降至0.07。