IT박스

Java에서 두 배로 정밀도 유지

itboxs 2020. 6. 28. 19:22
반응형

Java에서 두 배로 정밀도 유지


public class doublePrecision {
    public static void main(String[] args) {

        double total = 0;
        total += 5.6;
        total += 5.8;
        System.out.println(total);
    }
}

위 코드는 다음과 같이 인쇄됩니다.

11.399999999999

11.4를 인쇄 (또는 그대로)하려면 어떻게해야합니까?


다른 사람들이 언급했듯이 BigDecimal11.4의 정확한 표현을 원한다면 클래스 를 사용하고 싶을 것입니다 .

이제 왜 이런 일이 일어나는지 약간의 설명이 있습니다.

Java floatdouble기본 유형은 부동 소수점 숫자이며 숫자는 분수와 지수의 이진 표현으로 저장됩니다.

보다 구체적으로 double형식 과 같은 배정 밀도 부동 소수점 값 은 64 비트 값입니다.

  • 1 비트는 부호를 나타냅니다 (양수 또는 음수).
  • 지수에 대해 11 비트.
  • 유효 자릿수 (이진수로서 소수 부분)는 52 비트입니다.

이 부분들은 결합되어 double값을 나타냅니다.

(출처 : Wikipedia : 배정 밀도 )

Java에서 부동 소수점 값을 처리하는 방법에 대한 자세한 설명은 4.2.3 섹션 : Java 언어 사양의 부동 소수점 유형, 형식 및 값 을 참조하십시오.

byte, char, int, long유형되는 고정 소수점 숫자의 정확한 representions있는 숫자. 고정 소수점 숫자와 달리 부동 소수점 숫자는 "대부분의 시간"으로 가정해도 숫자의 정확한 표현을 반환 할 수 없습니다. 이것이 당신 11.399999999999의 결과로 끝나는 이유 입니다 5.6 + 5.8.

1.5 또는 150.1005와 같이 정확한 값이 필요한 경우 고정 소수점 유형 중 하나를 사용하면 숫자를 정확하게 나타낼 수 있습니다.

이미 여러 번 언급했듯이 Java에는 BigDecimal매우 많은 수와 매우 작은 수를 처리 하는 클래스가 있습니다.

BigDecimal클래스 의 Java API 참조에서 :

불변의 임의 정밀도 부호있는 10 진수 BigDecimal은 임의의 정밀도 정수 비 스케일 값과 32 비트 정수 스케일로 구성됩니다. 0 또는 양수인 경우 배율은 소수점 오른쪽의 자릿수입니다. 음수 인 경우, 스케일링되지 않은 숫자 값에 스케일 부정의 거듭 제곱에 10을 곱합니다. 따라서 BigDecimal로 표시되는 숫자의 값은 (unscaledValue × 10 ^ -scale)입니다.

부동 소수점 숫자 및 정밀도와 관련하여 스택 오버플로에 대한 많은 질문이 있습니다. 관심을 가질만한 관련 질문 목록은 다음과 같습니다.

부동 소수점 숫자에 대한 아주 세부적인 내용을 알고 싶다면 모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 사항을 살펴보십시오 .


예를 들어, 이중 숫자를 입력 33.33333333333333하면 얻는 값이 실제로 가장 가까운 표현 가능한 배정 밀도 값입니다.

33.3333333333333285963817615993320941925048828125

100으로 나누면

0.333333333333333285963817615993320941925048828125

배정도 숫자로 표현할 수 없으므로 가장 가까운 표현 가능한 값으로 반올림됩니다.

0.3333333333333332593184650249895639717578887939453125

이 값을 인쇄하면 소수점 이하 17 자리로 다시 반올림 되어 다음을 제공합니다.

0.33333333333333326

값을 분수로만 처리하려는 경우 분자 및 분모 필드를 보유하는 분수 클래스를 만들 수 있습니다.

더하기, 빼기, 곱하기 및 나누기 및 toDouble 메서드에 대한 메서드를 작성하십시오. 이 방법으로 계산 중에 실수를 피할 수 있습니다.

편집 : 빠른 구현,

