Java에서 double을 사용하여 정밀도 유지


답변:


151

다른 사람들이 언급했듯이 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)입니다.

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

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


3
실제로 "소수점"앞의 1이 정규화되지 않은 값에 대해 암시되어 추가 정밀도를 제공하기 때문에 일반적으로 53 개의 유효 비트가 있습니다. 예를 들어, 3은 (1.) 1000 ... x 2 ^ 1로 저장되는 반면 0.5는 (1.) 0000 ... x 2 ^ -1로 저장됩니다. 값이 비정규 화 된 경우 (모든 지수 비트가 0 임) 일반적으로 유효 숫자가 적을 것입니다. 예를 들어 1 x 2 ^ -1030은 (0.) 00000001 x 2 ^ -1022로 저장되므로 유효 숫자 7 자리가 스케일로 희생되었습니다.
사라 필립스

1
동안은 주목해야한다 BigDecimal훨씬 낮은 속도보다 double가 정확도 15 소수점을 가지고 이중으로 필요하지 않습니다이 경우, 당신은 단지 반올림해야합니다.
Peter Lawrey

2
@PeterLawrey은 15 개 진수이 숫자 들은 모든 경우, 정확도를 하기 전에 소수점. 소수점과 이진 분수의 비교할 수 없기 때문에 소수점 이후에 모든 것이 발생할 수 있습니다.
Lorne의 후작 23.53에

@EJP 맞습니다. 약 15 자리의 정밀도가 있습니다. 16 일 수 있지만 15 또는 14라고 가정하는 것이 더 안전합니다.
Peter Lawrey

@PeterLawrey EJP의 보정은 내 질문 때문이었다 stackoverflow.com/questions/36344758/...는 당신은 정확히 15 어떤 상황이 16 또는 14이 될 수없는 이유에 대해 확장하십시오 수 있을까?
Shivam Sinha

103

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

33.3333333333333285963817615993320941925048828125

이것을 100으로 나누면 다음과 같은 결과가 나타납니다.

0.333333333333333285963817615993320941925048828125

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

0.3333333333333332593184650249895639717578887939453125

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

0.33333333333333326

114
미래에 이것을 읽고있는 사람에게는 왜 그 답이 그 질문과 관련이 없는지 의아해 할 것입니다. 어떤 중재자는 나 (그리고 다른 사람들)가이 질문에 대해 다른 질문과 병합하기로 결정했습니다.
Stephen Canon

정확한 이중 값을 어떻게 알 수 있습니까?
Michael Yaworski


23

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

더하기, 빼기, 곱하기 및 나누기 및 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);
    }
}

}

1
확실 numerator하고 s denominator해야 int합니까? 부동 소수점 정밀도를 원하는 이유는 무엇입니까?
Samir Talwar

실제로 필요하지는 않지만 toDouble 함수에서 캐스팅을 피하여 코드를 더 잘 읽습니다.
Viral Shah

5
ViralShah : 또한 수학 연산을 처리 할 때 부동 소수점 오류가 발생할 수 있습니다. 이 연습의 요점은 정확히 피하는 것이기 때문에 변경하는 것이 현명합니다.
Samir Talwar

위의 Samir Talwar가 언급 한 이유로 double 대신 int를 사용하도록 편집했습니다.
Viral Shah

3
분수의 이러한 구현은 가장 간단한 형태로 줄이지 않기 때문에 문제가 있습니다. 2 / 3 * 1 / 2는 답이 1/3이되도록 2/6을줍니다. 이상적으로는 생성자에서 분자와 제수의 gcd를 찾고 둘로 나눕니다.
Salix alba

15

제한된 정밀도 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을 사용하십시오 (더하기 및 빼기) 사소한 : 곱셈을 조심하십시오).

그러나 계산을 위해 이진에 만족하지만 약간 친숙한 형식으로 인쇄하려는 경우 java.util.Formatter또는을 시도하십시오 String.format. 형식 문자열에서 double의 전체 정밀도보다 작은 정밀도를 지정하십시오. 10 개의 유효 숫자 (예 : 11.399999999999)는 11.4이므로 이진 결과가 소수의 소수 자릿수가 필요한 값에 매우 가까운 경우 결과는 거의 정확하고 사람이 읽을 수 있습니다.

지정하는 정밀도는 숫자로 수행 한 수학의 양에 따라 다릅니다. 일반적으로할수록 더 많은 오류가 누적되지만 일부 알고리즘은 다른 알고리즘보다 훨씬 빠르게 누적됩니다 ( '불안정한'이라고 함). 반올림 오류와 관련하여 "안정적"과 반대). 당신이하고있는 모든 것이 약간의 값을 추가하는 것이라면, 소수점 이하 1 자리를 떨어 뜨리면 물건이 정렬 될 것입니다. 실험.


