C # 부동 표현식 : 결과 부동 소수점을 int로 캐스팅 할 때 이상한 동작


128

다음과 같은 간단한 코드가 있습니다.

int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;

speed1speed2같은 값을해야하지만 사실, 내가 가진 :

speed1 = 61
speed2 = 62

캐스팅 대신 Math.Round를 사용해야한다는 것을 알고 있지만 왜 값이 다른지 이해하고 싶습니다.

생성 된 바이트 코드를 보았지만 저장소와로드를 제외하고 opcode는 동일합니다.

또한 Java에서 동일한 코드를 시도했지만 62와 62를 올바르게 얻습니다.

누군가 이것을 설명 할 수 있습니까?

편집 : 실제 코드에서는 직접 6.2f * 10이 아니라 함수 호출 * 상수입니다. 다음 바이트 코드가 있습니다.

에 대한 speed1:

IL_01b3:  ldloc.s    V_8
IL_01b5:  callvirt   instance float32 myPackage.MyClass::getSpeed()
IL_01ba:  ldc.r4     10.
IL_01bf:  mul
IL_01c0:  conv.i4
IL_01c1:  stloc.s    V_9

에 대한 speed2:

IL_01c3:  ldloc.s    V_8
IL_01c5:  callvirt   instance float32 myPackage.MyClass::getSpeed()
IL_01ca:  ldc.r4     10.
IL_01cf:  mul
IL_01d0:  stloc.s    V_10
IL_01d2:  ldloc.s    V_10
IL_01d4:  conv.i4
IL_01d5:  stloc.s    V_11

피연산자가 부동 소수점이고 유일한 차이점은이라는 것을 알 수 있습니다 stloc/ldloc.

가상 머신의 경우 Mono / Win7, Mono / MacOS 및 .NET / Windows로 동일한 결과를 시도했습니다.


9
내 생각에 연산 중 하나는 단 정밀도로 수행되고 다른 하나는 배정 밀도로 수행됩니다. 그중 하나는 62보다 약간 작은 값을 반환하므로 정수로자를 때 61이됩니다.
Gabe

2
이것들은 전형적인 부동 소수점 정밀도 문제입니다.
TJHeuvel

3
.Net / WinXP, .Net / Win7, Mono / Ubuntu 및 Mono / OSX에서 이것을 시도하면 Windows 버전 모두에서 결과를 얻을 수 있지만 Mono 버전 모두에서 speed1 및 speed2는 62입니다. 감사합니다 @BoltClock
Eugen Rieck

6
미스터 Lippert ... 당신 주위에 ??
vc 74

6
컴파일러의 상수 식 평가 기는 여기서 당첨되지 않습니다. 분명히 첫 번째 표현식에서 6.2f를 자르고 있습니다.베이스 2에는 정확한 표현이 없으므로 6.199999로 끝납니다. 그러나 두 번째 표현에서는 그렇게하지 않습니다. 어쩌면 배정도로 관리 할 수 ​​있습니다. 그렇지 않으면 부동 소수점 일관성은 문제가되지 않습니다. 이 문제는 해결되지 않으며 해결 방법을 알고 있습니다.
Hans Passant

답변:


168

우선, 6.2f * 10부동 소수점 반올림으로 인해 정확히 62가 아니라고 알고 있습니다 (실제로 표현하면 값은 61.99999809265137 double입니다). 귀하의 질문은 두 개의 겉보기 적으로 동일한 계산이 왜 잘못된 값을 초래하는지에 대한 것입니다.

해답은의 경우이다 (int)(6.2f * 10), 사용자가 복용 double값 61.99999809265137 및 61을 수득 정수로 절단.

의 경우 float f = 6.2f * 10double 값 61.99999809265137을 가져와 가장 가까운으로 반올림 합니다 ( float62). 그런 다음 float정수로 자르고 결과는 62입니다.

연습 : 다음과 같은 일련의 작업 결과를 설명하십시오.

double d = 6.2f * 10;
int tmp2 = (int)d;
// evaluate tmp2

업데이트 : 바와 같이 코멘트에 주목는 표현은 6.2f * 10공식적이며 float두 번째 매개 변수는 암시 적 변환이 있기 때문에에 float이는 더 나은 에 암시 적 변환보다 double.

실제 문제는 컴파일러가 형식 유형 (섹션 11.2.2)보다 정밀도높은 중간체를 사용할 수 있지만 필수는 아닙니다 . 그렇기 때문에 다른 시스템에서 다른 동작이 나타나는 이유는 다음과 같습니다. 식에서 (int)(6.2f * 10)컴파일러는 6.2f * 10로 변환하기 전에 값 을 고정밀 중간 형식 으로 유지할 수 int있습니다. 일치하면 결과는 61입니다. 그렇지 않으면 결과는 62입니다.

두 번째 예에서는 float정수로 변환하기 전에 반올림을 강제 실행 하는 명시 적 할당 입니다.


6
이것이 실제로 질문에 대답하는지 잘 모르겠습니다. 왜 (int)(6.2f * 10)복용 double으로, 값을 f그것이있어 지정 float? 요점은 (여전히 대답하지 않은) 여기에 있다고 생각합니다.
ken2k

1
float 리터럴 * int literal이기 때문에 컴파일러가 최선의 숫자 유형을 자유롭게 사용할 수 있다고 결정하고 정밀도를 높이기 위해 두 배로 나갔습니다 (아마도). (또한 IL이 동일하다는 것을 설명 할 것입니다)
George Duckett

