4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

819.25を掛けて8192で割れば、10で割ったのに近い値が得られる説を検証!

Posted at

背景

一般に、除算命令の無いCPUで整数の除算を行うのは、重い処理になりがちである。
しかし、「10で割る」というのは、コンピュータ向けの二進数から人間向けの十進数に変換するときなど、よく行われる処理である。
2の正の整数乗での割り算は、ビットシフトで表現できるので、除算命令が無くても比較的簡単にできることが多い。
そこで、10で割った値を、愚直に除算を行うよりも簡単な処理で求めたい。

最初のアイデア

2の正の整数乗を十進数で表したとき、その1の位は必ず2・4・6・8のいずれかである。
偶数しか無いうえ、0 も無いため 10 の倍数にはならない。
しかし、1の位が「2」のときは、10で割ると「0.2」になる。
「0.2」を表現するのは難しいが、「0.25」を掛けるのであれば2ビット右シフトすることで表現できる。
ある程度大きい数を使ったほうが、誤差は少なくなるだろう。

という考察を踏まえ、819.25 / 8192 を計算してみると、0.100006103515625 となった。
0.1 のあとにある程度 0 が並んでいるので、これで10で割るのに近い値が得られるかな……?

最初のアイデアを検証

実際に「819.25を掛けて8192で割る」を行った値と、10で割った値を比較してみる。
大きい値を入力したときには誤差が出ても、小さい値の入力なら正しい値が出てくるのであれば、十分使えることが期待できる。
どのくらいの値まで耐えられるか、やってみよう。

#include <stdio.h>
#include <limits.h>

unsigned int div10(unsigned int a) {
	return (a * 819ull + (a >> 2)) >> 13;
}

int main(void) {
	for (unsigned int i = 0; ; i++) {
		if (div10(i) != i / 10) {
			printf("%u -> %u\n", i, div10(i));
			break;
		}
		if (i == UINT_MAX) break;
	}
	return 0;
}

これを実行すると、

16389 -> 1639

と出力された。
すなわち、016388 を入力したときは正しい 10 で割った商が得られたが、16389 を入力すると正しい商 1638 より 1 大きい値が出てしまった。

せめて16ビット (65535) までは耐えてほしかったな……

大きすぎる値が出るなら減らしてみよう

もともと、819.25 / 8192 の値は 0.100006103515625 であり、0.1 より少し大きい。
入力を大きくすると出力も欲しい値より大きくなるのは、このためだろう。

出力が少し大きくなるのであれば、少し引いて補正してみたらどうだろうか?
やってみよう。

この章では、前章のコードから div10 関数のみを変えたコードを実行するので、div10 関数と出力結果のみを示す。

unsigned int div10(unsigned int a) {
	return (a * 819ull + (a >> 2) - (a >> 3)) >> 13;
}
10 -> 0

引きすぎたようだ。

unsigned int div10(unsigned int a) {
	return (a * 819ull + (a >> 2) - (a >> 4)) >> 13;
}
50 -> 4

まだ引きすぎのようだ。

unsigned int div10(unsigned int a) {
	return (a * 819ull + (a >> 2) - (a >> 5)) >> 13;
}
43669 -> 4367

おっ、正しい値が得られる範囲が伸びたぞ!

unsigned int div10(unsigned int a) {
	return (a * 819ull + (a >> 2) - (a >> 5) - (a >> 6)) >> 13;
}
261629 -> 26163

正しい値が得られる範囲がさらに伸び、目標の16ビットを突破したぞ!

unsigned int div10(unsigned int a) {
	return (a * 819ull + (a >> 2) - (a >> 5) - (a >> 6) - (a >> 9) - (a >> 10)) >> 13;
}
4175869 -> 417587

さらに試すと、どうやらシフト幅を2連続で使い、その後2あけてまた2連続で使い……とすると、どんどん正しい値が得られる範囲が伸びるみたいだ!

unsigned int div10(unsigned int a) {
	return (
		a * 819ull + (a >> 2) - (a >> 5) - (a >> 6) - (a >> 9) - (a >> 10)
		- (a >> 13) - (a >> 14)
	) >> 13;
}
66650109 -> 6665011

いいぞいいぞ!

unsigned int div10(unsigned int a) {
	return (
		a * 819ull + (a >> 2) - (a >> 5) - (a >> 6) - (a >> 9) - (a >> 10)
		- (a >> 13) - (a >> 14) - (a >> 17) - (a >> 18)
	) >> 13;
}
1063780349 -> 106378035