3
아니요, 금전적 가치에 이중을 사용 하지 마십시오 ! 돈으로 정밀도가 필요합니다. 대신 BigDecimal을 사용하십시오. 그렇지 않으면 대답이 좋습니다. 정밀도가 필요한 것은 BigDecimal을 사용하십시오. 정밀도가 중요하지 않은 경우 float 또는 double을 사용할 수 있습니다.
MetroidFan2002

1
질문은 더 이상 돈이 관련되어 있음을 나타내거나 암시하지 않습니다. 나는 구체적으로 돈을 위해 BigDecimal 또는 정수를 사용한다고 말합니다. 뭐가 문제 야?
Steve Jessop

1
그리고 동일한 "BigDecimal를 사용하지 마십시오는"돈을 위해 이중 사용하지 않는 " 또는 3 분의 두 배를". 그러나 때때로 문제는 분열과 관련이 있는데,이 경우 모든 분모의 모든 주요 요소로 나눌 수없는 모든 염기가 거의 나쁘다.
Steve Jessop

1
정밀도가 유효 숫자 4 자리 미만인 경우 .9999 = 1
Brian Leahy

9

정밀 수학이 정말로 필요한 경우 java의 java.math.BigDecimal 클래스를 사용하는 것이 좋습니다. 다음은 BigDecimal대한 Oracle / Sun의 좋은 기사입니다 . 누군가가 언급 한 바와 같이 당신이 1/3를 대표 할 수 없다 동안, 당신은 할 수 있습니다 당신이 결과가되고 싶어 정확히 어떻게 정확하게 결정하는 힘이있다. setScale ()은 당신의 친구입니다 .. :)

자, 지금 당장 너무 많은 시간을 보냈기 때문에 귀하의 질문과 관련된 코드 예제가 있습니다.

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


    }
}

그리고 내가 좋아하는 새로운 언어 인 Groovy를 연결하기 위해 여기에 똑같은 예가 있습니다.

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

5

이것을 세 줄의 예로 만들었을 것입니다. :)

정확한 정밀도를 원하면 BigDecimal을 사용하십시오. 그렇지 않으면 원하는 정밀도에 10을 곱한 정수를 사용할 수 있습니다.


5

다른 사람들이 지적했듯이, 소수는 10의 거듭 제곱을 기반으로하고 이진수는 2의 거듭 제곱을 기반으로하기 때문에 모든 소수 값을 이진수로 표현할 수있는 것은 아닙니다.

정밀도가 중요한 경우 BigDecimal을 사용하지만 친숙한 출력을 원할 경우 :

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

당신에게 줄 것이다 :

11.40


5

7.3에는 바이너리로 유한 표현이 없기 때문에 할 수 없습니다. 가장 가까운 곳은 2054767329987789 / 2 ** 48 = 7.3 + 1 / 1407374883553280입니다.

자세한 설명 은 http://docs.python.org/tutorial/floatingpoint.html참조하십시오 . (Python 웹 사이트에 있지만 Java와 C ++에는 동일한 "문제"가 있습니다.)

해결책은 문제가 무엇인지에 따라 다릅니다.

  • 노이즈 숫자를 모두보고 싶지 않다면 문자열 형식을 수정하십시오. 유효 숫자가 15 자리를 넘지 않아야합니다 (또는 플로트의 경우 7).
  • 숫자의 부정확성이 "if"문과 같은 것을 깨뜨리는 경우 if (x == 7.3) 대신 if (abs (x-7.3) <TOLERANCE)를 작성해야합니다.
  • 돈으로 일하고 있다면 실제로 원하는 것은 십진 고정 소수점입니다. 정수 센트 또는 통화의 가장 작은 단위를 저장하십시오.
  • (아무리도 매우 중요) 53 개의 유효 비트 (15-16 개의 유효 자릿수) 이상의 정밀도가 필요한 경우 BigDecimal과 같은 고정밀 부동 소수점 유형을 사용하십시오.

7.3은 바이너리로 유한 한 표현을하지는 않지만 C ++에서 같은 것을 시도 할 때
-7.3을 얻습니다

2
wrongusername : 아닙니다. 그것은 단지 그런 식으로 표시됩니다. 실제 답변을 보려면 "% .17g"형식 (또는 "% .51g")을 사용하십시오.
dan04

4
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
}

3

java.math.BigDecimal 사용

이중은 내부적으로 이진 분수이므로 때로는 소수를 정확한 소수로 표현할 수 없습니다.


1
맹목적으로 BigDecimal을 추천하면 -1입니다. 실제로 십진 산술 이 필요 하지 않으면 (즉, 돈으로 계산을하는 경우) BigDecimal은 도움이되지 않습니다. 모든 부동 소수점 오류를 해결하지는 못합니다. 여전히 1 / 3 * 3 = 0.9999999999999999999999999999 및 sqrt (2) ** 2 = 1.999999999999999999999999999를 처리해야합니다. 또한 BigDecimal은 속도가 크게 저하됩니다. 더구나, 자바에는 연산자 오버로딩이 없기 때문에 모든 코드를 다시 작성해야합니다.
dan04

