4. 예상
• 앞서 코드를 눈으로 보면 대충 값이 나옴.
• C1은 13…
• C2도 13……
• 당연 C3도 13………..
암 절대 틀릴 리 없지!! 내말은 진리라오!!
5. 결과
• 두둥… 실행해 보니… 결과는 내 얼굴을 후려침.
그리고 내 발가락도 가져감……
으 아니 컴파일러 양반.. 이러면 안되잖아!!
6. 분석-1
• 갑자기 궁금했더랬지…아 왜 팠을까 그냥 묻을걸…
• 요게 만들어진 어셈 코드….
(봐도 뭔지 모르겠음..)
• 하나씩 파보자…
7. 분석-2
• 처음엔 연산을 의심.
• 값이 엉뚱한 녀석은 다음 연산.
int c1 = (int)(a * b);
002026A5 fld dword ptr [ebp-40h]
002026A8 fmul dword ptr [ebp-44h]
002026AB fstp qword ptr [ebp-7Ch]
002026AE movsd xmm0,mmword ptr [ebp-7Ch]
002026B3 cvttsd2si eax,xmm0
002026B7 mov dword ptr [ebp-4Ch],eax
• 평범하게 FPU에 갔다 나오는 연산만 수행함.
• 특이점이라면 cvttsd2si 라는 SSE 명령어 셋을 쓴다 정도???
8. 분석-3
• 정상인 연산은 다음.
int c3 = (int)(float)(a * b);
002526CC fld dword ptr [ebp-40h]
002526CF fmul dword ptr [ebp-44h]
002526D2 fstp dword ptr [ebp-74h]
002526D5 fld dword ptr [ebp-74h]
002526D8 fstp qword ptr [ebp-7Ch]
002526DB fld qword ptr [ebp-7Ch]
002526DE fstp qword ptr [ebp-7Ch]
002526E1 movsd xmm0,mmword ptr [ebp-7Ch]
002526E6 cvttsd2si eax,xmm0
002526EA mov dword ptr [ebp-54h],eax
• 역시 FPU를 좀더 여러 번 왕래한 것 뿐.. 연산 구조는 앞의 문제
와 동일.
9. 분석-4
• 특이점 발견.
int c3 = (int)(float)(a * b);
002526CC 링 dword ptr [ebp-40h]
002526CF fmul dword ptr [ebp-44h]
002526D2 fstp dword ptr [ebp-74h] ->
002526D5 fld dword ptr [ebp-74h]
002526D8 fstp qword ptr [ebp-7Ch] ->
002526DB fld qword ptr [ebp-7Ch]
002526DE fstp qword ptr [ebp-7Ch]
002526E1 movsd xmm0,mmword ptr [ebp-7Ch]
002526E6 cvttsd2si eax,xmm0
002526EA mov dword ptr [ebp-54h],eax
• 값을 임의의 위치에 쓰고 다시 받으니 보정이 되어있네??? 이 부분은
(float) casting 부분임.
• 이 부분은 FPU 레지스터가 80비트이고 dword는 32비트 이므로 단정도가 잘리게 됨. 그래서
보정이 되는 거.
10. 분석-5
• 근데 이것도 특이점일 뿐.. 원인은 아님.
• 뒤져보다 보니.. 다음 코드가 눈에 띄임.
00252695 mov dword ptr [ebp-44h], 3FA66666h
요놈… 0x3FA66666h
2진수로는 111111101001100110011001100110
10진수로는 1067869798
부동소수점으로는 1.29999995e+0000
• 아 뭔가 비슷하다. 이게 문제의 원인 같음.
11. 분석-6
• 컴파일러가 코드를 생성할 때 코드에 상수가 있으면 이를 해당
generic 타입으로 생성해줌.
이 경우는 float이니 4바이트로 생성.. DWORD 타입.
근데 0.3은 표현할 수 없어서.. 0110이 반복되는 수로 표현.(정확히는 내부에서
정수 / 10^(n)로 계산해서 그럼..)
이런 수식을 확인할 수 있는 예로 다음과 같은 코드를 쓰면
float b = (float)13 * 0.1f;
0x3FA66667h로 들어감.
• 이게 바로 4바이트 변수로 복사되서 생기는 문제. 애초에 값의 변화
가 없다면 매번 동일한 결과를 볼 수 있겠지만 단정도의 손상이 발생
해서 12, 13으로 요상한 결과가 되는 거다.
12. 분석-7
• 그럼 컴파일러 문제니 손 빨고 있어야 하나?
• 그건 아님. Intel에서 SSE 명령셋으로 새로운 걸 준비해서 제공했음.
• 기존 실수 연산에서 저런 오차가 자주 발생하자 FPU를 직접 사용하지 않고
새로운 명령어 셋으로 float연산을 SSE를 통해 assign하도록 변경함.(SSE 버퍼
는 128비트.)
• 해당 명령은 movss, mulss. (자세한 설명은 생락한다..)
• 이게 뭐냐 하면.. 모든 float값은 xmmX 메모리로 갔다가 오기(sse 전
용 register) 때문에 값이 잘리거나 변형이 없어짐.(128비트에서 32비
트로 복사시 잘라서 32비트만 가져오기 떄문에 단정도 손상이 없음.)
• 그래서 초기화 값이 길든 짧든 2의 배수이므로 문제없이 복사가 이
루어짐. 무의미한 변환이 없어져서 씽크가 맞음.
13. 결론
• 모든 역사는 컴파일러에서..(퍽!)
• movss / mulss 를 쓰면 된다..(근데 어떻게 쓰라고..--;;)
• 쓰는 방법은 간단함. C++의 경우엔 VC2010부터.. GCC는 모르겠음.
• C#은… x86 컴파일러는 지원 안하고 x64는 자동으로 빌드해줌.
FLOAT은 거지같다…그냥 쓰지말자