55835


倍精度小数をテキスト形式で保存する場合には、１０進数の有効桁数N桁表示で表すのが妥当だと考えています。 (例: `1.23456e+10`,
などこの場合有効数字6桁)

無限に有効数字の桁数をとれるのであるならば、それは倍精度小数に再変換した際にも、もともとの数字に戻ってくることが保証できるような気がします。しかし、リソースは有限なので、必要以上の桁数を保存するのは無駄です。

### 質問

  * 倍精度浮動小数を１０進数の有効数値表示で保存する場合、元々の数字に必ず戻ってこれるような有効桁数のうち、最小のものはいくつですか？
  * ２進数有効桁数表示である倍精度浮動小数と、１０進数有効桁数表示の、形式の違いによって丸める必要が発生した場合、それは一番近い表現可能な値へと丸めることとします。


CC BY-SA 4.0
2019-06-15T11:38:25.600
55812
2019-07-19T12:48:06.400
754
question
浮動小数点数
変換の際には、表現しうる直近の数字へ丸めるとした場合、倍精度小数を相互変換で差異なく表せる１０進数有効桁数は？


> 倍精度小数をテキスト形式で保存する場合には、１０進数の有効桁数N桁表示で表すのが妥当だと考えています。

数値をテキスト形式で保存する目的がわからないので妥当性の判断ができません。  
そのテキストデータをどのように使用するのでしょうか？

もし同じ（CPUアーキテクチャの）マシンでプログラムが読み込んで  
その値を演算等に使用するだけであればテキストでなく  
バイナリ形式で保存すれば完全に値が保持されます。  
テキスト形式で保存する必要があるでしょうか？

> 倍精度浮動小数を１０進数の有効数値表示で保存する場合、元々の数字に必ず戻ってこれるような有効桁数のうち、最小のものはいくつですか？

そのマシンのCPUがどの規格に従うかで変わってきます。  
現在の多くのパソコンはIEEE 754 形式です。  
そうだと仮定すると17桁が必要最低限かと思います。  
最下位桁の扱いがテキスト化する時とテキストを読み込む時で同じであることが前提です。


CC BY-SA 4.0
2019-06-16T01:50:24.517
55829
2019-07-19T12:48:06.400
2019-07-19T12:48:06.400
4388
4388
55812
answer


結論だけでよければ、Wikipedia(その引用元はIEEE-754規格そのもの)に書いてあります。

