Мы используем 64-битный процессор
Какой 64-битный процессор?
В общем случае, если вы умножаете число с N битами на другое число, которое имеетM битов, результат будет иметь до N + M битов.Для целочисленного деления это аналогично - если число с N битами делится на число с M битами, результат будет иметь N-M + 1 бит.
Поскольку умножение естественным образом «расширяется» (результат имеет больше цифрчем любое из исходных чисел), а целочисленное деление естественно «сужается» (результат имеет меньше цифр);некоторые процессоры поддерживают «умножение с расширением» и «деление сужения».
Другими словами, некоторые 64-разрядные процессоры поддерживают деление 128-разрядного числа на 64-разрядное число, чтобы получить 64-разрядный результат.Например, в 80x86 это одна инструкция DIV
.
К сожалению, C не поддерживает "умножение с расширением" или "сужение деления".Он поддерживает только «результат того же размера, что и исходные операнды».
По иронии судьбы (для 64-разрядных делителей без знака на 64-разрядных 80x86) другого выбора нет, и компилятор должен использовать инструкцию DIV
, которая будетразделите 128-битное число на 64-битное число.Это означает, что язык C вынуждает вас использовать 64-битный числитель, затем код, сгенерированный компилятором, расширяет ваш 64-битный числитель до 128 бит и делит его на 64-битное число, чтобы получить 64-битный результат;а затем вы пишете дополнительный код, чтобы обойти тот факт, что язык не позволил вам использовать 128-битный числитель для начала.
Надеемся, вы сможете увидеть, как эту ситуацию можно считать "не идеальной".
Что бы я хотел, это способ заставить компилятор поддерживать "сужение деления".Например, возможно, злоупотребляя приведениями и надеясь, что оптимизатор достаточно умен, например:
__uint128_t numerator = (__uint128_t)1 << 64;
if(n > 1) {
return (uint64_t)(numerator/n);
}
Я проверил это для последних версий GCC, CLANG и ICC (используя https://godbolt.org/)и обнаружил, что (для 64-битных 80x86) ни один из компиляторов не достаточно умен, чтобы понять, что единственная инструкция DIV
- это все, что нужно (все они сгенерировали код, который выполняет call __udivti3
, что является дорогой функцией для получения128-битный результат).Компиляторы будут использовать DIV
только тогда, когда (128-битный) числитель равен 64 битам (и ему будет предшествовать XOR RDX,RDX
, чтобы установить наибольшую половину 128-битного числителя в нули).
Другими словами, вполне вероятно, что единственный способ получить идеальный код (сама инструкция DIV
на 64-битной 80x86) - прибегнуть к встроенной сборке.
Например, лучший код, который вы 'Вы получите без встроенной сборки (из ответа Нейта Элдриджа):
mov rax, rdi
xor edx, edx
neg rax
div rdi
add rax, 1
ret
... и лучший возможный код:
mov edx, 1
xor rax, rax
div rdi
ret