| Autor: | Wojciech Muła |
|---|---|
| Dodany: | 23.10.2002 |
Contents
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