質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.34%

言語別?計算誤差に関して

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 1,246

te2ji

score 15866

言語別の計算誤差に関して、興味が出たのでご教示下さい。

もともとは、別の質問の回答で、小数点以下の誤差が気になり、少し検証してみました。

別の質問

簡単に言うと、ruby で (33.1 + 34.2) / 2 を計算すると誤差がでるということです。

$ irb
irb(main):001:0> (33.1 + 34.2) / 2
=> 33.650000000000006

自前の環境で簡単に確認できるモノで試してみました。

echo "scale=20;  (33.1 + 34.2) / 2 " |bc
33.65000000000000000000
php -r "echo ((33.1 + 34.2) / 2);"
33.65

win7標準の電卓アプリ
(33.1 + 34.2) / 2 = 33.65

言語によって、誤差が出るものなのでしょうか?

と質問しようと思って、少し続けて検証してみたところ

php -r "echo number_format((33.1 + 34.2) / 2,20,null,'');"
33.65000000000000568434
console.log(((33.1 + 34.2) / 2));
33.650000000000006

と出ました^^;

各言語の計算で、標準で使う変数の型が違うということなんですかね?
詳しい方、コメントいただければ幸いです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+2

ほとんどの言語において「浮動小数点数」がどのような形式なのかは処理系依存です。ですが、ほぼ全ての環境において、IEEE 754のbinary64(倍精度)が使われます。C言語のdoubleがこれに相当します。多くの実装系がC言語に依存しており、実際の計算はC言語におけるdouble、つまり、IEEE 754 binary64を使用していると考えて問題ありません。
※ C言語のdoubleがIEEE 754 binary64であると仕様で決められているわけではありません。しかし、ほとんどのCPUの浮動小数点数についての各命令がIEEE 754に基づいているため、それ以外の形式が使われる処理系を私は知りません。
※ C言語のfloatのように言語によってはbinary32(単精度)も用意されています。また、追加のライブラリ等で十進や拡張倍精度等の他のIEEE 754が別途用意されている場合もあります。
※ Javaなど一部の言語ではIEEE 754 binary64であることが仕様として定められている場合があります。

さて、このbaniray64ですが、内部表現がニ進数表記です。どういう意味かというとニ進数の小数点数は正確に表せますが、十進数は一部の数字を除いて正確に表現できません。たとえば0.50b0.1になりますが、0.10b0.000110011001100...と循環小数になっており、有限のビットでは表現不可能です。
※ 0bをつけている場合はニ進数表記であることを表します。

つまり、33.1は正確は十進数の33.1という数字では決して無く、実際は"33.10000000000000142108547152..."という33.1の近似値になります。そう、33.1というリテラルは33.1ではありません。33.1に近い別の何かです。ニ進数で循環小数にならない一部を除き、十進数の小数点数は誤差が常につきまいといます。

では、計算結果でなぜ誤差の分が出るときと出ないと時があるかというと、浮動小数点数はどこまでが正しいのかを記録しないからです。33.1は33.10まで正しいのか33.1000まで正しいのかがわかりません。他の数を足したりした場合の結果も、どの精度まで正しいのかわかりません。そのため、通常、浮動小数点数を出力するときは、精度を指定(printfで"%.2f"などにする等)するか、デフォルトでどこまでの精度を表示するかが決まっています。足し算や掛け算によって誤差は大きくなり、信頼できる精度は小さくなりますので、デフォルトで表現する精度(どこまで出すかは言語による)をこえたときに端数のような数字が現れます。
※  十進数->ニ進数、ニ進数->十進数で丸め処理が行われます。丸め方は実装依存です。処理系により異なる可能性があります。
※ 質問のPHPの例は単にデフォルトで表示する精度がRubyよりも少ないため、表示が異なるだけかと思います。精度を合わせれば同じ結果になるでしょう。

ここまではC言語およびC言語のdoubleをそのまま使うような言語(Python、Ruby、JavaScriptなど)の話です。この動作はCPUへの命令をそのまま使うため高速であり、C言語での演算と同じ結果が得られるという利点があります。そもそもの測定値に誤差があるような通常の演算において、精度は後から決めれば良いため、この方法でも問題がありません。しかしそうはいかない場合があります。