もう少し!

unsigned int div10(unsigned int a) {
	return (
		a * 819ull + (a >> 2) - (a >> 5) - (a >> 6) - (a >> 9) - (a >> 10)
		- (a >> 13) - (a >> 14) - (a >> 17) - (a >> 18) - (a >> 21) - (a >> 22)
	) >> 13;
}

ついに、32ビット符号なし整数の全範囲で正しい値が得られるようになった!

考察と整理

この「2連続で使い、その後2あけてまた2連続で使い」という特徴的なパターンは、何を表しているのだろうか?

Interactive visualization of the floating-point format

0.1 を変換してみると、以下の結果が得られた。

要素
Sign 0
Exponent 0 1 1 1 1 0 1 1
mantissa 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 1

Sign や Exponent は大文字から始まるが、mantissa は小文字から始まる。
これは原文ママである。

これは、十進数の 0.1 は、二進数では 1.10011001100110011001101… × $2^{-4}$ と表せることを示している。
この「11001100…」というパターンが、「2連続で使い、その後2あけてまた2連続で使い」の正体であると考えられる。
さらに、十進数の 819 も、二進数で表すと 1100110011 となり、パターンに沿っていることがわかる。

ということは、今回やったのは二進数の 1100110011.01 - 0.00001100110011… を掛けて $2^{13}$ で割る、という操作だったということになる。

ところで、(a >> 2) のところだけ1ビットだけ単独で使っており、一見パターンに沿っていないように見える。
しかし、ここまでは足し算、この後は引き算である。
1ビットだけ単独で立っているところから、その下の2ビット連続で使うところを引くと、

  11010000
-       11
----------
  11001101

となり、「2ビット連続」が1セット増え、その下に新しい「1ビットだけ単独」が増えることがわかる。
2ビットの連続を引くと精度が上がったのは、このようにパターンを伸ばす効果があったからだろう。

さて、「2ビット連続」を引くことでパターンを伸ばせるなら、もっと上位からこれを用いてパターンを伸ばしてもよいだろう。
ついでに、「2ビット連続」をバラバラに書かず、まとめて処理しよう。

unsigned int div10(unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	return (
		((unsigned long long)a << 10) - (a3 << 6) - (a3 << 2) - (a3 >> 2)
		- (a3 >> 6) - (a3 >> 10) - (a3 >> 14) - (a3 >> 18) - (a3 >> 22)
	) >> 13;
}

これでも、32ビット符号なし整数の全範囲で正しい値を得ることができた。

シフトの幅を減らす

最初は、十進数でみてよさそうな性質が見えた「819.25を掛けて8192で割る」を用いたが、二進数でのパターンが見えた今では「8192で割る」にこだわらなくて良さそうである。
もう少し小さい値で割るようにしても、精度を確保できるだろうか?

たとえば、シフト幅を1バイト捨てるだけで実現できる 8 にしてみた。

unsigned int div10(unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	return (
		((unsigned long long)a << 5) - (a3 << 1) - (a3 >> 3) - (a3 >> 7)
		- (a3 >> 11) - (a3 >> 15) - (a3 >> 19) - (a3 >> 23) - (a3 >> 27)
	) >> 8;
}

これでも、32ビット符号なし整数の全範囲で正しい値を得ることができた。

シフト幅を 7 にすると、途中の値に対するシフトの幅が 4 の倍数になり、きれいである。

unsigned int div10(unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	return (
		((unsigned long long)a << 4) - a3 - (a3 >> 4) - (a3 >> 8)
		- (a3 >> 12) - (a3 >> 16) - (a3 >> 20) - (a3 >> 24) - (a3 >> 28)
	) >> 7;
}

これでも、32ビット符号なし整数の全範囲で正しい値を得ることができた。

シフト幅をさらにもう1ビット削ってみる。

unsigned int div10(unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	return (
		((unsigned long long)a << 3) - (a3 >> 1) - (a3 >> 5) - (a3 >> 9)
		- (a3 >> 13) - (a3 >> 17) - (a3 >> 21) - (a3 >> 25) - (a3 >> 29)
	) >> 6;
}
9786709 -> 978671

さすがに削りすぎたようである。

目標を16ビットに下げれば、これで十分である。

