W zasadzie zanim przejd臋 dalej, gritzy za j00ru, kt贸ry razem ze mn膮 kombinowa艂 jak to inaczej zapisa膰 ;>
Problem by艂 prosty: zapisa膰 za pomoc膮 dzia艂a艅 matematycznych poni偶sz膮, sk艂adaj膮c膮 si臋 z dw贸ch inline if'贸w, linijk臋 kodu (osoby zwi膮zane z grafik膮 komputerow膮 od strony programowania, pewnie nie raz widzia艂y tak膮 czy podobn膮 lini臋 kodu):
r = r > 255 ? 255 : r < 0 ? 0 : r; // int r, CPU = x86, sizeof(int) = 4Kod ten dzia艂a w taki oto spos贸b:
Je偶eli r jest wi臋ksze od 255, r = 255
Je偶eli r jest mniejsze od 0, r = 0
Je偶eli r jest z przedzia艂u domkni臋tego [0,255], r ma pozosta膰 bez zmianZanim przejd臋 dalej, wa偶na uwaga - we wszystkich poni偶szych operacjach 'r' traktuje jak unsigned int - czyli w razie czego nale偶y dokona膰 cast z int na unsigned int (nie chce aby kompilator korzysta艂 z instrukcji dope艂niaj膮cych najbardziej znacz膮cy bit przy niekt贸rych operacjach).
Zacznijmy od cz臋艣ci 'je偶eli r jest mniejsze od 0, r = 0' - czyli, dla ka偶dej liczby ujemnej, r ma by膰 r贸wne 0. Jako 偶e liczba 0 jest do艣膰 charakterystyczna (wszystkie bity ustawione na 0), mo偶emy spr贸bowa膰 zrobi膰 mno偶enie lub AND bitowe na r liczby 0 je偶eli r jest ujemne, lub 0xff (255 = 0xff, 0xff = 1111 1111, 8 bit贸w bo do zapisania 255 i tak wi臋cej nam nie potrzeba) je艣li r jest dodatnie, czyli np.:
r = r & (r < 0 ? 0 : 0xff)Oczywi艣cie ten inline if tam nas nie zadowala, wi臋c postarajmy si臋 go zast膮pi膰 czym艣 innym.
Jak wiadomo, ka偶da liczba ujemna zapisana w systemie U2 (z kt贸rego korzysta x86 w przypadku int贸w) b臋dzie mia艂a najbardziej znacz膮cy bit (tj. 31 bit, licz膮c od 0) zapalony. Korzystaj膮c z tego, mo偶emy napisa膰:
(r >> 31)Po tej operacji, r b臋dzie r贸wne 0 je偶eli liczba by艂a dodatnia, a 1 je偶eli by艂a ujemna. Jednak my chcemy uzyska膰 0 dla ujemnych, a 0xff dla dodatnich. Mo偶emy to zrobi膰 po prostu odejmuj膮c 1.
(r >> 31) - 1)Teraz, dla liczb dodatnich, b臋dzie to (0 - 1) czyli -1, a wi臋c w systemie U2 0xffffffff, a dla liczb ujemnych, b臋dzie to (1 - 1), czyli 0. Oczywi艣cie teraz wystarczy zrobi膰 AND bitowe z 0xff, i dostajemy 0xff dla dodatnich, i 0 dla ujemnych, czyli dok艂adnie to co zwraca (r < 0 ? 0 : 0xff). Mo偶emy wi臋c napisa膰:
r = r & (((r >> 31) - 1) & 0xff)OK, dolna cz臋艣膰 liczb za艂atwiona, teraz czas na pozosta艂e dwa warunki, czyli 'dla liczb > 255, zwr贸膰 255, a dla liczb mniejszych od 255, zwr贸膰 dan膮 liczb臋'. Ca艂o艣膰 mo偶emy sprowadzi膰 do instrukcji OR bitowego z liczb膮 0xff je偶eli liczba jest wi臋ksza od 255, lub 0 je偶eli liczba jest mniejsza, i do AND bitowego z 0xff.
r = (r | (r > 255 ? 0xff : 0)) & 0xffJe偶eli r b臋dzie wi臋ksze ni偶 255, to zostanie wykonany OR bitowy z 255, czyli bity od 0 do 7 zostan膮 zapalone, a nast臋pnie AND bitowy przytnie wszystkie pozosta艂e bity, tak aby zosta艂o tylko to 1111 1111, czyli 255. Ale znowu mamy inline if'a.
Jak si臋 go pozby膰 ? Mo偶emy tutaj skorzysta膰 z faktu 偶e je偶eli liczba jest wi臋ksza ni偶 255, to na pewno b臋dzie zapalony kt贸ry艣 bit powy偶ej 7dmego (jak pisa艂em wcze艣niej, 255 to 1111 1111, czyli bity od 0 do 7 zapalone, tak wi臋c ka偶da wi臋ksza liczba b臋dzie musia艂a zapali膰 kt贸ry艣 dalszy bit, np. 8smy czy 30sty). Skorzystajmy wi臋c z negacji liczby (!) aby osi膮gn膮膰 warto艣ci 0 lub jeden, je偶eli kt贸re艣 g贸rne bity r s膮 zapalone:
!(r >> 8)Teraz, dla r > 255, dostaniemy !(liczba wi臋ksza lub r贸wna 1), czyli 0, a dla r <= 255, dostaniemy !(0), czyli 1. Ponownie skorzystamy z odejmowania aby dosta膰 wszystkie bity zapalone lub zgaszone, i ponownie u偶yjemy AND bitowego do obci臋cia bit贸w zapalonych do dolnych o艣miu.
(((!(r >> 8)) - 1) & 0xff)Po tej operacji dla dodatnich r > 255 dostajemy liczb臋 0xff, a dla dodatnich r <= 255, dostajemy 0. Czyli mo偶emy podmieni膰 powy偶szego if'a.
r = (r | (((!(r >> 8)) - 1) & 0xff)) & 0xffOczywi艣cie powy偶szy zapis nie dzia艂a za dobrze dla liczb ujemnych, kt贸re przecie偶 wszystkie maj膮 zapalony bit numer 31, wi臋c zostan膮 potraktowane jak liczby wi臋ksze od 255. Ale spokojnie mo偶emy to zignorowa膰 - druga cz臋艣膰 r贸wnania (czyli to co wyprowadzili艣my wcze艣niej) si臋 tym zajmie.
OK, a teraz 艂膮czymy obie cz臋艣ci r贸wnania - jest to do艣膰 proste - za pierwsze 'r' w pierwszym r贸wnaniu wystarczy podstawi膰 drugie r贸wnanie, dzi臋ki czemu otrzymamy:
r = (r | (((!(r >> 8)) - 1) & 0xff)) & 0xff & (((r >> 31) - 1) & 0xff)Oczywi艣cie tak na prawd臋 wystarczy jedno & 0xff, wi臋c reszty mo偶emy si臋 pozby膰 - zostawmy tylko ostatnie.
r = (r | ((!(r >> 8)) - 1)) & (((r >> 31) - 1) & 0xff)Mo偶emy jeszcze napisa膰 jaki艣 programik testowy, i zobaczy膰 偶e to na prawd臋 dzia艂a:
#include <stdio.h>
int func(unsigned int r)
{
r = (r | ((!(r >> 8)) - 1)) & (((r >> 31) - 1) & 0xff);
return r;
}
int
main(void)
{
int i;
for(i = -1024; i <= 1024; i++)
printf("%i = %i\n", i, func((unsigned int)i));
return 0;
}
Kompilacja i output:
$ g++ t1.cpp
$ ./a.out
-1024 = 0
-1023 = 0
-1022 = 0
-1021 = 0
[...]
-3 = 0
-2 = 0
-1 = 0
0 = 0
1 = 1
2 = 2
3 = 3
4 = 4
[...]
252 = 252
253 = 253
254 = 254
255 = 255
256 = 255
257 = 255
258 = 255
[...]
1020 = 255
1021 = 255
1022 = 255
1023 = 255
1024 = 255
Powy偶szy post stanowi dow贸d na to 偶e mo偶na cz臋艣膰 if贸w zapisa膰 jako operacje matematyczne, ale prawie 偶e na pewno nie nale偶y tego robi膰 ze wzgl臋du na znaczn膮 utrat臋 czytelno艣ci kodu ;>
Natomiast niew膮tpliwie jest to pouczaj膮ce do艣wiadczenie ;>
Pytanie na koniec - czy b臋dzie to rzeczywi艣cie szybsze od dw贸ch if'贸w? Hmm... mo偶e na starszych komputerach, ale teraz, nie s膮dz臋 ;> (niemniej jednak zach臋cam do test贸w)
I tyle...
P.S. dodam 偶e niekt贸re (tak, GCC) kompilatory operacje ! zapisuj膮 jako:
test eax, eax
jne L2
mov DWORD PTR [ebp-4], 0
jmp L4
L2:
mov DWORD PTR [ebp-4], -1
L4:Tak wi臋c 膰wiczenie dla zainteresowanych - jak si臋 pozby膰 ! z dzia艂a艅 ?;>


Comments:
xor ecx,ecx
inc ecx ; ecx = 1
test eax,eax
cmovnz eax,edx
cmovz eax,ecx
; eax = input
mov edx, 255
sub edx, eax
sbb ecx, ecx
and ecx, edx
add eax, ecx
i wiele innych ciekawych przykladow...
a i wykonanie ciekawe ;)
Pozdrawiam,
Ciekawy wpis, skad Gynvael bierzesz takie "szalone" pomysly? ;p
Mala ciekawostka, malo kto zdaje sobie sprawe ze popularny operator trojargumentowy w C++ moze byc uzyty jako lvalue:
(a ? b : c) = 3;
Ja wiem, co mo偶na z tym branchem przy negacji zrobi膰. W艂膮czy膰 optymalizacje ;). 呕aden szanuj膮cy si臋 kompilator nie zrobi czego艣 takiego z w艂膮czonymi optymalizacjami.
Swoj膮 drog膮 - to mo偶e by膰 szybsze ni偶 wersja z ifami - mispredicted branch potrafi zabole膰...
co nie zmienia faktu, 偶e analiza matematyczna to spora przesada i paskudne wspomnienie.
Add a comment: