Średnia arytmetyczna

Autor: Wojciech Muła
Dodany:2002

Treść

Średnia arytmetyczna liczona jest według wzoru (nr 1):

av1 = (a+b)/2

W przypadku operacji całkowitoliczbowych dzielenie przez 2 jest równoważne przesunięciu bitowemu w prawo o jedną pozycję.

W takich przypadkach problemem może być przekraczanie zakresu podczas dodawania. Można albo rozszerzyć zakres liczb, albo zastosować troszkę inny wzór — wymaga więcej dzielenia, ale na pewno nie spowoduje przekroczenia zakresu.

Można się przed tym zabezpieczyć, stosując inny wzór (nr 2):

la = LSB(a)
lb = LSB(b)

av2 = (a/2 + b/2) + (la+lb)/2

Z kolei (la+lb)/2 może zostać zapisane jako (la & lb), co upraszcza nieco sprawę. Można jeszcze bardziej uprościć wzór na av2, pomijając ten czynnik — nie spowoduje to zauważalnych gołym okiem różnic.

Rozszerzanie zakresu (x86)

; wejście: al, bl
; wyjście: al = (al+bl)/2
: niszczy: ah, bh

xor ah, ah
xor bh, bh
add ax, bx   ; a+b
shr ax, 1    ; ax/2

Bez rozszerzania zakresu (x86)

Wykorzystano fakt, że wynik dodawania i odejmowanie jest o jeden bit szerszy niż operandy — dodatkowym bitem wyniku jest CF (bit przeniesienia).

; wejście: al, bl
; wyjście: al = (al+bl)/2

add al, bl   ; (CF:al) = (0:al) + (0:bl)
rcr al, 1    ; podzielenie (CF:al)/2

Wzór 2 (x86, równoległe obliczenia)

; wejście: eax, ebx (zawartość każdego rejestru traktowana
;                    jako wektor 4 bajtów)
; wyjście: eax

mov ecx, eax        ; ecx = la
mov edx, ebx        ; edx = lb

and ecx, edx        ;
and ecx, 0x01010101 ; ecx = la & lb

; -- zaznaczona część wystarczy do zaimplementowania
;    uproszczonego wzoru na av2
        shr eax, 1          ;
        shr ebx, 1          ;
        and eax, 0x7f7f7f7f ; a>>1
        and ebx, 0x7f7f7f7f ; b>>1

        add eax, ebx        ; eax = (a>>1 + b>>1)

add eax, ecx        ; eax += (la&lb)

Obliczania MMX

Niestety, Intel w pierwszej wersji MMX nie wpadł na pomysł rozkazu pavgub.

; wejście: mm0, mm1 - packed bytes
; wyjście: mm3
; niszczy: mm2, mm3

segment .data

mmx_MSB dd 0x80808080, 0x80808080

segment .text

movq    mm3, [mmx_MSB]

movq    mm2, mm0
paddb   mm0, mm1 ; dodaj "normalnie"
paddusb mm2, mm1 ; i z nasyceniem

pcmpeqb mm2, mm0 ; tam gdzie wystąpiło nasycenie bajt jest zerowany,
                 ; w przeciwnym przypadku równy 0xff
pand    mm2, mm3 ; zostaw tylko najstarsze bity — ósme (licząc od zera)
                 ; bity wyniku

psrlq   mm0, 1
pandn   mm3, mm0 ; mm3 = mm0/2

por     mm3, mm2 ; uzupełnij wynik ósmym bitem (w wyniku — siódmym)

Wzór 2 (MMX)

; wejście: mm0, mm1 = packed bytes
; wyjście: mm0
; niszczy: mm2, mm3

segment .data

mmx_LSB     dd 0x01010101,0x01010101
mmx_7lowest dd 0x7f7f7f7f,0x7f7f7f7f

segment .text

movq  mm2, mm0
movq  mm3, mm1

psrlq mm0, 1             ;
psrlq mm1, 1             ;

pand  mm0, [mmx_7lowest] ;
pand  mm1, [mmx_7lowest] ;
paddb mm0, mm1           ; mm0 = mm0/2 + mm1/2


pand  mm2, mm3           ;
pand  mm2, [mmx_LSB]     ;
paddb mm0, mm2           ; mm0 += LSB(mm0) & LSB(mm1)