Podzielić przez 10 używając przesunięć bitowych?
Czy możliwe jest podzielenie liczby całkowitej bez znaku przez 10, używając czystych przesunięć bitowych, dodawania, odejmowania i może mnożyć? Korzystanie z procesora o bardzo ograniczonych zasobach i powolnym dzieleniu.
6 answers
Oto, co robi kompilator Microsoft, kompilując podziały przez małe stałe całkowe. Załóżmy, że maszyna 32-bitowa (kod można odpowiednio dostosować):
int32_t div10(int32_t dividend)
{
int64_t invDivisor = 0x1999999A;
return (int32_t) ((invDivisor * dividend) >> 32);
}
Chodzi o to, że mnożymy przez bliskie przybliżenie 1/10 * 2^32, a następnie usuwamy 2^32. Podejście to można dostosować do różnych dzielników i różnych szerokości bitów.
To działa świetnie dla architektury ia32, ponieważ jego instrukcja IMUL umieści 64-bitowy produkt w edx:eax, a wartość edx będzie pożądaną wartością. Viz (zakładając, że dywidenda jest przekazywana w eax i iloraz zwracany w eax)
div10 proc
mov edx,1999999Ah ; load 1/10 * 2^32
imul eax ; edx:eax = dividend / 10 * 2 ^32
mov eax,edx ; eax = dividend / 10
ret
endp
Nawet na maszynie z instrukcją powolnego mnożenia, będzie to szybsze niż podział oprogramowania.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-17 10:44:53
Choć odpowiedzi udzielone do tej pory pasują do rzeczywistego pytania, nie pasują do tytułu. Oto rozwiązanie mocno inspirowane Hacker ' s Delight , które naprawdę wykorzystuje tylko przesunięcia bitów.
unsigned divu10(unsigned n) {
unsigned q, r;
q = (n >> 1) + (n >> 2);
q = q + (q >> 4);
q = q + (q >> 8);
q = q + (q >> 16);
q = q >> 3;
r = n - (((q << 2) + q) << 1);
return q + (r > 9);
}
Myślę, że jest to najlepsze rozwiązanie dla architektur, w których brakuje instrukcji mnożenia.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-14 22:38:30
Oczywiście, że możesz, jeśli możesz żyć z pewną stratą w precyzji. Jeśli znasz zakres wartości wejściowych, możesz wymyślić bitowe przesunięcie i mnożenie, które jest dokładne. Kilka przykładów jak można podzielić przez 10, 60, ... jak to jest opisane w tym blogu, aby sformatować Czas najszybszy sposób możliwe.
temp = (ms * 205) >> 11; // 205/2048 is nearly the same as /10
Twój, Alois Kraus
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-05 21:12:48
Biorąc pod uwagę odpowiedź Kuby Obera, jest jeszcze jedna w tym samym duchu. Wykorzystuje iteracyjne przybliżenie wyniku, ale nie spodziewałbym się żadnych zaskakujących występów.
Powiedzmy, że musimy znaleźć x
Gdzie x = v / 10
.
Użyjemy operacji odwrotnej v = x * 10
, ponieważ ma ona właściwość nice, że gdy x = a + b
, to x * 10 = a * 10 + b * 10
.
Użyjmy x
jako zmiennej posiadającej jak dotąd najlepsze przybliżenie wyniku. Po zakończeniu wyszukiwania x
zatrzyma wynik. Będziemy ustawia każdy bit b
z x
od najbardziej znaczącego do mniej znaczącego, jeden po drugim porównuje koniec (x + b) * 10
z v
. Jeśli jest mniejszy lub równy v
, to bit b
jest ustawiony w x
. Aby przetestować następny bit, po prostu przesuniemy b o jedną pozycję w prawo (podziel przez dwa).
Możemy uniknąć mnożenia przez 10 trzymając x * 10
i b * 10
w innych zmiennych.
Daje to następujący algorytm dzielenia v
przez 10.
uin16_t x = 0, x10 = 0, b = 0x1000, b10 = 0xA000;
while (b != 0) {
uint16_t t = x10 + b10;
if (t <= v) {
x10 = t;
x |= b;
}
b10 >>= 1;
b >>= 1;
}
// x = v / 10
Edit: aby uzyskać algorytm Kuby Obera, który unika potrzeby zmiennej x10
, możemy zamiast tego odjąć b10
od v
i v10
. W tym przypadku {[19] }nie jest już potrzebny. Algorytm staje się
uin16_t x = 0, b = 0x1000, b10 = 0xA000;
while (b != 0) {
if (b10 <= v) {
v -= b10;
x |= b;
}
b10 >>= 1;
b >>= 1;
}
// x = v / 10
Pętla może być rozwinięta, a różne wartości b
i b10
mogą być wstępnie obliczone jako stałe.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-07 02:48:46
Cóż dzielenie to odejmowanie, więc tak. Przesuń w prawo o 1 (Podziel przez 2). Teraz odejmij 5 od wyniku, licząc liczbę razy, które wykonujesz odejmowanie, aż wartość będzie mniejsza niż 5. Wynikiem jest liczba wykonanych odejmowań. A podział pewnie będzie szybszy.
Hybrydowa strategia przesunięcia w prawo, a następnie podziel przez 5 przy użyciu normalnego podziału może dać ci poprawę wydajności, jeśli logika w dzielniku jeszcze tego nie robi.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-05 21:12:46
Na architekturach, które mogą przesuwać się tylko o jedno miejsce na raz, seria wyraźnych porównań ze zmniejszającymi się potęgami dwóch pomnożonych przez 10 może działać lepiej niż rozwiązanie z hacker ' s delight. Przy założeniu dywidendy 16 bitowej:
uint16_t div10(uint16_t dividend) {
uint16_t quotient = 0;
#define div10_step(n) \
do { if (dividend >= (n*10)) { quotient += n; dividend -= n*10; } } while (0)
div10_step(0x1000);
div10_step(0x0800);
div10_step(0x0400);
div10_step(0x0200);
div10_step(0x0100);
div10_step(0x0080);
div10_step(0x0040);
div10_step(0x0020);
div10_step(0x0010);
div10_step(0x0008);
div10_step(0x0004);
div10_step(0x0002);
div10_step(0x0001);
#undef div10_step
if (dividend >= 5) ++quotient; // round the result (optional)
return quotient;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-07 02:48:15