unsigned int div10(unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	return (
		((unsigned long long)a << 3) - (a3 >> 1) - (a3 >> 5) - (a3 >> 9) - (a3 >> 13)
	) >> 6;
}
120149 -> 12015

もう1ビット削ると、かなり役立たなくなってきた。
こうなると、引く項の数を増やしても、精度は伸びないようである。

unsigned int div10(unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	return (((unsigned long long)a << 2) - (a3 >> 2) - (a3 >> 6) - (a3 >> 10)) >> 5;
}
2389 -> 239

10で割った余りを求める

今回の方法で、32ビット符号なし整数を10で割った商を求めることができた。
しかし、二進数を十進数に変換する処理では、10で割った余りも欲しい。
そこで、最後の右シフトで捨てられる情報から、10で割った余りが得られないかを考えてみる。

シフト幅を8ビットにしたバージョンのプログラムを変更し、10で割った余りと「最後の右シフトで捨てられる情報」の関係を観察してみる。

#include <stdio.h>
#include <limits.h>

unsigned int div10(unsigned int *dropped, unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	unsigned long long value =
		((unsigned long long)a << 5) - (a3 << 1) - (a3 >> 3) - (a3 >> 7)
		- (a3 >> 11) - (a3 >> 15) - (a3 >> 19) - (a3 >> 23) - (a3 >> 27);
	*dropped = value & 0xff;
	return value >> 8;
}

unsigned int dropped_count[10][256];

int main(void) {
	for (unsigned int i = 0; ; i++) {
		unsigned int res, dropped;
		res = div10(&dropped, i);
		dropped_count[i % 10][dropped]++;
		if (res != i / 10) {
			printf("%u -> %u\n", i, res);
			break;
		}
		if (i == UINT_MAX) break;
	}
	for (int a = 0; a < 256; a++) {
		printf("%3d %02x", a, a);
		for (int b = 0; b < 10; b++) {
			printf("%10u", dropped_count[b][a]);
		}
		putchar('\n');
	}
	return 0;
}

以下の実行結果が得られた。

