소수점 이하 자리를 두 배로 이동


97

그래서 저는 1234와 같은 이중 세트를 가지고 있습니다. 12.34로 만들기 위해 소수점 자리를 옮기고 싶습니다.

이렇게하려면 .1에서 1234를 두 번 곱합니다.

double x = 1234;
for(int i=1;i<=2;i++)
{
  x = x*.1;
}
System.out.println(x);

결과 "12.340000000000002"가 인쇄됩니다.

소수점 둘째 자리로 서식을 지정하지 않고 12.34를 올바르게 이중 저장하는 방법이 있습니까?



43
당신이하지 않은 이유가 x /= 100;있습니까?
Mark Ingram

답변:


189

또는를 사용하는 경우 반올림 을 사용 double하거나 float반올림 오류가 발생할 것으로 예상해야합니다. 이 작업을 수행 할 수없는 경우 BigDecimal.

문제는 0.1이 정확한 표현이 아니며 계산을 두 번 수행하면 해당 오류를 복합화한다는 것입니다.

그러나 100은 정확하게 표현할 수 있으므로 다음을 시도하십시오.

double x = 1234;
x /= 100;
System.out.println(x);

인쇄 :

12.34

이것은 Double.toString(d)사용자를 대신하여 소량의 반올림을 수행 하기 때문에 작동 하지만 많지는 않습니다. 반올림하지 않고 어떻게 생겼는지 궁금한 경우 :

System.out.println(new BigDecimal(0.1));
System.out.println(new BigDecimal(x));

인쇄물:

0.100000000000000005551115123125782702118158340454101562
12.339999999999999857891452847979962825775146484375

요컨대, 명시 적으로 수행하든 그렇지 않든 부동 소수점의 현명한 답변에 대해 반올림은 피할 수 없습니다.


참고 : x / 100그리고 x * 0.01그것은 오류를 반올림에 관해서 정확하게 동일하지 않습니다. 이는 첫 번째 표현식의 반올림 오차가 x 값에 의존하는 반면 0.01두 번째 표현식의 반올림 오차는 고정되어 있기 때문 입니다.

for(int i=0;i<200;i++) {
    double d1 = (double) i / 100;
    double d2 = i * 0.01;
    if (d1 != d2)
        System.out.println(d1 + " != "+d2);
}

인쇄물

0.35 != 0.35000000000000003
0.41 != 0.41000000000000003
0.47 != 0.47000000000000003
0.57 != 0.5700000000000001
0.69 != 0.6900000000000001
0.7 != 0.7000000000000001
0.82 != 0.8200000000000001
0.83 != 0.8300000000000001
0.94 != 0.9400000000000001
0.95 != 0.9500000000000001
1.13 != 1.1300000000000001
1.14 != 1.1400000000000001
1.15 != 1.1500000000000001
1.38 != 1.3800000000000001
1.39 != 1.3900000000000001
1.4 != 1.4000000000000001
1.63 != 1.6300000000000001
1.64 != 1.6400000000000001
1.65 != 1.6500000000000001
1.66 != 1.6600000000000001
1.88 != 1.8800000000000001
1.89 != 1.8900000000000001
1.9 != 1.9000000000000001
1.91 != 1.9100000000000001

26
처음에 그렇게 생각하지 않았다 니 믿을 수 없습니다! 감사합니다
:-P

6
100은 이진 형식으로 정확하게 표현할 수 있지만 100으로 나누는 것은 정확하게 표현할 수 없습니다. 따라서, 1234/100당신이했던 것처럼 글쓰기 는 근본적인 문제에 대해 아무것도하지 않습니다 1234 * 0.01. 글쓰기와 정확히 동일해야합니다 .
Brooks Moses

1
@Peter Lawrey : 왜 숫자가 홀수인지 짝수인지 더 설명 해주실 수 있나요? / = 100과 * =. 01은 같을 것이라고 생각합니다. 100이 정수 임에도 불구하고 유형 강제의 결과로 어쨌든 100.0으로 변환되기 때문입니다.
eremzeit

1
/100*0.01영업 이익의 서로 동등 있지만 *0.1*0.1.
Amadan

1
제가 말하고자하는 것은 0.1을 두 번 곱하면 0.01을 한 번 곱하는 것보다 평균적으로 더 큰 오류가 발생한다는 것입니다. 그러나 나는 100에 대한 @JasperBekkers의 요점이 다르고 정확히 이진 표현이 가능하다는 점을 기꺼이 인정할 것입니다.
Amadan

52

아니오-십진수 값을 정확하게 저장하려면을 사용하십시오 BigDecimal. double간단하게 할 수 있는 더 정확히 소수점 숫자의 유한 번호와 세 번째의 값을 기록 할 수있는 것보다 정확히 0.1과 같은 수를 나타냅니다.


46

서식 지정하는 경우 printf를 시도하십시오.

double x = 1234;
for(int i=1;i<=2;i++)
{
  x = x*.1;
}
System.out.printf("%.2f",x);

산출

12.34

8
등급이 높을수록 기술적으로 더 통찰력이 있지만 이것이 OP 문제에 대한 정답입니다. 우리는 일반적으로 double 의 약간의 부정확성에 대해 신경 쓰지 않으므로 BigDecimal은 과도하지만 표시 할 때 종종 출력이 우리의 직관과 일치하는지 확인하기를 원하므로 System.out.printf()올바른 방법입니다.
dimo414

