魔法の数字 6755399441055744
魔法の数字
なにこれ
浮動小数点数を整数に丸めるマジックナンバーです [1].
使い方
- 適当な数値を用意します.
- マジックナンバーを足します.
- マジックナンバーを引きます.
- 丸められて整数部分が得られます.
なんで?
足して引いただけなのに,小数部分が吹き飛びました.実の所,これは浮動小数点数 (IEEE 754) の仕様を利用したトリックです.まずは,この数の正体と IEEE 754 の挙動から丁寧に確認して参りましょう.
正体
仕組み
このトリックの核心は,有効数字の性質上,double が いつでも同じ細かさで実数を表せる訳ではないという点にあります.十進数でイメージすると分かり易いです.例えば有効数字が 3 桁しかない場合,
double の有効数字は 53 桁ですから,
ひとまず整理しますと,この丸めトリックは以下のように説明できます:
- マジックナンバーを足すと小数部分が保持しきれなくなる.
- そこからマジックナンバーを引くと整数のみが戻ってくる.
2^{51} の必要性
さて,
マジックナンバーを
こうすれば,
丸められる理由
有効数字の制約ゆえ,小数部分が切捨てられたのだと考えがちですが,実際の挙動は「銀行丸め」になります.これはトリック固有の魔法ではなく,IEEE 754 の仕様に由来するものであります.
銀行丸めは四捨五入に似て非なるものでして,端数が
注意点
-
以上の説明から解る通り,この方法は全ての
について万能ではありません.x がC + x に入っている間だけ小数が確実に消えるという性質を利用する以上,[2^{52}, 2^{53}) の条件下で有効です.\left|x\right| < 2^{51} -
処理系によっては浮動小数点数の「丸めモード」を変更でき,ここで銀行丸め以外のモードを選択すると意図せぬ結果が得られることがあります.
-
や\mathrm{NaN} のような特殊値はこの手法の対象外です.\pm\infty
この数の起源
さて,ここまで
結論から申しますと,この手法は公式な標準規格や学術論文で提案されたものではないと見られます.IEEE 754 が定めているのは飽くまで浮動小数点数の表現形式そのものであり,或る数を足して引けば丸められるというような応用テクニックまでは扱っておりません.
この手法はプログラミング実装上の小技として自然発生的に広まったもので,低レベル処理の高速化テクニックとして昔から断片的に流通していたと推察されます.
オンラインコミュニティのみならず,OSS プロジェクトにもこの数は登場します.Windows のアプリケーションを Unix 系 OS で動かすためのツールである Wine のソースコードには,以下のコメントと伴に本記事のマジックナンバーが登場します [2].
wine/libs/lcms2/src/lcms2_internal.h// Fast floor conversion logic. Thanks to Sree Kotay and Stuart Nixon
この Sree Kotay さんと Stuart Nixon さんが発案者なのかどうかは不明ですが,少なくとも
現代の CPU (SSE2 以降) には CVTTSD2SI 等の高速な整数変換命令があり,このトリックを使わずとも同等の処理が可能です.そう考えると,この数字は当時のハードウェアの制約を乗り越えるために生み出された遺産と言えるかもしれません.
サンプルコード
Haskell による実装例です.
-- マジック丸め.型は Double のままだよ
round' :: Double -> Double
round' x =
let magic = 6755399441055744 :: Double
in magic + x - magic
main :: IO ()
main = do
-- 切捨ての例
print $ round' 123.456 -- 123.0
-- 切上げの例
print $ round' 456.789 -- 457.0
-
ここでの浮動小数点数とは倍精度浮動小数点数 (double, f64) を指します.単精度 (float, f32) では 12582912 が使われます. ↩︎
-
Wine Project, lcms2_internal.h, commit
9f41143, https://github.com/wine-mirror/wine/blob/d671927488486b8541cc235a73c94989a21e9caa/libs/lcms2/src/lcms2_internal.h#L156 ↩︎
Discussion