実行結果
  0 00         1         0         0         0         0         0         0         0         0         0
  1 01     68440         0         0         0         0         0         0         0         0         0
  2 02   3079000         0         0         0         0         0         0         0         0         0
  3 03  22056812         0         0         0         0         0         0         0         0         0
  4 04  52269164         0         0         0         0         0         0         0         0         0
  5 05  65706336         0         0         0         0         0         0         0         0         0
  6 06  67094688         0         0         0         0         0         0         0         0         0
  7 07  67103390         0         0         0         0         0         0         0         0         0
  8 08  66210966         0         0         0         0         0         0         0         0         0
  9 09  55320624         0         0         0         0         0         0         0         0         0
 10 0a  26102512         0         0         0         0         0         0         0         0         0
 11 0b   4351265         0         0         0         0         0         0         0         0         0
 12 0c    133504         0         0         0         0         0         0         0         0         0
 13 0d        28         0         0         0         0         0         0         0         0         0
 14 0e         0         0         0         0         0         0         0         0         0         0
 15 0f         0         0         0         0         0         0         0         0         0         0
 16 10         0         0         0         0         0         0         0         0         0         0
 17 11         0         0         0         0         0         0         0         0         0         0
 18 12         0         0         0         0         0         0         0         0         0         0
 19 13         0         0         0         0         0         0         0         0         0         0
 20 14         0         0         0         0         0         0         0         0         0         0
 21 15         0         0         0         0         0         0         0         0         0         0
 22 16         0         0         0         0         0         0         0         0         0         0
 23 17         0         0         0         0         0         0         0         0         0         0
 24 18         0         0         0         0         0         0         0         0         0         0
 25 19         0         0         0         0         0         0         0         0         0         0
 26 1a         0       211         0         0         0         0         0         0         0         0
 27 1b         0    244952         0         0         0         0         0         0         0         0
 28 1c         0   5975796         0         0         0         0         0         0         0         0
 29 1d         0  30327768         0         0         0         0         0         0         0         0
 30 1e         0  57957875         0         0         0         0         0         0         0         0
 31 1f         0  66556472         0         0         0         0         0         0         0         0
 32 20         0  67107064         0         0         0         0         0         0         0         0
 33 21         0  67076258         0         0         0         0         0         0         0         0
 34 22         0  64996010         0         0         0         0         0         0         0         0
 35 23         0  48829888         0         0         0         0         0         0         0         0
 36 24         0  18278976         0         0         0         0         0         0         0         0
 37 25         0   2112854         0         0         0         0         0         0         0         0
 38 26         0     32606         0         0         0         0         0         0         0         0
 39 27         0         0         0         0         0         0         0         0         0         0
 40 28         0         0         0         0         0         0         0         0         0         0
 41 29         0         0         0         0         0         0         0         0         0         0
 42 2a         0         0         0         0         0         0         0         0         0         0
 43 2b         0         0         0         0         0         0         0         0         0         0
 44 2c         0         0         0         0         0         0         0         0         0         0
 45 2d         0         0         0         0         0         0         0         0         0         0
 46 2e         0         0         0         0         0         0         0         0         0         0
 47 2f         0         0         0         0         0         0         0         0         0         0
 48 30         0         0         0         0         0         0         0         0         0         0
 49 31         0         0         0         0         0         0         0         0         0         0
 50 32         0         0         0         0         0         0         0         0         0         0
 51 33         0         0         0         0         0         0         0         0         0         0
 52 34         0         0     21778         0         0         0         0         0         0         0
 53 35         0         0   1728896         0         0         0         0         0         0         0
 54 36         0         0  16513408         0         0         0         0         0         0         0
 55 37         0         0  46979340         0         0         0         0         0         0         0
 56 38         0         0  64548102         0         0         0         0         0         0         0
 57 39         0         0  67061144         0         0         0         0         0         0         0
 58 3a         0         0  67107912         0         0         0         0         0         0         0
 59 3b         0         0  66682584         0         0         0         0         0         0         0
 60 3c         0         0  59119013         0         0         0         0         0         0         0
 61 3d         0         0  32476656         0         0         0         0         0         0         0
 62 3e         0         0   6932284         0         0         0         0         0         0         0
 63 3f         0         0    325144         0         0         0         0         0         0         0
 64 40         0         0       469         0         0         0         0         0         0         0
 65 41         0         0         0         0         0         0         0         0         0         0
 66 42         0         0         0         0         0         0         0         0         0         0
 67 43         0         0         0         0         0         0         0         0         0         0
 68 44         0         0         0         0         0         0         0         0         0         0
 69 45         0         0         0         0         0         0         0         0         0         0
 70 46         0         0         0         0         0         0         0         0         0         0
 71 47         0         0         0         0         0         0         0         0         0         0
 72 48         0         0         0         0         0         0         0         0         0         0
 73 49         0         0         0         0         0         0         0         0         0         0
 74 4a         0         0         0         0         0         0         0         0         0         0
 75 4b         0         0         0         0         0         0         0         0         0         0
 76 4c         0         0         0         0         0         0         0         0         0         0
 77 4d         0         0         0         7         0         0         0         0         0         0
 78 4e         0         0         0     96384         0         0         0         0         0         0
 79 4f         0         0         0   3673832         0         0         0         0         0         0
 80 50         0         0         0  24051344         0         0         0         0         0         0
 81 51         0         0         0  53845556         0         0         0         0         0         0
 82 52         0         0         0  65981338         0         0         0         0         0         0
 83 53         0         0         0  67099904         0         0         0         0         0         0
 84 54         0         0         0  67099904         0         0         0         0         0         0
 85 55         0         0         0  65981338         0         0         0         0         0         0
 86 56         0         0         0  53845556         0         0         0         0         0         0
 87 57         0         0         0  24051344         0         0         0         0         0         0
 88 58         0         0         0   3673832         0         0         0         0         0         0
 89 59         0         0         0     96384         0         0         0         0         0         0
 90 5a         0         0         0         7         0         0         0         0         0         0
 91 5b         0         0         0         0         0         0         0         0         0         0
 92 5c         0         0         0         0         0         0         0         0         0         0
 93 5d         0         0         0         0         0         0         0         0         0         0
 94 5e         0         0         0         0         0         0         0         0         0         0
 95 5f         0         0         0         0         0         0         0         0         0         0
 96 60         0         0         0         0         0         0         0         0         0         0
 97 61         0         0         0         0         0         0         0         0         0         0
 98 62         0         0         0         0         0         0         0         0         0         0
 99 63         0         0         0         0         0         0         0         0         0         0
