2011-12-14 1 views
2

임의의 정밀도가 필요한 경우 Java 부동 소수점 기본 값을 사용하지 않는 것으로 알려져 있습니다. Goetz는 그의 excellent article에서 문제를 설명했습니다.반올림으로 Java 부동 소수점 위험을 피할 수 있습니까?

특정 프로젝트에서 임의의 정밀도를 달성해야한다고 가정하면 (예 : JavaME와 같이 API에서 사용할 수 없기 때문에) BigDecimal 클래스가 없으며 맞춤 구현을 개발할 시간이 없습니다. 사전에 비교적 작은 정밀도 (2 ~ 4 소수)가 필요하다는 것을 알고 있다면 float 및 double 유형과 반올림 기능을 사용하여 100 % 신뢰할 수있는 응급 해결책을 구현할 수 있습니까? 그렇다면 API에서 어떤 기능을 사용할 수 있습니까? 이 기능을 사용할 수 없지만 여전히 문제를 해결할 수 있다고 생각하면 구현하는 것이 얼마나 복잡할까요?

+2

문제를 충분히 자세히 설명하지 않았습니까? – antlersoft

+1

은 2 자리 정밀도를 원한다고 가정합니다. 0.01/10에서 기대할 것입니다. * 10. 할 것? 0 또는 0.01을 반환합니까? 나중에 - 유한 비트 수가 충분하지 않은 경우이를 달성 할 수 있습니다. – amit

+0

질문을 업데이트합니다. –

답변

3

아니요, 일부 값은 부동 소수점 산술을 사용하여 표현할 수 없으므로 불가능합니다. 0.1이 가장 간단한 예입니다.

+0

그러나 OP가 2 자리 숫자 만 있으면 '[0.095,0.105]'범위의 각 숫자를 '0.1'로 정의 할 수 있습니다. 분명히, 당신은이 방법을 계산할 때 정확성을 잃어 버립니다 ... 비트 수를 제한하면 피할 수 없습니다. 숫자를 나타 내기 위해 가능할 수도 있습니다. – amit

+0

거짓. 부동 소수점 연산을 전혀 사용하지 않으면 (예 : 내 대답) '0.1'을 완벽하게 나타낼 수 있습니다. –

+1

@YuvalAdam : OK,하지만 OP는 "* 100 % 신뢰할 수있는 응급 해결 방법 **을 구현하기 위해 ** float 및 double 유형 ** 및 반올림 기능 *"을 사용합니다. 그래서 downvote 이해가 안돼 ... 귀하의 솔루션은 꽤 좋은, 사실 이것이 어떻게'BigDecimal' 작품 :-)입니다. –

0

이 경우 왜 부동 소수점 연산을 사용합니까? 정확도를 곱한 Integer을 사용하면됩니다.

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

작은 밀도 값은 같은 467.81424,678,142로 나타내는 표준 Integer 연산을 사용하여 계산한다. 정밀도의 손실이 없습니다.

@TomaszNurkiewicz가 언급했듯이, 이것은 정확히 BigDecimal이하는 것입니다. 그래서 당신의 질문은 실제로 의미가 없습니다. 부동 소수점 산술은 완벽하게 훌륭하며 프로그래머가 자신이하는 일을 안다는 것을 인정하면 언급 한 경우조차도 처리 할 수 ​​있습니다.

+3

부동 소수점을 사용하지 않고 어떻게 곱셈/나눗셈을 할 것입니까? – DaveRlz

+0

+1, 이것은 본질적으로 "fixnum"입니다. 제공된 예에서는 실제로 손실이 없지만 일반적으로 정밀도의 손실은 없다고 논쟁합니다. 그러나 비 컴퓨터 과학자가 알다. – Arafangion

+0

@DaveRlz : 정수 곱셈과 나눗셈은 여전히 ​​작동합니다. – Arafangion

1

"100 % 신뢰성"을 정의하십시오. 거의 모든 언어에서 사용되는 IEEE 754 부동 소수점 값 (이는 Java 관련 문제가 결코 아닙니다)은 실제로 신뢰성있게 설계된 작업을 수행합니다. 그들은 사람들이 (십진수) 소수점 수를 기대하는 방식으로 항상 행동하는 것은 아닙니다.

부동 소수점과 관련된 문제를 해결하는 것이 필요한 경우 먼저 문제가 무엇이고이 새로운 형식이 해당 인스턴스에서 어떻게 작동해야하는지 정확하게 지정해야합니다.

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 코드를 발견했다. 그들은 아마도 향상시킬 수 있습니다.

+0

귀하의 기여에 감사드립니다. 그러나 이것은 불합리한 숫자와 함께 작동하지 않습니다. –

+1

심지어 비합리적으로 BigDecimal이 실패합니다. :디 – OldCurmudgeon

1

0.15의 절반 무엇

가 가장 가까운 백 반올림 번호?

0.15/2 = 0.075로, 을 최대에서 0.08로 반올림합니다 (반올림 반올림 또는 반반 짝 반올림 규칙으로 가정). 다운 0.07-반올림 IEEE 754 산술

0.15/2 = 0.07499999999999999722444243843710864894092082977294921875.