[IEEE-754 6
文字列表現](https://ja.wikipedia.org/wiki/IEEE_754#%E6%96%87%E5%AD%97%E5%88%97%E8%A1%A8%E7%8F%BE)

> 二進形式の値を十進の外部文字列形式に変換する場合、以下の桁数にすれば元の値を完全に保持できる[11]。
>
>   * binary16 の場合、5桁
>   * binary32 の場合、9桁
>   * binary64 の場合、17桁
>   * binary128 の場合、36桁
>

IEEE形式倍精度の仮数部は53bit、よくある有効桁数の計算53×log_10(2)≒15.9545から16桁と言う数字を出すことがありますが、それでは完全に元の値を再現できない場合があると言うことですね。

数学的な解説が欲しいのか、結論だけわかればいいのか不明なので、とりあえずはここで置きます。

* * *

承認されたあとですが、16桁では足りないという例を拾ってきましたので、追記しておきます。

[code]

    2進表現(符号_指数部_仮数部)                                            10進16桁表現       10進17桁表現
    ------------------------------------------------------------------ ----------------- ------------------
    0_01111111111_1111111111111111111111111111111111111111111111111101 1.999999999999999 1.9999999999999993
    0_01111111111_1111111111111111111111111111111111111111111111111100 1.999999999999999 1.9999999999999991
    0_01111111111_1111111111111111111111111111111111111111111111111011 1.999999999999999 1.9999999999999990
    0_01111111111_1111111111111111111111111111111111111111111111111010 1.999999999999999 1.9999999999999987
    
[/code]


CC BY-SA 4.0
2019-06-16T02:36:21.600
55832
2019-06-16T03:17:50.250
2019-06-16T03:17:50.250
13972
13972
55812
answer


IEEE 754 の倍精度で、最近接偶数丸めを使った場合を考えます。

このとき、10 進表記で **17 桁** が最適です。ただし注意点があるのでこの投稿を最後まで読んでください。

## IEEE 754 文字列表現

IEEE 754
には、[浮動小数点数のビット列を文字列形式に変換する機能](https://ja.wikipedia.org/wiki/IEEE%E2%82%8754#%E6%96%87%E5%AD%97%E5%88%97%E8%A1%A8%E7%8F%BE)の仕様があります。特に
10 進表記の文字列とは相互変換のアルゴリズムが定められており、このアルゴリズムにしたがうと倍精度では 17 桁の有効数字が最適です。

IEEE 754 には更に、17 桁よりある程度多すぎても駄目だと書かれています。文字列への変換の実装によっては有効数字を 17 より 3
以上増やしてしまうと逆変換で元に戻らない場合があるそうです。 (具体的にどのような実装だとそうなるのか私は把握していません。)

## 16 桁だと駄目

しかしそもそもどうして 17 桁必要なのでしょうか。以下、なぜ 17 桁必要なのかを理論的に考えます。

まず、16 桁だと駄目な例があります。たとえば 2 進表記で 1.111...111 (小数部分には 1 が 52 個) となる数 `x` は、2 進表記の
10.0 (10 進表記の 2.0) に近いものの 10.0 未満の数です。しかしこの数 `x` を 10 進表記の有効数字 16
桁で近似すると繰り上がりが起こって 2.000...000 になってしまい、これを 2 進表記に戻しても 1.111...111 にはなりません。

以下は、実際に C 言語でこのことを確かめるプログラムとその実行結果です。[Wandbox
でも試せます。](https://wandbox.org/permlink/d1W9ovVhSnqDMUoA)

[code]

    #include <stdio.h>
    
    int main() {
        long long p = (1LL << 53) - 1;
        long long q = (1LL << 52);
        double x = (1.0 * p) / q;
    
        // 整数部分に 1 桁あるので小数点以下は 1 小さいです。
        printf("16: %.15lf\n17: %.16lf\n18: %.17lf\n30: %.29lf\n", x, x, x, x);
        return 0;
    }
    
[/code]

[code]

    $ gcc prog.c -std=gnu11
    $ ./a.out
    16: 2.000000000000000
    17: 1.9999999999999998
    18: 1.99999999999999978
    30: 1.99999999999999977795539507497
    
[/code]

## 17 桁だと OK

1 桁増やして、17 桁あれば正確に文字列で表すことができます。つまり、実数の倍精度浮動小数点数 `F` を 10 進表記で有効数字 17 桁の小数 `D`
に変換したとき、`D` を浮動小数点数に逆変換した結果は必ず `F` になります。

このことを示すには、浮動小数点数側の持つ「表現している値」の区間が、変換先の 10
進表記側の持つ「表現している値」の区間を真に含むことを言えば充分です。というのもこれが言えると、浮動小数点数から変換した先の 10 進表記の数 `D`
が表現している値の区間 `[D - δ/2, D + δ/2]` (最近接丸め)
に含まれる浮動小数点数が高々ひとつであることが言えるからです。つまり、浮動小数点数を 10
進表記に変換したものを逆変換すると必ず元に戻ってくることが保証できます。

さて上で述べたことを示すには、浮動小数点数側の区間幅の半分が 10
進表記側の区間幅より大きいことを言えば良いです（区間幅の関係図を描くと分かります）。具体的に区間幅を計算してみましょう。10 進表記したときの数 `D` が
`10ⁿ ≦ D ≦ 10ⁿ⁺¹` という大きさの数のとき、`D` の有効数字は 17 桁なので `D` の持つ区間の幅 `δ` は `10ⁿ⁻¹⁷⁺¹ =
10ⁿ⁻¹⁶` です。対して 2 進表記側の仮数部は (52 + 1) bit なので、`10ⁿ < 2ᵐ` となるような最小の `m` を使うと、2
進表記側の区間幅は最小でも `2ᵐ⁻⁵²` であると言えます。`2⁻⁵² / 2 = 2⁻⁵³ ≒ 1.11 × 10⁻¹⁶`
であることから区間幅の大小関係を比較すると下のとおりです。

[code]

    10ⁿ⁻¹⁶ < 2ᵐ10⁻¹⁶ < 2ᵐ2⁻⁵³ = 2ᵐ⁻⁵² / 2
    
[/code]

したがって示せました。

## ただしどちらにせよ注意せねば駄目

以上で、倍精度の浮動小数点数のうち実数であるものを文字列で正確に表すには有効数字が 17 桁あれば良いことが分かりました。

ただし、もし IEEE 754 の浮動小数点数の情報を一切欠かすことなくテキスト形式で保存したいのであれば、これでも不充分です。というのも **IEEE
754 には[signaling NaN](https://ja.wikipedia.org/wiki/NaN#signaling_NaN)
が存在し、これを単に `"NaN"` という文字列にすると仮数部のビット列に込められていた情報が失われてしまう**からです。

signaling NaN が quiet NaN
になってもよいのであれば、この桁数の文字列にすることで浮動小数点数と文字列の間を正確に変換できます。実は先に述べた IEEE 754
の文字列表現についての仕様にも NaN について書かれており、signaling NaN が quiet NaN
になってしまうことを除けば正確に文字列表現できるように実装せよと書かれています。

## 補足と謝辞

  * 非正規数について議論しませんでしたが、非正規数は正規数より有効数字が小さいので OK です。
  * `-0` があるため、文字列として出力されるまでの間で有効数字以外に符号が保存されているかも気にすべきです。
  * 場合によっては、ロケールに気を付けるべきです。小数点が `.` になるか `,` になるかが変わる場合があります。
  * 16 桁における具体的な反例は、[hidesugar2](https://twitter.com/hidesugar2) に教えてもらいました。ありがとうございます。
  * 回答を考える際、最初[計算機イプシロン](https://en.wikipedia.org/wiki/Machine%E2%82%91psilon)が 1.11 × 10⁻¹⁶ だから 17 桁、と言おうと思ったのですがこれだけだと理由が不充分でした。計算機イプシロンは丸めにおける最大の相対誤差のことですが、これは他の基数へ変換して逆変換したときの誤差についてまでは述べていません。
  * 参考 
    * [IEEE 754](https://ja.wikipedia.org/wiki/IEEE%E2%82%8754) \-- Wikipedia
    * [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) \--   
Numerical Computation Guide

      * Theorem 15 に同様の問題が書かれています。


CC BY-SA 4.0
2019-06-16T04:07:27.293
55835
2019-06-19T03:34:18.060
2019-06-19T03:34:18.060
19110
19110
55812
answer