8

Глядя на сборку x86, созданную компилятором, я заметил, что (беззнаковые) целые деления иногда реализуются как целочисленные умножения. Эти оптимизации, кажется, следует формеВыполнение целочисленного деления с использованием умножения

value/n => (value * ((0xFFFFFFFF/n) + 1))/0x100000000 

Например, выполняя деление на 9:

12345678/9 = (12345678 * 0x1C71C71D)/0x100000000 

Деление на 3 будет использовать умножение 0x55555555 + 1, и так далее.

Используя тот факт, что инструкция mul хранит большую часть результата в регистре edx, конечный результат деления можно получить, используя одно умножение с магическим значением. (Хотя эта оптимизация иногда используется в сочетании с побитным сдвигом в конце.)

Мне хотелось бы получить некоторое представление о том, как это работает. Когда этот подход действителен? Почему нужно добавить к нашему «магическому числу»?

+2

Постоянная, которую вы умножаете на, является приближением обратной. Случайные +/- 1 здесь и там должны убедиться, что он всегда «закруглен» правильно. Доказательство правильности конкретного метода может быть сделано либо математически, либо методом грубой силы всех числителей. (Для 32-битных, это вполне возможно.) – Mysticial

+0

@Mysticial: Это похоже на ответ. –

+0

@ScottHunter Возможно, позже, когда я уйду от работы. У меня нет достаточно инструментов, чтобы дать исчерпывающий ответ. – Mysticial

ответ

10

Этот метод называется «разделение инвариантным умножением».

Константы, которые вы видите, фактически являются приблизительными к взаимному.

Таким образом, вместо вычисления:

N/D = Q 

вы сделать что-то вроде этого, вместо:

N * (1/D) = Q 

где 1/D является возвратным, который может быть предвычисленными.

В принципе, обратные значения являются неточными, если D - это сила двух. Таким образом, будет какая-то ошибка округления. +1, который вы видите, предназначен для исправления ошибки округления.


Наиболее распространенным примером является деление на 3:

N/3 = (N * 0xaaaaaaab) >> 33 

Где 0xaaaaaaab = 2^33/3 + 1.

Этот подход будет обобщен на другие делители.

+1

Каноническая ссылка: T. Granlund и PL Montgomery,« Разделение на инвариантные целые числа с использованием умножения », в * Материалы конференции SIGPLAN '94 по программированию языка программирования и Реализация *, 1994, стр. 61-72. – njuffa

+1

Дополнительная, более поздняя ссылка: Н. Мёллер и Т. Гранлунд, «Улучшенное деление на инвариантные целые числа», * IEEE Transactions on Computers *, vol. 60, no. 2, pp. 165 -175, февраль 2011. – njuffa

+0

Ваше обобщение и доказательство неверны. Также 0x55555556 для деления на 3 работает только для подписанного диапазона, т.е. до 2^31. – Jester

Смежные вопросы