重要なお知らせ

「教えて! goo」は2025年9月17日(水)をもちまして、サービスを終了いたします。詳細はこちら>

【GOLF me!】初月無料お試し

今、VC++(MFC)で、15桁の数値まで表示可能な電卓アプリを作成しています。
そこで今つまずいているのが、double型で計算したとき(演算結果が小数の場合)の誤差の問題です。

とりあえず、いろいろなHPなどの情報を見たりして、誤差問題解決を下記のようにしました。

「数値の頭(左側)から16桁目を四捨五入する」


小数の場合はほとんど誤差が生じるため、計算後、計算結果が小数ならば、必ず
上記の誤差処理を行っています。



しかしこれでは、以下の場合に不具合が出てしまいます。

・ 0.99 999 999 999 999 ÷ 10 = 本来の答えは「0.09 999 999 999 999 9」
⇒ しかし15桁までの表示なので、本来は「0.09 999 999 999 999」と15桁まで出力
    させなくてはいけないのに、16桁目の「9」を四捨五入したせいで「0.1」という表示に
    なってしまう。

16桁目を四捨五入しないと誤差をとることはできないし、でも上記の例だと正しい結果
が出力されません。
どうしたらいいのか頭を悩ませています。


何か良い解決法等あれば、ご教授お願いします!!

A 回答 (6件)

こんにちは。



他の回答者さんも書かれているように、
まず基本的に、浮動小数点形式の実数値での計算を行う場合は、予め
仕様として、小数点以下桁数を、そのシステム(処理系)の演算精度に
合わせて決めうちで設定しておいて、その桁数を超えた値については、
丸め処理(四捨五入、切り捨て、切り上げ)を行うしかないと思います。

そして可能であれば、実数値は以下のように整数値に変換してから、
計算を行うようにすれば、見かけ上、計算誤差を抑えることが可能だと
思います。

■浮動小数点形式の実数値の整数化演算
1)計算に使用する値は、全て小数点以下の値を含まない整数値に変換
  (小数点位置をずらして整数化)して、整数値のみで計算を行う。
2)計算時の小数点以下の値が発生する場合の処理方法(四捨五入、
  切り捨て、切り上げ)は、予め決めておく。
3)値を表示、及び出力する段階で、整数値を実数値に再変換(小数点
  位置の復元化)する。

以下は、上記の処理を行うサンプルです。
宜しければ検証してみて下さい。

■サンプルソース(下記リンク先参照)
http://ideone.com/RtxSu

■サンプルの実行結果
== [入力値÷10.0]の計算[1](小数点以下桁数=14桁) ==
入力値 (整数値)= 99999999999999.00
出力値1(整数値)= 10000000000000.00 (÷10.0で4捨5入)
出力値2(整数値)= 9999999999999.00 (÷10.0で切り捨て)
出力値3(整数値)= 10000000000000.00 (÷10.0で切り上げ)
入力値 (実数値)= 0.9999999999999900
出力値1(実数値)= 0.1000000000000000(÷10.0で4捨5入)
出力値2(実数値)= 0.0999999999999900(÷10.0で切り捨て)
出力値3(実数値)= 0.1000000000000000(÷10.0で切り上げ)

== [入力値÷10.0]の計算[2](小数点以下桁数=14桁) ==
入力値 (整数値)= -12345678901234.00
出力値1(整数値)= -1234567890123.00 (÷10.0で4捨5入)
出力値2(整数値)= -1234567890123.00 (÷10.0で切り捨て)
出力値3(整数値)= -1234567890124.00 (÷10.0で切り上げ)
入力値 (実数値)= -0.1234567890123400
出力値1(実数値)= -0.0123456789012300(÷10.0で4捨5入)
出力値2(実数値)= -0.0123456789012300(÷10.0で切り捨て)
出力値3(実数値)= -0.0123456789012400(÷10.0で切り上げ)

