はい、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;
ちょうどは、上位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バイトのデータを浪費するという犠牲を払ってコードを単純化します。それを取り除くのもかなり簡単ですが、私は今のところ気にしません。
いずれにせよ、分割を廃止すると、専用の分割ハードウェアがないかなりの数のローからミッドレンジのプロセッサで、これがかなり速くなります。