
2025/12/15 0:42
Zmij: Faster floating point double-to-string conversion
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
要約:
著者はGitHubで新しいバイナリから10進数への浮動小数点変換ルーチンを公開しました。この実装は既存のソリューションに比べて大幅に高速です。ベンチマークでは、Dragonboxより約68 %速く、自身のSchubfach実装より2倍速いことが示され、macOS上で
sprintfを最大59倍も速く処理します。また、Apple M1チップ上で単精度・倍精度変換はわずか10–20ナノ秒です。アルゴリズムはSchubfachを改良し、候補選択の簡素化、整数乗算・対数近似・除算/剰余計算・条件分岐の削減により効率化しています。また、Cassio Neriからアイデアを取り入れ、固定小数点演算とルックアップテーブルを用いて計算速度をさらに向上させています。今後は現在の科学的(指数)モードに続き、単純な固定小数形式も追加する予定です。高速化された変換ルーチンは、ロギングライブラリやデータシリアライズフレームワーク、性能重視のアプリケーションなど、浮動小数点数をシリアライズするすべてのソフトウェアに恩恵をもたらし、CPU使用率を下げスループットを向上させます。
要約スケルトン
本文が主に伝えようとしていること(メインメッセージ)
著者はGitHubで既存の方法より大幅に高速な新しいバイナリから10進数への浮動小数点変換ルーチンを公開しました。
証拠/根拠(これが言われている理由)
ベンチマークでは、Dragonboxより約68 %速く、自身のSchubfachコードより2倍速いこと、macOS上で
sprintfより最大59倍速いことが示され、Apple M1上で単精度・倍精度変換が10–20ナノ秒しかかからないと報告されています。
関連ケース/背景(文脈・過去の出来事・周辺情報)
実装はSchubfachアルゴリズムをベースに、候補選択、整数乗算、対数近似、除算/剰余計算、条件分岐を削減して改良しています。またCassio Neriのアイデアも取り入れ、固定小数点演算とルックアップテーブルで計算を簡素化しています。
今後起こり得ること(将来の展開/予測)
著者は現在の科学的(指数)モードに続き、単純な固定小数形式へのサポートを追加する予定です。
この影響が及ぶ可能性のあるもの(ユーザー・企業・業界)
高速化された変換ルーチンは、ロギングライブラリやデータシリアライズフレームワーク、性能重視アプリケーションなど、浮動小数点数をシリアライズするすべてのソフトウェアに恩恵をもたらし、CPU使用率を削減しスループットを向上させます。
本文
ソフトウェアエンジニアの人生に必ず訪れる瞬間――新しいバイナリからデシマルへの浮動小数点変換手法を思いつくとき
それが私の来た時です。週末だけで書いたものですが、ほぼ完結したコードがあります: https://github.com/vitaut/zmij。
Dragon4、Grisu、Schubfach を実装して得られた教訓に加え、自分と他者からの新しいアイデアを取り入れています。主な指針は Alexandrescu の「何もしないことよりも少し作業するほうがいい」という考え方で、Schubfach から条件付き分岐・計算・候補数まで削除していくことで多くの改善を実現しました。
パフォーマンス
dtoa-benchmark 上での性能は以下の通りです。
| 指標 | 比較対象 | 実際の速度差 |
|---|---|---|
| ~68 % 速い | Dragonbox(正しさが証明されているアルゴリズムの中では前頭に立つ) | |
| ~2× 速い | Schubfach の自分実装(原論文に忠実) | |
| ~3.5× 速い | libc++ の (Ryu?) | |
| ~6.8× 速い | Google の double‑conversion(Grisu3) | |
| ~59× 速い | macOS 上の (Dragon4?) |
Apple M1 で単一の double を変換するだけで、10〜20 ns 程度です。
改善点は何か?
Schubfach と比較した改善一覧です。
| 改善項目 | 内容 |
|---|---|
| 候補数を 1–3 に減らす | 考慮する候補が少なくなる |
| 短いケースでの整数乗算を削減 | 計算コストが低下 |
| ログ近似を高速化 | log10(2) の近似計算をより効率的に |
| 除算と剰余を高速化 | を最適化 |
| 条件付き分岐を減らす | 可能な限りブランチレスに |
| 符号部・指数の出力を効率化 | 桁ペア用テーブルを利用 |
小規模分岐の簡略化
NaN、無限大、ゼロまたはサブノーマルといった特殊ケースを一つの分岐で高速に判定。一般ケースはさらにシンプルになります。
固定小数点でのログ近似を高速化
Schubfach は 64‑bit 乗算を使いますが、入力範囲内では 32‑bit 近似でも十分です。
constexpr int log10_2_sig = 315'653; constexpr int log10_2_exp = 20; auto floor_log10_pow2(int e) noexcept -> int { return e * log10_2_sig >> log10_2_exp; }
Dragonbox も同様に 32‑bit 近似を採用しています。
整数除算 → 乗算
inline auto divmod100(uint32_t value) noexcept -> divmod_result { assert(value < 10'000); constexpr int exp = 19; // 19 は 12 より高速または同等(3 桁でも) constexpr int sig = (1 << exp) / 100 + 1; uint32_t div = (value * sig) >> exp; // value / 100 return {div, value - div * 100}; }
ブランチレスで不規則な丸め区間を処理
追加の複雑さを避け、分岐なしで処理します。
Cassio Neri の洞察
Schubfach は四つの候補数を検討しますが、Cassio からは最初の場合に上限値だけで一つの候補を構築できると示されました。これにより短いケースで二回の 64‑bit 乗算を削減できます。
丸めチェックの簡素化
従来の Schubfach:
uint64_t dec_sig_over = dec_sig_under + 1; bool under_in = lower + bin_sig_lsb <= (dec_sig_under << 2); bool over_in = (dec_sig_over << 2) + bin_sig_lsb <= upper; if (under_in != over_in) { return write(buffer, under_in ? dec_sig_under : dec_sig_over, dec_exp); }
最適化版:
int64_t cmp = int64_t(scaled_sig - ((dec_sig_under + dec_sig_over) << 1)); bool under_closer = cmp < 0 || (cmp == 0 && (dec_sig_under & 1) == 0); bool under_in = (dec_sig_under << 2) >= lower; write(buffer, (under_closer & under_in) ? dec_sig_under : dec_sig_over, dec_exp);
符号部と指数の出力
- 桁ペアをテーブル化して整数乗算を半分に削減。
- 余計なゼロを除去するために別の小規模テーブルでブランチレスに処理。
新しいアルゴリズムか?
まだ確定ではありませんが、独自の GitHub プロジェクトとして価値があります。
{fmt} で採用され、Thrift やその他 JSON シリアライゼーションでも候補です。ISO C++ の P2587 「to_string or not to_string」論文により std::to_string がこの手法または類似手法を使えるようになれば、標準 API はさらに高速で実用的になります。
現在の制限
- 科学記数法(指数表記)のみ対応。固定小数点形式は簡単に追加可能です。
ちょっとした話: かつて同僚だった David Gay は Bell Labs で初期の dtoa 実装を書き、長年広く使われました。
最終更新: 2025‑12‑13