100 64         0         0         0         0         0         0         0         0         0         0
101 65         0         0         0         0         0         0         0         0         0         0
102 66         0         0         0         0         0         0         0         0         0         0
103 67         0         0         0         0      5474         0         0         0         0         0
104 68         0         0         0         0    897898         0         0         0         0         0
105 69         0         0         0         0  11788240         0         0         0         0         0
106 6a         0         0         0         0  41006352         0         0         0         0         0
107 6b         0         0         0         0  62757599         0         0         0         0         0
108 6c         0         0         0         0  66975360         0         0         0         0         0
109 6d         0         0         0         0  67108752         0         0         0         0         0
110 6e         0         0         0         0  66926744         0         0         0         0         0
111 6f         0         0         0         0  61991961         0         0         0         0         0
112 70         0         0         0         0  38910104         0         0         0         0         0
113 71         0         0         0         0  10417072         0         0         0         0         0
114 72         0         0         0         0    707960         0         0         0         0         0
115 73         0         0         0         0      3214         0         0         0         0         0
116 74         0         0         0         0         0         0         0         0         0         0
117 75         0         0         0         0         0         0         0         0         0         0
118 76         0         0         0         0         0         0         0         0         0         0
119 77         0         0         0         0         0         0         0         0         0         0
120 78         0         0         0         0         0         0         0         0         0         0
121 79         0         0         0         0         0         0         0         0         0         0
122 7a         0         0         0         0         0         0         0         0         0         0
123 7b         0         0         0         0         0         0         0         0         0         0
124 7c         0         0         0         0         0         0         0         0         0         0
125 7d         0         0         0         0         0         0         0         0         0         0
126 7e         0         0         0         0         0         0         0         0         0         0
127 7f         0         0         0         0         0         0         0         0         0         0
128 80         0         0         0         0         0         0         0         0         0         0
129 81         0         0         0         0         0     32606         0         0         0         0
130 82         0         0         0         0         0   2112854         0         0         0         0
131 83         0         0         0         0         0  18278976         0         0         0         0
132 84         0         0         0         0         0  48829888         0         0         0         0
133 85         0         0         0         0         0  64996010         0         0         0         0
134 86         0         0         0         0         0  67076258         0         0         0         0
135 87         0         0         0         0         0  67107064         0         0         0         0
136 88         0         0         0         0         0  66556472         0         0         0         0
137 89         0         0         0         0         0  57957875         0         0         0         0
138 8a         0         0         0         0         0  30327768         0         0         0         0
139 8b         0         0         0         0         0   5975796         0         0         0         0
140 8c         0         0         0         0         0    244952         0         0         0         0
141 8d         0         0         0         0         0       211         0         0         0         0
142 8e         0         0         0         0         0         0         0         0         0         0
143 8f         0         0         0         0         0         0         0         0         0         0
144 90         0         0         0         0         0         0         0         0         0         0
145 91         0         0         0         0         0         0         0         0         0         0
146 92         0         0         0         0         0         0         0         0         0         0
147 93         0         0         0         0         0         0         0         0         0         0
148 94         0         0         0         0         0         0         0         0         0         0
149 95         0         0         0         0         0         0         0         0         0         0
150 96         0         0         0         0         0         0         0         0         0         0
151 97         0         0         0         0         0         0         0         0         0         0
152 98         0         0         0         0         0         0         0         0         0         0
153 99         0         0         0         0         0         0         0         0         0         0
154 9a         0         0         0         0         0         0       952         0         0         0
155 9b         0         0         0         0         0         0    426280         0         0         0
156 9c         0         0         0         0         0         0   7989851         0         0         0
157 9d         0         0         0         0         0         0  34632208         0         0         0
158 9e         0         0         0         0         0         0  60176580         0         0         0
159 9f         0         0         0         0         0         0  66783720         0         0         0
160 a0         0         0         0         0         0         0  67108394         0         0         0
161 a1         0         0         0         0         0         0  67040424         0         0         0
162 a2         0         0         0         0         0         0  64029864         0         0         0
163 a3         0         0         0         0         0         0  45052052         0         0         0
164 a4         0         0         0         0         0         0  14839700         0         0         0
165 a5         0         0         0         0         0         0   1402528         0         0         0
166 a6         0         0         0         0         0         0     14176         0         0         0
167 a7         0         0         0         0         0         0         0         0         0         0
168 a8         0         0         0         0         0         0         0         0         0         0
169 a9         0         0         0         0         0         0         0         0         0         0
170 aa         0         0         0         0         0         0         0         0         0         0
171 ab         0         0         0         0         0         0         0         0         0         0
172 ac         0         0         0         0         0         0         0         0         0         0
173 ad         0         0         0         0         0         0         0         0         0         0
174 ae         0         0         0         0         0         0         0         0         0         0
175 af         0         0         0         0         0         0         0         0         0         0
176 b0         0         0         0         0         0         0         0         0         0         0
177 b1         0         0         0         0         0         0         0         0         0         0
178 b2         0         0         0         0         0         0         0         0         0         0
179 b3         0         0         0         0         0         0         0         0         0         0
180 b4         0         0         0         0         0         0         0      8960         0         0
181 b5         0         0         0         0         0         0         0   1127526         0         0
182 b6         0         0         0         0         0         0         0  13263308         0         0
183 b7         0         0         0         0         0         0         0  43057520         0         0
184 b8         0         0         0         0         0         0         0  63435032         0         0
185 b9         0         0         0         0         0         0         0  67012480         0         0
186 ba         0         0         0         0         0         0         0  67108646         0         0
187 bb         0         0         0         0         0         0         0  66863912         0         0
188 bc         0         0         0         0         0         0         0  61133068         0         0
189 bd         0         0         0         0         0         0         0  36781096         0         0
190 be         0         0         0         0         0         0         0   9150989         0         0
191 bf         0         0         0         0         0         0         0    552392         0         0
192 c0         0         0         0         0         0         0         0      1800         0         0
193 c1         0         0         0         0         0         0         0         0         0         0
194 c2         0         0         0         0         0         0         0         0         0         0
195 c3         0         0         0         0         0         0         0         0         0         0
196 c4         0         0         0         0         0         0         0         0         0         0
197 c5         0         0         0         0         0         0         0         0         0         0
198 c6         0         0         0         0         0         0         0         0         0         0
199 c7         0         0         0         0         0         0         0         0         0         0
200 c8         0         0         0         0         0         0         0         0         0         0
201 c9         0         0         0         0         0         0         0         0         0         0
202 ca         0         0         0         0         0         0         0         0         0         0
203 cb         0         0         0         0         0         0         0         0         0         0
204 cc         0         0         0         0         0         0         0         0         0         0
205 cd         0         0         0         0         0         0         0         0        84         0
206 ce         0         0         0         0         0         0         0         0    182120         0
207 cf         0         0         0         0         0         0         0         0   5116903         0
208 d0         0         0         0         0         0         0         0         0  28198760         0
209 d1         0         0         0         0         0         0         0         0  56691792         0
210 d2         0         0         0         0         0         0         0         0  66400904         0
211 d3         0         0         0         0         0         0         0         0  67105650         0
212 d4         0         0         0         0         0         0         0         0  67087086         0
213 d5         0         0         0         0         0         0         0         0  65379968         0
214 d6         0         0         0         0         0         0         0         0  50595456         0
215 d7         0         0         0         0         0         0         0         0  20129524         0
216 d8         0         0         0         0         0         0         0         0   2560762         0
217 d9         0         0         0         0         0         0         0         0     47720         0
218 da         0         0         0         0         0         0         0         0         0         0
219 db         0         0         0         0         0         0         0         0         0         0
220 dc         0         0         0         0         0         0         0         0         0         0
221 dd         0         0         0         0         0         0         0         0         0         0
222 de         0         0         0         0         0         0         0         0         0         0
223 df         0         0         0         0         0         0         0         0         0         0
224 e0         0         0         0         0         0         0         0         0         0         0
225 e1         0         0         0         0         0         0         0         0         0         0
226 e2         0         0         0         0         0         0         0         0         0         0
227 e3         0         0         0         0         0         0         0         0         0         0
228 e4         0         0         0         0         0         0         0         0         0         0
229 e5         0         0         0         0         0         0         0         0         0         0
230 e6         0         0         0         0         0         0         0         0         0         0
231 e7         0         0         0         0         0         0         0         0         0      1800
232 e8         0         0         0         0         0         0         0         0         0    552392
233 e9         0         0         0         0         0         0         0         0         0   9150989
234 ea         0         0         0         0         0         0         0         0         0  36781096
235 eb         0         0         0         0         0         0         0         0         0  61133068
236 ec         0         0         0         0         0         0         0         0         0  66863912
237 ed         0         0         0         0         0         0         0         0         0  67108646
238 ee         0         0         0         0         0         0         0         0         0  67012480
239 ef         0         0         0         0         0         0         0         0         0  63435032
240 f0         0         0         0         0         0         0         0         0         0  43057520
241 f1         0         0         0         0         0         0         0         0         0  13263308
242 f2         0         0         0         0         0         0         0         0         0   1127526
243 f3         0         0         0         0         0         0         0         0         0      8960
244 f4         0         0         0         0         0         0         0         0         0         0
245 f5         0         0         0         0         0         0         0         0         0         0
246 f6         0         0         0         0         0         0         0         0         0         0
247 f7         0         0         0         0         0         0         0         0         0         0
248 f8         0         0         0         0         0         0         0         0         0         0
249 f9         0         0         0         0         0         0         0         0         0         0
250 fa         0         0         0         0         0         0         0         0         0         0
251 fb         0         0         0         0         0         0         0         0         0         0
252 fc         0         0         0         0         0         0         0         0         0         0
253 fd         0         0         0         0         0         0         0         0         0         0
254 fe         0         0         0         0         0         0         0         0         0         0
255 ff         0         0         0         0         0         0         0         0         0         0

