Autor: | Wojciech Muła |
---|---|
Dodany: | 23.10.2002 |
Treść
W procesorach Pentium nie ma rozkazu porównywania liczb zmiennoprzecinkowych, takiego który bezpośrednio modyfikował rejestr flag. Dopiero w Pentium Pro wprowadzono rozkaz fcom.
Poniżej przedstawię sposób porównywania liczb floating point tylko za pomocą rozkazów stałoprzecinkowych.
Liczba zmiennoprzecinkowa o pojedynczej precyzji (32-bitowa) ma następującą strukturę:
31 30 23 0 +-+--------+-----------------------+ |S|EXP+bias| mantissa | +-+--------+-----------------------+
Wykładnik nie jest zapisywany jako liczba ujemna, tylko liczba z przesunięciem (ang. bias) i dla liczb typu single przesunięcie wynosi 127, tak więc w NKB zawsze jest to liczba dodatnia.
Normalizacja mantysy polega na doprowadzeniu liczby zmiennoprzecinkowej do postaci 1.bity po przecinku, wobec czego część całkowita jest znana i nie zapisuje się jej.
Wartość liczby obliczana jest zgodnie ze wzorem:
− 1S ⋅ (1 + mantissa) ⋅ 2exponent − bias
Ponieważ za znak odpowiada 1 bit wyznaczenie modułu lub wartości przeciwnej jest banalne:
; eax - liczba zmiennoprzecinkowa (32 bity) and eax, 7fffffffh ; moduł xor eax, 80000000h ; wartość przeciwna
Jeśli policzyć wartość dwusłowa, zawierającego liczbę typu float, jako liczbę zapisaną w naturalnym kodzie binarnym (NKB), to jej wartość wyniesie:
(S shl 31) or ((EXP+Bias) shl 23) or mantissa
Jak widać wartościowanie pól EXP+Bias i mantissa jest takie samo jak we wzorze na wartość liczby floating-point (większą wagę ma wykładnik). Jedynie bit znaku S ma niewłaściwą (zbyt dużą) wagę — wartość w NKB liczby ujemnej będzie zawsze większa od liczby dodatniej.
Z tego powodu trzeba rozpatrzeć trzy przypadki:
int compare_fp(float A, float B) { // liczby binarne dword a = (dword*)&A; dword b = (dword*)&B; // znaki liczb dword sign_a = a & 0x80000000; dword sign_b = b & 0x80000000; // obie dodatnie if ((sign_a == 0) && (sign_b == 0)) return a > b; // obie ujemne if ((sign_a != 0) && (sign_b != 0)) return b > a; // różne znaki a ^= 0x80000000; b ^= 0x80000000; return a > b; }
; wejście: eax - a ; ebx - b ; wyjście: znaczniki flag ustawione zgodnie z warunkiem float(eax) & float(ebx) compare_fp: mov ecx, eax mov edx, ebx and ecx, 0x80000000 ; ecx := sign_a and edx, 0x80000000 ; edx := sign_b ; jeśli obie liczby ujemne zamień ich kolejność ; cond1 := (float(eax) < 0 and float(ebx) < 0) push ecx push edx and ecx, edx ; sar ecx, 31 ; ecx = cond1 ? 0xffffffff : 0x00000000 mov edx, ebx ; sub edx, eax ; and edx, ecx ; edx = cond1 ? ebx - eax : 0x00000000 add eax, edx ; eax = cond1 ? eax+ebx-eax : eax sub ebx, edx ; ebx = cond1 ? ebx-ebx+eax : ebx ; jeśli liczby są różnych znaków zaneguj bity znaku ; cond2 := (float(eax) < 0 xor float(ebx) < 0) pop ecx pop edx xor ecx, edx ; ecx = cond2 ? 0x80000000 : 0x00000000 xor eax, ecx xor edx, ecx ; na końcu porównaj cmp eax, ebx ret