是的,还有另一种方式,最初是由 Terje Mathiesen 发明的(至少是 AFAIK)。你(有点)乘以倒数,而不是除以 10。当然,诀窍在于整数不能直接表示倒数。为了弥补这一点,您使用缩放整数。如果我们有浮点,我们可以提取数字,例如:
input = 123
first digit = integer(10 * (fraction(input * .1))
second digit = integer(100 * (fraction(input * .01))
...依此类推,根据需要获得尽可能多的数字。为了用整数做到这一点,我们基本上只是将它们缩放 2 32(并将每个向上四舍五入,因为我们将使用截断数学)。在 C 中,算法如下所示:
#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;
just 表示我们使用高 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 字节数据为代价简化了代码。消除它也很容易,但我暂时没有打扰。
在任何情况下,取消分区可以在相当多的缺乏专用分区硬件的中低端处理器上更快地实现这一点。