27408


お世話になります。

アルゴリズムか言語特有の問題かが解らないのですが…

C#にて、スタックポインタを使用した、逆ポーランド法で式を計算するプログラムを  
書いているときですが、計算する変数はdecimalを使って、小数まで計算できる  
ようにしたのですが、表題のような計算式を入れると、計算結果が『10.0000000000000000』  
桁数は正確に数えたわけではないので、多分これくらいだったと思います。

これというのは、表示されてはいないものの、『ぴったり10ではない』ということなのでしょぅか。  
それとも、10として計算してはいるけれど、直前の計算で10/3=3.333333333...  
としてしまっているため、その時の桁数通りに扱われているということなのでしょうか。

行いたいことは、当然『10』として結果を得たいのです。いくら数的に10であっても、  
できれば小数点以下は表示させたくありません。もちろん、表示する時点に数値を  
調べ、小数点以下が0ならば小数点から下は表示しないと言った文字列操作で  
できなくはないですが、あまり解決策としてはよくありません。  
このように、『小数点でも割り切れない値』というものを、正しい値のまま  
扱うことはできますでしょうか。

対策方法を教えてください。  
お願いいたします。


CC BY-SA 3.0
2016-07-07T06:45:52.417
27407
2016-07-08T03:37:30.280
2016-07-07T07:31:25.443
76
9374
question
c# アルゴリズム
計算式『10/3*3』について


１０／３は、循環小数（3.33333…が無限に続く）なので、メモリが有限であるので、(コンピュータで)小数を使って正確に数値を表すことはできません。（つまりどこかで丸めが生じる）。

計算上（無理数ではなく）こうした循環小数を扱う場合には、  
循環小数は、分数の形に変換できるので、  
数値計算を分子・分母のペア（つまり分数を内部表現として持つ）で扱う(計算を続けるうちに扱う数値が大きくなることが予想されるので、BigIntegerを使用する）ようにすれば、いいかと思います。


CC BY-SA 3.0
2016-07-07T07:00:16.887
27408
2016-07-07T07:12:55.837
2016-07-07T07:12:55.837
5044
5044
27407
answer


> これというのは、表示されてはいないものの、『ぴったり10ではない』ということなのでしょぅか。  
>  それとも、10として計算してはいるけれど、直前の計算で10/3=3.333333333...  
>  としてしまっているため、その時の桁数通りに扱われているということなのでしょうか。

後者です。というのもdecimal型は値だけでなく、その値の有効精度も併せて保持しています。

[code]

    var d1 = 10m;
    var d2 = 10.0000m;
    Console.WriteLine(d1);   // 10
    Console.WriteLine(d2);   // 10.0000
    
    var s = d2 + 1m;
    Console.WriteLine(s);    // 11.0000
    
[/code]

`10/3`は循環小数ですので精度限界まで`3`が続いてしまうのは仕方がありません。  
またその後の演算においても精度が引き継がれるように設計されています。


CC BY-SA 3.0
2016-07-07T23:36:55.297
27443
2016-07-08T03:37:30.280
2016-07-08T03:37:30.280
4236
4236
27407
answer


一般論として

> 『小数点でも割り切れない値』というものを、正しい値のまま扱うことはできますでしょうか。

について言えば他の方の回答どおり、有理数をうまく扱うように処理する必要があります。

「10/3*3の結果を10と表示するには」という点においてのみ補足したいと思います。

> 直前の計算で10/3=3.333333333...としてしまっているため、その時の桁数通りに扱われているということなのでしょうか。

これはそのとおりです。decimal型は内部に桁数情報(のようなもの)を保持しており、計算結果がたまたま後ろに0が続く数値になったとしてもそれが勝手に減らされる(正規化)ことはありません。それを普通にToString()で文字列化するとその桁数にしたがって0が続くことになります。

`(decimal数値).ToString("G0")`のように書式指定すれば無駄な0が表示されなくなり、期待通り`10`という表示結果が得られます。

* * *

**ちなみに**

`10m / 3m *
3m`の計算結果はきっちり`10m`になります。循環小数が正確に表現できないはずのdecimalでこのような結果になる理由を書こうと思いましたが、長くなりそうなのでおいておきます。また時間があれば補足するかも知れません。

これが`10m / 6m *
3m`だと、`5.0000000000000000000000000001`となり、数学的には正確な計算ができていないことがわかります。


CC BY-SA 3.0
2016-07-08T01:54:51.773
27446
2016-07-08T01:54:51.773
4765
27407
answer


数論上の計算式と、プログラム言語上の計算式は、異なる手順(プロセス)で行われるので、異なる結果となるのが一般論となります。プログラム言語上では、計算手順が異なれば結果も異なります。

また、プログラム言語上の計算式は、数値を限度と精度に限界のある「型」に丸めてから  
計算せざるを得ないので、一般論としては「必ず誤差がある」ということを覚悟しなければなりません。

さて、式「10/3*3」は数論上の式であって、プログラム上の手順が省略されているわけですが、  
簡略化したプログラム言語上の手順を考えると

1.何らかの型に数値10を用意する。  
2.それを3で割り算し、同型に代入する。  
3.上記数値に3を掛算し、同型に代入する。

のような動作になります。

「何らかの型」が16bit整数だった場合、小数点以下が無いため、  
2.の結果は「10÷3=3」となり、3.の結果は「3×3=9」となります。  
これが、プログラム言語上の正しい計算結果です。

「何らかの型」が32bit浮動小数(=float型)ないし、64bit浮動小数(=double)であったと仮定しても、2.の手順で必ず誤差が出るため、数学的結果とは異なる数値となります。

さて、対策ですが、一般的には

(A)誤差が出にくい手順で計算させる。

しかありません。例の式だと「(10*3)/3」の手順で計算させると誤差がなくなります。  
2.の手順が「10×3=30」、3.が「30÷3=10」となり、16bit整数でも数学的結論と見解が一致するわけですね。


CC BY-SA 3.0
2016-07-08T02:34:09.443
27447
2016-07-08T02:44:42.710
2016-07-08T02:44:42.710
3793
3793
27407
answer