28

금융 소프트웨어에서는 동전에 정수를 사용하는 것이 일반적입니다. 학교에서 부동 소수점 대신 고정 소수점을 사용하는 방법을 배웠지 만 일반적으로 2의 거듭 제곱입니다. 동전을 정수로 저장하는 것은 "고정 소수점"이라고도합니다.

int i=1234;
printf("%d.%02d\r\n",i/100,i%100);

수업 시간에 우리는 일반적으로 어떤 숫자가 밑수로 정확하게 표현 될 수 있는지 물었습니다.

들면 base=p1^n1*p2^n2어떤 N을 나타낼 수있다 .. 여기서, N = N * ^ M1 P1 P2 * ^ m2.

보자 base=14=2^1*7^1... 당신이 표현할 수 1/7 1/14 1/28 사십구분의 일하지만 1/3

금융 소프트웨어에 대해 알고 있습니다. Ticketmaster의 재무 보고서를 VAX asm에서 PASCAL로 변환했습니다. 그들은 동전에 대한 코드가있는 자체 formatln ()을 가졌습니다. 변환 이유는 32 비트 정수로는 더 이상 충분하지 않기 때문입니다. +/- 20 억 페니는 2 천만 달러이고 월드컵이나 올림픽을 위해 넘쳤습니다.

나는 비밀을 맹세했습니다. 오 잘. 아카데미에서 좋은 경우 게시합니다. 업계에서는 비밀을 유지합니다.


12

정수 표현을 시도 할 수 있습니다.

int i =1234;
int q = i /100;
int r = i % 100;

System.out.printf("%d.%02d",q, r);

5
@ 댄 : 왜? 이는 하드웨어 수준의 속도를 유지하면서 금융 앱 (또는 작은 반올림 오류도 허용되지 않는 다른 앱)에 대한 올바른 접근 방식입니다. (물론, 일반적으로 모든 시간을 기입하지, 클래스에 싸여 것)
Amadan

7
이 솔루션에는 약간의 문제가 있습니다. 나머지 r가 10 미만이면 0 패딩이 발생하지 않고 1204는 12.4의 결과를 생성합니다. 올바른 형식 문자열에 더 유사하다 "%의 D % 02D."
jakebman

10

이것은 컴퓨터가 부동 소수점 숫자를 저장하는 방식으로 인해 발생합니다. 그들은 정확하게 그렇게하지 않습니다. 프로그래머는 이 부동 소수점 가이드 를 읽고 부동 소수점 숫자를 처리하는 시련과 고난을 숙지 해야 합니다.


아아, 방금 똑같은 장소에 대한 설명을 쓰고 있었어요. +1.
Pops

@ 하하, 죄송합니다. 어쨌든 나는 스키 티드를 얻었다. :-)
CanSpice

나는 그 이유를 알았지 만 소수점 자리를 옮기는 창의적인 방법이 있는지 궁금합니다. 12.34를 더블로 깔끔하게 저장할 수 있기 때문에 .1
BlackCow

1
12.34를 더블에 깔끔하게 저장할 수 있다면 자바가 그렇게했을 것이라고 생각하지 않습니까? 그렇지 않습니다. 다른 데이터 유형 (예 : BigDecimal)을 사용해야합니다. 또한 루프에서 수행하는 대신 100으로 나누지 않는 이유는 무엇입니까?
CanSpice

Do'h ... 예, 100으로 나누면 깨끗한 12.34가됩니다 ... 감사합니다
:-P

9

많은 게시물에서 BigDecimal을 사용한다고 언급했지만 아무도 BigDecimal을 기반으로 정답을 제공하는 것을 귀찮게하지 않습니까? BigDecimal을 사용하더라도이 코드에서 알 수 있듯이 여전히 잘못 될 수 있기 때문입니다.

String numstr = "1234";
System.out.println(new BigDecimal(numstr).movePointLeft(2));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal(0.01)));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal("0.01")));

이 출력을 제공합니다.

12.34
12.34000000000000025687785232264559454051777720451354980468750
12.34

BigDecimal 생성자는 숫자 생성자보다 String 생성자를 사용하는 것이 더 낫다고 구체적으로 언급합니다. 궁극적 인 정밀도는 선택적 MathContext의 영향도받습니다.

BigDecimal의 자바 독에 따르면 이 가능 하다 BigDecimal를 만들 정확히 당신이 문자열 생성자를 사용하여 제공하는 0.1와 동일합니다.


5

네, 있습니다. 이중 작업을 수행 할 때마다 정확도가 떨어질 수 있지만 정확도는 각 작업마다 다르며 올바른 작업 순서를 선택하여 최소화 할 수 있습니다. 예를 들어 숫자 집합을 곱할 때 곱하기 전에 지수별로 집합을 정렬하는 것이 가장 좋습니다.

숫자 처리에 대한 괜찮은 책은 이것을 설명합니다. 예 : http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

그리고 귀하의 질문에 답하려면 :

곱하기 대신 나누기를 사용하면 올바른 결과를 얻을 수 있습니다.

double x = 1234;
for(int i=1;i<=2;i++)
{
  x =  x / 10.0;
}
System.out.println(x);

3

아니요, Java 부동 소수점 유형 (실제로 모든 부동 소수점 유형)은 크기와 정밀도 사이의 절충안입니다. 많은 작업에 매우 유용하지만 임의의 정밀도가 필요한 경우 BigDecimal.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.