public class Fraction {

private int numerator;
private int denominator;

public Fraction(int n, int d){
    numerator = n;
    denominator = d;
}

public double toDouble(){
    return ((double)numerator)/((double)denominator);
}


public static Fraction add(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop + bTop, a.denominator * b.denominator);
    }
    else{
        return new Fraction(a.numerator + b.numerator, a.denominator);
    }
}

public static Fraction divide(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}

public static Fraction multiply(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}

public static Fraction subtract(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop-bTop, a.denominator*b.denominator);
    }
    else{
        return new Fraction(a.numerator - b.numerator, a.denominator);
    }
}

}

제한된 정밀도 10 진수 산술을 사용하고 1/3을 처리하려는 경우 동일한 문제가 발생합니다. 0.333333333 * 3은 1.00000000이 아니라 0.999999999입니다.

불행히도 5.6, 5.8 및 11.4는 이진수로 반올림되지 않습니다. 따라서 0.3333이 정확히 1/3이 아닌 것처럼 플로트 표현은 정확하지 않습니다.

사용하는 모든 숫자가 되풀이되지 않는 소수이고 정확한 결과를 원하면 BigDecimal을 사용하십시오. 또는 다른 사람들이 말했듯이, 귀하의 가치가 모두 0.01 또는 0.001의 배수 또는 무언가라는 의미에서 돈과 같다면 모든 것에 10의 고정 제곱을 곱하고 int 또는 long을 사용하십시오 (더하기 및 빼기 사소한 : 곱셈을 조심하십시오).

However, if you are happy with binary for the calculation, but you just want to print things out in a slightly friendlier format, try java.util.Formatter or String.format. In the format string specify a precision less than the full precision of a double. To 10 significant figures, say, 11.399999999999 is 11.4, so the result will be almost as accurate and more human-readable in cases where the binary result is very close to a value requiring only a few decimal places.

The precision to specify depends a bit on how much maths you've done with your numbers - in general the more you do, the more error will accumulate, but some algorithms accumulate it much faster than others (they're called "unstable" as opposed to "stable" with respect to rounding errors). If all you're doing is adding a few values, then I'd guess that dropping just one decimal place of precision will sort things out. Experiment.


You may want to look into using java's java.math.BigDecimal class if you really need precision math. Here is a good article from Oracle/Sun on the case for BigDecimal. While you can never represent 1/3 as someone mentioned, you can have the power to decide exactly how precise you want the result to be. setScale() is your friend.. :)

Ok, because I have way too much time on my hands at the moment here is a code example that relates to your question:

import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * xaymaca@gmail.com
 * on Mar 17, 2010 at  11:05:16 PM
 */
public class BigUp {

    public static void main(String[] args) {
        BigDecimal first, second, result ;
        first = new BigDecimal("33.33333333333333")  ;
        second = new BigDecimal("100") ;
        result = first.divide(second);
        System.out.println("result is " + result);
       //will print : result is 0.3333333333333333


    }
}

and to plug my new favorite language, Groovy, here is a neater example of the same thing:

import java.math.BigDecimal

def  first =   new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")


println "result is " + first/second   // will print: result is 0.33333333333333

Pretty sure you could've made that into a three line example. :)

If you want exact precision, use BigDecimal. Otherwise, you can use ints multiplied by 10 ^ whatever precision you want.


As others have noted, not all decimal values can be represented as binary since decimal is based on powers of 10 and binary is based on powers of two.

If precision matters, use BigDecimal, but if you just want friendly output:

System.out.printf("%.2f\n", total);

Will give you:

11.40

You're running up against the precision limitation of type double.

Java.Math has some arbitrary-precision arithmetic facilities.


You can't, because 7.3 doesn't have a finite representation in binary. The closest you can get is 2054767329987789/2**48 = 7.3+1/1407374883553280.

Take a look at http://docs.python.org/tutorial/floatingpoint.html for a further explanation. (It's on the Python website, but Java and C++ have the same "problem".)

