Да, есть другой способ , первоначально изобретенный (по крайней мере AFAIK) Терье Матизеном.Вместо того, чтобы делить на 10, вы (вроде) умножаете на обратную величину.Хитрость, конечно, заключается в том, что в целых числах вы не можете представлять обратную величину напрямую.Чтобы восполнить это, вы работаете с масштабированными целыми числами.Если бы у нас была плавающая точка, мы могли бы извлечь цифры с помощью чего-то вроде:
input = 123
first digit = integer(10 * (fraction(input * .1))
second digit = integer(100 * (fraction(input * .01))
... и т. Д. Столько раз, сколько необходимо.Чтобы сделать это с целыми числами, мы просто масштабируем их на 2 32 (и округляем каждое вверх, так как будем использовать усеченную математику).В Си алгоритм выглядит так:
#include <stdio.h>
// here are our scaled factors
static const unsigned long long factors[] = {
3435973837, // ceil((0.1 * 2**32)<<3)
2748779070, // ceil((0.01 * 2**32)<<6)
2199023256, // etc.
3518437209,
2814749768,
2251799814,
3602879702,
2882303762,
2305843010
};
static const char shifts[] = {
3, // the shift value used for each factor above
6,
9,
13,
16,
19,
23,
26,
29
};
int main() {
unsigned input = 13754;
for (int i=8; i!=-1; i--) {
unsigned long long inter = input * factors[i];
inter >>= shifts[i];
inter &= (unsigned)-1;
inter *= 10;
inter >>= 32;
printf("%u", inter);
}
return 0;
}
Операции в цикле будут напрямую отображаться в инструкции на большинстве 32-битных процессоров.Ваша типичная инструкция умножения будет принимать 2 32-битных ввода и давать 64-битный результат, что нам здесь и нужно.Обычно это будет немного быстрее, чем инструкция деления.В типичном случае некоторые операции могут (или, по крайней мере, с некоторой тщательностью) исчезнуть на языке ассемблера.Например, там, где я сделал inter &= (unsigned)-1;
, на языке ассемблера вы обычно сможете просто использовать нижний 32-битный регистр, в котором был сохранен результат, и просто игнорировать все, что содержит верхние 32 бита.Аналогично, inter >>= 32;
означает, что мы используем значение в верхнем 32-битном регистре и игнорируем нижний 32-битный регистр.
Например, в языке ассемблера x86 это выглядит примерно так:
mov ebx, 9 ; maximum digits we can deal with.
mov esi, offset output_buffer
next_digit:
mov eax, input
mul factors[ebx*4]
mov cl, shifts[ebx]
shrd eax, edx, cl
mov edx, 10 ; overwrite edx => inter &= (unsigned)-1
mul edx
add dl, '0'
mov [esi], dl ; effectively shift right 32 bits by ignoring 32 LSBs in eax
inc esi
dec ebx
jnz next_digit
mov [esi], bl ; zero terminate the string
На данный момент я чуть-чуть обманул и написал код, предполагающий дополнительный элемент в начале каждой таблицы (factors
и shifts
).Это не является строго необходимым, но упрощает код за счет потери 8 байтов данных.С этим тоже довольно легко покончить, но на данный момент я не беспокоился.
В любом случае, отказ от деления делает это довольно быстрым на довольно низких или средних уровнях.процессоры линейки, в которых отсутствует специальное оборудование для разделения.