Быстрое разделение на GCC / ARM



Насколько мне известно, большинство компиляторов делают быстрое деление путем умножения, а затем смещения бита вправо. Например, если вы проверяете этот поток SO, он говорит, что когда вы попросите компилятор Microsoft выполнить деление на 10, он умножит дивиденд на 0x1999999A (что составляет 2^32/10), а затем разделит результат на 2^32 (используя 32 сдвига вправо).



Пока все хорошо.



Как только я протестировал то же самое деление на 10 на ARM, используя GCC, компилятор сделал что-то немного разный. Сначала он умножил дивиденд на 0x66666667 (2^34/10), а затем разделил результат на 2^34. До сих пор это то же самое, что и Microsoft, за исключением использования более высокого мультипликатора. После этого, однако, он вычитал (dividend/2^31) из результата.



Мой вопрос : Почему в версии ARM есть это дополнительное вычитание? Можете ли вы привести мне числовой пример, где без этого вычитания результат будет неправильным?



Если вы хотите проверить сгенерированный код, то это ниже (с моими комментариями):



        ldr     r2, [r7, #4] @--this loads the dividend from memory into r2
movw r3, #:lower16:1717986919 @--moves the lower 16 bits of the constant
movt r3, #:upper16:1717986919 @--moves the upper 16 bits of the constant
smull r1, r3, r3, r2 @--multiply long, put lower 32 bits in r1, higher 32 in r3
asr r1, r3, #2 @--r3>>2, then store in r1 (effectively >>34, since r3 was higher 32 bits of multiplication)
asr r3, r2, #31 @--dividend>>31, then store in r3
rsb r3, r3, r1 @--r1 - r3, store in r3
str r3, [r7, #0] @--this stores the result in memory (from r3)
600   2  

2 ответов:

После этого, однако, он вычитал (dividend/2^31) из результата.

На самом деле, он вычитает dividend >> 31, что является -1 для отрицательного dividend и 0 для неотрицательного дивиденда, когда смещение вправо отрицательных целых чисел является арифметическим сдвигом вправо (и int имеет ширину 32 бита).

0x6666667 = (2^34 + 6)/10

Поэтому для x < 0 мы имеем, записывая x = 10*k + r С -10 < r <= 0,

0x66666667 * (10*k+r) = (2^34+6)*k + (2^34 + 6)*r/10 = 2^34*k + 6*k + (2^34+6)*r/10

Теперь арифметическое правое смещение отрицательных целых чисел дает пол v / 2^n, так что

(0x66666667 * x) >> 34

Результаты в

k + floor((6*k + (2^34+6)*r/10) / 2^34)

Поэтому мы должны видеть, что

-2^34 < 6*k + (2^34+6)*r/10 < 0

Правильное неравенство легко, оба k и r неположительны, и не оба равны 0.

Для левого неравенства требуется немного больше анализа.

r >= -9
Таким образом, абсолютное значение (2^34+6)*r/10 не более 2^34+6 - (2^34+6)/10.
|k| <= 2^31/10,

Так что |6*k| <= 3*2^31/5.

И остается проверить, что

6 + 3*2^31/5 < (2^34+6)/10
1288490194   < 1717986919

Да, верно.

x SAR 31 является 0xffffffff (-1) для отрицательных значений x и 0x00000000 для положительных значений.

Таким образом, rsbвычитает -1 из результата (что равнозначно сложению 1), Если дивиденд был отрицательным.

Предположим, что ваш дивиденд равен -60. С помощью простого умножения и сдвига вы получите результат -7, поэтому он вычитает -1, чтобы получить ожидаемый результат -6.

Comments

    Ничего не найдено.