== [入力値÷10.0]の計算[3](小数点以下桁数=14桁) ==
入力値 (整数値)= -12345678901235.00
出力値1(整数値)= -1234567890124.00 (÷10.0で4捨5入)
出力値2(整数値)= -1234567890123.00 (÷10.0で切り捨て)
出力値3(整数値)= -1234567890124.00 (÷10.0で切り上げ)
入力値 (実数値)= -0.1234567890123500
出力値1(実数値)= -0.0123456789012400(÷10.0で4捨5入)
出力値2(実数値)= -0.0123456789012300(÷10.0で切り捨て)
出力値3(実数値)= -0.0123456789012400(÷10.0で切り上げ)

以上です。
    • good
    • 0

直接の回答ではありませんが、



double と long double のデータサイズについて。

手元にある、32bit Windows 用の Borland の処理系(Borland C++ 5.82)の例では、
sizeof(double) は、8
sizeof(long double) は、10
です。

IEEE 754形式を採用している場合、float は単精度、double は倍精度、long double は拡張精度になっているケースが多いかと思いますが。
    • good
    • 0

>どうしたらいいのか頭を悩ませています。



 「見た目」だけでしょ、悩むこと無いと思います。

>本来は「0.09 999 999 999 999」と15桁まで出力させなくてはいけない

「いけない」んだったら↑このように、「表示」する電卓アプリを作るしかない。
その仕様は、「はみ出た16桁目を★切り捨てる」とすればいい。

「0.1」という電卓での「2桁」ではなく、「15桁まで」が目的だったら、「いろいろなHPなどの情報」どおり「はみ出た16桁目を★四捨五入」し、
「0.10 000 000 000 000」と「表示」する電卓アプリを作ればいいと思います。

数値的に見ると、当然、切り捨てた方が「誤差」は大きくなります。

>16桁目の「9」を四捨五入したせいで「0.1」という表示になってしまう。

Win7 の電卓で .9・・99 と入れられるだけ入れて、/ 10 してみたら同じ結果になりました。
マイクロソフトも、

>「数値の頭(左側)から**桁目を四捨五入する」

という規則を守っているのかな。

ところで、

0.00 000 000 000 009 ÷ 10 = 本来の答えは「0.00 000 000 000 000 9」
なので末尾の 9 を四捨五入して 「0.00 000 000 000 001」
となりますが、これも

>正しい結果が出力されません。

となるのですか。すごく「まとも」と思いますが、・・・。
ご質問は、上へ上へと繰り上がっていっただけで、まったくいっしょですよ。

タイトルは、「dounle型で計算時の誤差」より、「15桁電卓の表示方法について」が良かったかも・・。
    • good
    • 0

long doubleが80bit精度だったのはWin16時代の話です。


Win32/Win64ではlong doubleはdoubleと常に同じ8Byteサイズ・64bit精度であり、仮数部精度は53bitです。
なのでいまさらWindowsでlong doubleを使う意味は無いです。


単純に精度を上げるには、「固定小数点数」を自前で実装して使用するか、.NETのSystem.Decimal型を使う手があります。

// VC++ 2005/2008のC++/CLI用サンプル。
#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args)
{
Console::WriteLine("SizeOf(Decimal) = {0}", sizeof(Decimal));
#if 0
Decimal dec = Decimal(0.999999999999999); // double の限界有効桁数。
#else
// 文字列から生成する方法。
Decimal dec;
Decimal::TryParse("0.9999999999999999999999999", dec);
#endif
Console::WriteLine("{0}", dec.ToString());
dec /= Decimal(10);
Console::WriteLine("{0}", dec.ToString());

Console::WriteLine(L"Press any...");
Console::ReadKey(true);
return 0;
}

MFCアプリに共通言語ランタイム(CLR)のサポートを追加して(/clr)、共通プロパティで適切な参照設定を行なえば、doubleより高精度の10進数用Decimal型を使えるようになりますが、当然doubleと比較して速度性能は犠牲になります。
    • good
    • 0

0.09 999 999 999 999


___1 234 567 890 123
と、13桁ですけど。これを15桁と思うと言うことは、1の位のゼロから数え始めてますね。先行するゼロを無視して有効数字の最初から数えないと駄目です。
    • good
    • 0

doubleは


32bit機と64bit機では長さが違います。

32bit機で普通のdoubleより長くしたいのであれば、
long double
という型があります。
長さが2倍になります。
これを使うと良いと思います。
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!