C # 부동 표현식 : 결과 부동 소수점을 int로 캐스팅 할 때 이상한 동작 동일한 코드를 시도했지만

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

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로 동일한 결과를 시도했습니다.



답변

우선, 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.2f같은 것 6.1999998...입니다. 이것을 int로 캐스트하면 잘 리며이 * 10은 61이됩니다.

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

견본

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

추가 정보


답변

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에 의해 수행되며 다른 규칙을 적용하는 것 같습니다 …


답변

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

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


답변

이 코드를 컴파일하고 분해했습니다 (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 


답변

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 참조


답변

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

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