The solution depends on what exactly your problem is:

  • If it's that you just don't like seeing all those noise digits, then fix your string formatting. Don't display more than 15 significant digits (or 7 for float).
  • If it's that the inexactness of your numbers is breaking things like "if" statements, then you should write if (abs(x - 7.3) < TOLERANCE) instead of if (x == 7.3).
  • If you're working with money, then what you probably really want is decimal fixed point. Store an integer number of cents or whatever the smallest unit of your currency is.
  • (VERY UNLIKELY) If you need more than 53 significant bits (15-16 significant digits) of precision, then use a high-precision floating-point type, like BigDecimal.

private void getRound() {
    // this is very simple and interesting 
    double a = 5, b = 3, c;
    c = a / b;
    System.out.println(" round  val is " + c);

    //  round  val is  :  1.6666666666666667
    // if you want to only two precision point with double we 
            //  can use formate option in String 
           // which takes 2 parameters one is formte specifier which 
           // shows dicimal places another double value 
    String s = String.format("%.2f", c);
    double val = Double.parseDouble(s);
    System.out.println(" val is :" + val);
    // now out put will be : val is :1.67
}

Use java.math.BigDecimal

Doubles are binary fractions internally, so they sometimes cannot represent decimal fractions to the exact decimal.


Multiply everything by 100 and store it in a long as cents.


Computers store numbers in binary and can't actually represent numbers such as 33.333333333 or 100.0 exactly. This is one of the tricky things about using doubles. You will have to just round the answer before showing it to a user. Luckily in most applications, you don't need that many decimal places anyhow.


Floating point numbers differ from real numbers in that for any given floating point number there is a next higher floating point number. Same as integers. There's no integer between 1 and 2.

There's no way to represent 1/3 as a float. There's a float below it and there's a float above it, and there's a certain distance between them. And 1/3 is in that space.

Apfloat for Java claims to work with arbitrary precision floating point numbers, but I've never used it. Probably worth a look. http://www.apfloat.org/apfloat_java/

A similar question was asked here before Java floating point high precision library


Doubles are approximations of the decimal numbers in your Java source. You're seeing the consequence of the mismatch between the double (which is a binary-coded value) and your source (which is decimal-coded).

Java's producing the closest binary approximation. You can use the java.text.DecimalFormat to display a better-looking decimal value.


Use a BigDecimal. It even lets you specify rounding rules (like ROUND_HALF_EVEN, which will minimize statistical error by rounding to the even neighbor if both are the same distance; i.e. both 1.5 and 2.5 round to 2).


Check out BigDecimal, it handles problems dealing with floating point arithmetic like that.

The new call would look like this:

term[number].coefficient.add(co);

Use setScale() to set the number of decimal place precision to be used.


Why not use the round() method from Math class?

// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4

If you have no choice other than using double values, can use the below code.

public static double sumDouble(double value1, double value2) {
    double sum = 0.0;
    String value1Str = Double.toString(value1);
    int decimalIndex = value1Str.indexOf(".");
    int value1Precision = 0;
    if (decimalIndex != -1) {
        value1Precision = (value1Str.length() - 1) - decimalIndex;
    }

    String value2Str = Double.toString(value2);
    decimalIndex = value2Str.indexOf(".");
    int value2Precision = 0;
    if (decimalIndex != -1) {
        value2Precision = (value2Str.length() - 1) - decimalIndex;
    }

    int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
    sum = value1 + value2;
    String s = String.format("%." + maxPrecision + "f", sum);
    sum = Double.parseDouble(s);
    return sum;
}

Short answer: Always use BigDecimal and make sure you are using the constructor with String argument, not the double one.

Back to your example, the following code will print 11.4, as you wish.

public class doublePrecision {
    public static void main(String[] args) {
      double total = new BigDecimal("0");
      total = total.add(new BigDecimal("5.6");
      total = total.add(new BigDecimal("5.8");
      System.out.println(total);
    }
}

Do not waste your efford using BigDecimal. In 99.99999% cases you don't need it. java double type is of cource approximate but in almost all cases, it is sufficiently precise. Mind that your have an error at 14th significant digit. This is really negligible!

To get nice output use:

System.out.printf("%.2f\n", total);

참고URL : https://stackoverflow.com/questions/322749/retain-precision-with-double-in-java

반응형