5
좋은 지적. 의 유형 6.2f * 10은 실제로 float는 아닙니다 double. 컴파일러는 11.1.6 의 마지막 단락에서 허용하는 것처럼 중간체를 최적화한다고 생각합니다 .
Raymond Chen

3
값이 동일합니다 (값은 61.99999809265137 임). 차이점은 값이 정수가되는 길입니다. 어떤 경우에는 정수로 직접 이동하고 다른 경우에는 float먼저 변환을 수행합니다.
Raymond Chen

38
여기 레이몬드의 대답은 물론 정확합니다. 나는 C # 컴파일러와 JIT 컴파일러가 모두 더 정밀도를 사용할 수 있습니다 점에 유의 언제든지 , 그리고 그렇게 일관성 . 사실 그들은 그렇게합니다. 이 질문은 StackOverflow에서 수십 번 발생했습니다. 최근 예는 stackoverflow.com/questions/8795550/… 을 참조하십시오 .
Eric Lippert

11

기술

부동 숫자는 거의 정확하지 않습니다. 6.2f같은 것 6.1999998...입니다. 이것을 int로 캐스트하면 잘 리며이 * 10은 61이됩니다.

Jon Skeets DoubleConverter수업을 확인하십시오 . 이 클래스를 사용하면 부동 숫자의 값을 문자열로 실제로 시각화 할 수 있습니다. Double그리고 float둘 다 부동 숫자 이며, 십진수는 고정 소수점 숫자가 아닙니다.

견본

DoubleConverter.ToExactString((6.2f * 10))
// output 61.9999980926513671875

추가 정보


5

IL을보십시오 :

IL_0000:  ldc.i4.s    3D              // speed1 = 61
IL_0002:  stloc.0
IL_0003:  ldc.r4      00 00 78 42     // tmp = 62.0f
IL_0008:  stloc.1
IL_0009:  ldloc.1
IL_000A:  conv.i4
IL_000B:  stloc.2

컴파일러는 컴파일 타임 상수 표현식을 상수 값으로 줄이고 상수를로 변환 할 때 어떤 시점에서 잘못된 근사를 만든다고 생각합니다 int. 의 speed2경우이 변환은 컴파일러가 아니라 CLR에 의해 수행되며 다른 규칙을 적용하는 것 같습니다 ...


1

내 생각 6.2f에 float 정밀도를 사용한 실제 표현은 6.1999999while 62f과 비슷할 것 62.00000001입니다. (int)캐스팅은 항상 십진수 값을 자르 므로 그 동작을 얻는 이유입니다.

편집 : 의견에 따르면 int훨씬 더 정확한 정의 로 캐스팅 하는 동작을 표현했습니다.


int소수 자릿수로 캐스팅 하면 소수점 이하 자릿수 가 잘립니다.
Jim D' Angelo

@James D' Angelo : 죄송합니다. 영어는 제 기본 언어가 아닙니다. 정확한 단어를 몰랐기 때문에 기본적으로 동일한 동작을 설명하는 "양수를 다룰 때 반올림"이라고 정의했습니다. 그러나 그렇습니다. 요점 은 잘림 이 정확한 단어입니다.
InBetween

문제는, 그냥 symantics 없습니다하지만 누군가가 생각을 시작하면 문제가 발생할 수 있습니다 float-> int반올림 포함합니다. = D
Jim D' Angelo

1

이 코드를 컴파일하고 분해했습니다 (Win7 / .NET 4.0에서). 컴파일러는 부동 상수 표현식을 double로 평가한다고 생각합니다.

int speed1 = (int)(6.2f * 10);
   mov         dword ptr [rbp+8],3Dh       //result is precalculated (61)

float tmp = 6.2f * 10;
   movss       xmm0,dword ptr [000004E8h]  //precalculated (float format, xmm0=0x42780000 (62.0))
   movss       dword ptr [rbp+0Ch],xmm0 

int speed2 = (int)tmp;
   cvttss2si   eax,dword ptr [rbp+0Ch]     //instrunction converts float to Int32 (eax=62)
   mov         dword ptr [rbp+10h],eax 

0

Single7 자리 만 포함 Int32하고 컴파일러로 캐스트 할 때 모든 부동 소수점 숫자를 자릅니다. 변환 중에 하나 이상의 유효 숫자가 손실 될 수 있습니다.

Int32 speed0 = (Int32)(6.2f * 100000000); 

619999980의 결과를 제공하므로 (Int32) (6.2f * 10)은 61을 제공합니다.

두 개의 Single이 곱해질 때 다릅니다.이 경우 자르기 작업은 없지만 근사치 만 있습니다.

http://msdn.microsoft.com/en-us/library/system.single.aspx 참조


-4

int파싱 ​​대신 타입 캐스팅을하는 이유가 있습니까?

int speed1 = (int)(6.2f * 10)

그런 다음 읽을 것

int speed1 = Int.Parse((6.2f * 10).ToString()); 

차이점은 아마도 반올림과 관련이 있습니다. 캐스팅 double하면 61.78426과 같은 것을 얻을 수 있습니다.

다음 출력에 유의하십시오

int speed1 = (int)(6.2f * 10);//61
double speed2 = (6.2f * 10);//61.9999980926514

그렇기 때문에 다른 가치를 얻게됩니다!


1
Int.Parse문자열을 매개 변수로 사용합니다.
ken2k

문자열 만 구문 분석 할 수 있습니다. System.Convert를 사용하지 않는 이유는 무엇입니까?
vc 74
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.