2
@ dan04-돈으로 계산하는 경우 왜 내재적 오류를 알고있는 부동 표현을 사용합니까? 정말로 센트 비율을 원한다면 오랫동안 사용하고 수천 센트를 계산하십시오. 더 많은 OP는 비이성적 인 숫자를 언급하지 않았으며, 그가 우려한 것은 추가뿐이었습니다. 게시물을주의 깊게 읽고 대답하기 전에 문제를 이해하면 당황 스러울 수 있습니다.
Newtopian

3
@Newtopian : 나는 당황 할 것이 없습니다. 영업은하지 NO 돈을 언급, 나 자신의 문제가 어떤 고유 decimalness을 가지고있는 표시를.
dan04

@ dan04-OP는하지 않았다 ... 당신은하고 맹목적으로 제공된 세부 정보의 양이 부족한 경우 완벽하게 수용 가능한 답변에 대해 의견을 제시했습니다.
Newtopian

2

모든 것에 100을 곱하고 센트 단위로 저장하십시오.


2
@Draemon-마지막 편집 이전의 게시물을 봅니다. "shoppingTotal"및 "calcGST"및 "calcPST"항목은 모두 돈처럼 보입니다.
Paul Tomblin

2

컴퓨터는 숫자를 이진수로 저장하며 실제로 33.333333333 또는 100.0과 같은 숫자를 정확하게 나타낼 수는 없습니다. 이것은 복식 사용에 대한 까다로운 것 중 하나입니다. 사용자에게 답변을 표시하기 전에 답을 반올림해야합니다. 운 좋게도 대부분의 응용 프로그램에서 소수점 이하 자릿수는 필요하지 않습니다.


나는 가능한 가장 높은 정밀도를 선호하는 확률 계산을하고 있습니다. 그러나 나는 한계가 있음을 이해합니다
Aly

2

부동 소수점 숫자는 주어진 부동 소수점 숫자에 대해 다음으로 높은 부동 소수점 숫자가 있다는 점에서 실수와 다릅니다. 정수와 같습니다. 1과 2 사이의 정수는 없습니다.

1/3을 부동 소수점으로 표현할 방법이 없습니다. 그 아래에 부유물이 있고 그 위에 부유물이 있으며 그 사이에 일정한 거리가 있습니다. 그리고 1/3은 그 공간에 있습니다.

Apfloat for Java는 임의의 정밀 부동 소수점 숫자와 함께 작동한다고 주장하지만 결코 사용하지는 않았습니다. 아마 볼만한 가치가 있습니다. http://www.apfloat.org/apfloat_java/

Java 부동 소수점 고정밀 라이브러리 전에 비슷한 질문을 받았습니다.


1

이중은 Java 소스에서 10 진수의 근사값 입니다. double (2 진 코드 값)과 소스 (10 진수 코드) 사이의 불일치 결과가 나타납니다.

Java가 가장 가까운 이진 근사를 생성합니다. java.text.DecimalFormat을 사용하여 더 잘 보이는 10 진수 값을 표시 할 수 있습니다.


1

BigDecimal을 사용하십시오. ROUND_HALF_EVEN과 같이 반올림 규칙을 지정할 수도 있습니다. ROUND_HALF_EVEN과 같이 거리가 같은 경우 짝수 이웃으로 반올림하여 통계 오류를 최소화합니다 (예 : 1.5와 2.5 반올림 2).


1

짧은 대답 : 항상 BigDecimal을 사용 하고 이중 인수가 아닌 String 인수 와 함께 생성자를 사용하고 있는지 확인하십시오 .

예를 들어, 다음 코드는 원하는대로 11.4를 인쇄합니다.

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

0

BigDecimal을 확인하면 부동 소수점 산술과 관련된 문제를 처리합니다.

새로운 전화는 다음과 같습니다.

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

setScale ()을 사용하여 사용할 소수점 이하 자릿수를 설정하십시오.


0

Math 클래스의 round () 메소드를 사용하지 않는 이유는 무엇입니까?

// 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

0

이중 값을 사용하는 것 외에 다른 선택이 없으면 아래 코드를 사용할 수 있습니다.

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

-1

BigDecimal을 사용하여 애 퍼드를 낭비하지 마십시오. 99.99999 %의 경우 필요하지 않습니다. java double 유형은 대략적인 크기이지만 거의 모든 경우에 충분히 정확합니다. 유효 숫자 14 자리에 오류가 있음을 유의하십시오. 이것은 정말로 무시할 만하다!

좋은 출력을 얻으려면 :

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

2
나는 그가 숫자 정밀도가 아니라 출력에 대해 걱정한다고 생각합니다. 예를 들어 BigDecimal은 도움이되지 않습니다. 3으로 나눕니다. 심지어 상황을 악화시킬 수도 있습니다.
Maciek D.

절대로 절대로 부동 소수점을 사용해서는 안됩니다. 나는 지시를 받았음에도 불구하고이 규칙을 어긴 계약자에게 중대한 재 작업이 시행되는 것을 보았다.
Lorne의 후작 23.52에
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.