この実行結果の行が「最後の右シフトで捨てられる情報」、列が10で割った余りに対応している。
この実行結果を見ると、入力が32ビット符号なし整数のとき、都合よく「最後の右シフトで捨てられる情報」の上位4ビットだけを見れば10で割った余りを求めることができそうなことがわかる。
というか、この部分は入力の値に 0.1 を掛けたときの小数部分に相当するので、10を掛けて同様に右シフトをすることで余りが得られそうだ。
この処理を組み込んでみよう。

#include <stdio.h>
#include <limits.h>

unsigned int div10(unsigned int *rem, unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	unsigned long long value =
		((unsigned long long)a << 5) - (a3 << 1) - (a3 >> 3) - (a3 >> 7)
		- (a3 >> 11) - (a3 >> 15) - (a3 >> 19) - (a3 >> 23) - (a3 >> 27);
	*rem = ((value & 0xff) * 10) >> 8;
	return value >> 8;
}

int main(void) {
	for (unsigned int i = 0; ; i++) {
		unsigned int res, rem;
		res = div10(&rem, i);
		if (res != i / 10 || rem != i % 10) {
			printf("%u -> %u, %u\n", i, res, rem);
			break;
		}
		if (i == UINT_MAX) break;
	}
	return 0;
}

結果は何も出力されず、任意の32ビット符号なし整数について正しい10で割った商と余りが求まることがわかった。
1個だけ掛け算を使っているのはカッコ悪いので、シフトと足し算に変換しておこう。