精度を保持したまま十進数で計算したい場合があります。電卓のような計算がまさしくそれです。そのような場合に備えて、任意精度の十進数浮動小数点数が用意されています。Rubyのbigdecimalが良い例でしょう。C言語のdoubleとは違い、精度自体が記憶されており、その精度において正確な計算ができるようになっています。また、内部表現が十進数のため、33.1を正確に33.1とすることができます。bcコマンドはこの方式を使っています。ですので、bashでbcコマンドを使った場合は十進数が正確に計算できます。また、高機能な電卓の実装でもこの方式を採用している場合が多いです。(Windowsの電卓も任意精度のようです。参考: SO(ja) | windowsの電卓の内部変数の型について)

もう一つ正確な計算方法があります。それは有理数を使う方法です。Perl6はデフォルトが有理数になっていますし、Rubyであればrをつけることで簡単に有理数にできます。Rubyで((33.1r + 34.2r) / 2).to_f.to_sは"33.65"となるはずです。計算はかなり遅いですが、四則演算であれば精度が落ちる事はありません。(平方根など無理数になる場合は浮動小数点数になってしまい、精度が落ちる場合があります)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/12 06:02

    今まで有効桁数を気にしなければいけないような計算をしなかったので、調べる機会がなかったのですが、全く知らないままだと、ハマりそうな箇所ですね。
    33.1の例は非常に分かりやすかったです。

    今回の質問の回答は【そのため、通常、浮動小数点数を出力するときは、精度を指定(printfで"%.2f"などにする等)するか、デフォルトでどこまでの精度を表示するかが決まっています。】ですね。これを知らないがゆえの質問でした。

    質問して良かったです。ありがとうございました。

    キャンセル

checkベストアンサー

+1

各言語の計算で、標準で使う変数の型が違うということなんですかね? 

基本的には同じだと思います。「IEEE 754」という浮動小数点に関する標準規格がありまして、ほとんどの処理系(CPUやFPU)はその規格に準拠しています。そうなると、そのCPU上で動くプログラミング言語もその規格に合わせるのが自然です。C#やJavaなど、言語によっては仕様として明確にIEEE 754準拠としているものもあります。

では、同じ結果のはずなのに表示結果が異なるのはどういうことかというと、「表示のさせ方が違う」のです。

多くの場合、実数の計算に64bit浮動小数点を使っていますが、10進表記における有効桁数は15桁です。ご質問の中で誤差が出ている表示は、どれも15桁を超えています。そうすると、超えた部分を無理矢理表示しようとして誤差も含めて表示されてしまうのです。

win7標準の電卓アプリ

これは特殊です。アプリケーションの仕様として、内部的には確か128bitで計算しています。そのため64bitよりも有効桁数が多いです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/11 22:43

    「IEEE 754」なんてあったんですね。知りませんでした。
    学習のキーとなりました。ありがとうございます。
    丸め誤差の最大に関しても、理解できる説明を見つけることが出来ました。
    ありがとうございます。

    キャンセル

-1

循環小数という言葉を聞いたことがありますか?

例えば、10進数浮動小数点数で、1/3を表そうとすると、0.33333.... と3を永遠に続けるしか1/3を表現することはできません。(このように、小数点以下の数字が一定のパターン(1/3の例では3が繰り返される)で永遠に続くのを循環小数と言います)

残念ながら、計算機には無限の桁数を表現できるだけのメモリがありませんから、循環小数点数は、どこかの桁以下を近似する(切り捨てる、切り捨てする桁の値で四捨五入する等の処理をする)事で計算機のメモリに数値を保持しています。どこかの桁以下を近似しているので、計算機の中の値は近似値にしかなりません。

このように計算機が数値の表現に使えるデータ(桁数)に限りかあることから、数値を近似せざるを得ず、その近似と正しい値との違いを「近似誤差」「丸めのJ誤差」などと呼びます。

丸目の誤差は、1つの浮動小数点数を2ビットで表現する計算機システムと、64ビットで表現する計算機システムでは異なります。

質問の中で、値が一致しない理由は、この丸目の誤差にあります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/11 22:21

    今回の誤差の差は、2ビットで表現する計算機システムと、64ビットで表現する計算機システムの差ということでしょうか?

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る