unsigned int div10(unsigned int *rem, unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	unsigned long long value =
		((unsigned long long)a << 5) - (a3 << 1) - (a3 >> 3) - (a3 >> 7)
		- (a3 >> 11) - (a3 >> 15) - (a3 >> 19) - (a3 >> 23) - (a3 >> 27);
	unsigned int rem_work = value & 0xff;
	*rem = (rem_work + (rem_work << 2)) >> 7;
	return value >> 8;
}

結論

以下の関数で、任意の32ビット符号なし整数 (少なくとも UINT_MAX 以下) を10で割った商と余りを求めることができる。

unsigned int div10(unsigned int *rem, unsigned int a) {
	unsigned long long a3 = ((unsigned long long)a << 1) + a;
	unsigned long long value =
		((unsigned long long)a << 5) - (a3 << 1) - (a3 >> 3) - (a3 >> 7)
		- (a3 >> 11) - (a3 >> 15) - (a3 >> 19) - (a3 >> 23) - (a3 >> 27);
	unsigned int rem_work = value & 0xff;
	*rem = (rem_work + (rem_work << 2)) >> 7;
	return value >> 8;
}

以下の関数で、任意の24ビット符号なし整数 (54,351,188 以下) を10で割った商と余りを求めることができる。

unsigned int div10(unsigned int *rem, unsigned int a) {
	unsigned int a3 = (a << 1) + a;
	unsigned int value =
		(a << 5) - (a3 << 1) - (a3 >> 3) - (a3 >> 7)
		- (a3 >> 11) - (a3 >> 15) - (a3 >> 19);
	unsigned int rem_work = value & 0xff;
	*rem = (rem_work + (rem_work << 2)) >> 7;
	return value >> 8;
}

以下の関数で、任意の16ビット符号なし整数 (232,788 以下) を10で割った商と余りを求めることができる。

unsigned int div10(unsigned int *rem, unsigned int a) {
	unsigned int a3 = (a << 1) + a;
	unsigned int value = (a << 5) - (a3 << 1) - (a3 >> 3) - (a3 >> 7) - (a3 >> 11);
	unsigned int rem_work = value & 0xff;
	*rem = (rem_work + (rem_work << 2)) >> 7;
	return value >> 8;
}

おまけ:GCC による「10で割る」の表現

Compiler Explorer を用いて、GCC が10 で割った商と余りをどう表現するかを見てみた。
以下のコードを入力とし、-O2 オプションをつけてコンパイルした。

unsigned int div10(unsigned int a) {
    return a / 10;
}

unsigned int mod10(unsigned int a) {
    return a % 10;
}

x86-64 gcc 14.2

div10:
        movl    %edi, %eax
        movl    $3435973837, %edx
        imulq   %rdx, %rax
        shrq    $35, %rax
        ret
mod10:
        movl    %edi, %eax
        movl    $3435973837, %edx
        imulq   %rdx, %rax
        shrq    $35, %rax
        leal    (%rax,%rax,4), %edx
        movl    %edi, %eax
        addl    %edx, %edx
        subl    %edx, %eax
        ret

商は入力にマジックナンバーを掛けて35ビット右シフトすることで、余りは入力から商の10倍を引くことで求めている。
マジックナンバーの 3435973837 を二進数で表すと 11001100110011001100110011001101 となり、パターンに沿っているらしいことがわかる。

ARM GCC 14.2.0

div10:
        movw    r3, #52429
        movt    r3, 52428
        umull   r3, r0, r3, r0
        lsrs    r0, r0, #3
        bx      lr
mod10:
        movw    r3, #52429
        movt    r3, 52428
        movs    r2, #10
        umull   r1, r3, r3, r0
        lsrs    r3, r3, #3
        mls     r0, r2, r3, r0
        bx      lr

「マジックナンバーを掛けて35ビット右シフトする」「余りは入力から商の10倍を引いて求める」方針のようである。

各命令の意味は、以下のものである。

命令 意味
movw a, b a ← b
movt a, b a ← (a & 0xffff) | (b << 16)
umull a, b, c, d b:a ← c * d
lsrs a, b, c a ← b >> c (論理シフト)
mls a, b, c, d a ← d - b * c

参考:

RISC-V (32-bits) gcc 14.2.0

div10:
        li      a5,-858992640
        addi    a5,a5,-819
        mulhu   a0,a0,a5
        srli    a0,a0,3
        ret
mod10:
        li      a5,-858992640
        addi    a5,a5,-819
        mulhu   a5,a0,a5
        srli    a5,a5,3
        slli    a4,a5,2
        add     a5,a4,a5
        slli    a5,a5,1
        sub     a0,a0,a5
        ret

「マジックナンバーを掛けて35ビット右シフトする」「余りは入力から商の10倍を引いて求める」方針のようである。
-858992640 を十六進数 (2の補数) で表すと 0xccccd000 であり、これに -819 を足すことでマジックナンバー 0xcccccccd (3435973837) を作っている。

mips gcc 14.2.0

div10:
        li      $2,10                 # 0xa
        bne     $2,$0,1f
        divu    $0,$4,$2
        break   7
        mflo    $2
        jr      $31
        nop

mod10:
        li      $2,10                 # 0xa
        bne     $2,$0,1f
        divu    $0,$4,$2
        break   7
        mfhi    $2
        jr      $31
        nop

除算命令を用いていることがわかる。

AVR GCC 14.2.0

__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
div10:
.L__stack_usage = 0
        ldi r22,lo8(10)
        ldi r23,0
        rcall __udivmodhi4
        mov r24,r22
        mov r25,r23
        ret
mod10:
.L__stack_usage = 0
        ldi r22,lo8(10)
        ldi r23,0
        rcall __udivmodhi4
        ret

この環境の unsigned int は2バイトのようである。
肝心の処理内容は、ライブラリに隠蔽されている。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?