
今、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桁目を四捨五入しないと誤差をとることはできないし、でも上記の例だと正しい結果
が出力されません。
どうしたらいいのか頭を悩ませています。
何か良い解決法等あれば、ご教授お願いします!!
No.5ベストアンサー
- 回答日時:
こんにちは。
他の回答者さんも書かれているように、
まず基本的に、浮動小数点形式の実数値での計算を行う場合は、予め
仕様として、小数点以下桁数を、そのシステム(処理系)の演算精度に
合わせて決めうちで設定しておいて、その桁数を超えた値については、
丸め処理(四捨五入、切り捨て、切り上げ)を行うしかないと思います。
そして可能であれば、実数値は以下のように整数値に変換してから、
計算を行うようにすれば、見かけ上、計算誤差を抑えることが可能だと
思います。
■浮動小数点形式の実数値の整数化演算
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で切り上げ)
以上です。

No.6
- 回答日時:
直接の回答ではありませんが、
double と long double のデータサイズについて。
手元にある、32bit Windows 用の Borland の処理系(Borland C++ 5.82)の例では、
sizeof(double) は、8
sizeof(long double) は、10
です。
IEEE 754形式を採用している場合、float は単精度、double は倍精度、long double は拡張精度になっているケースが多いかと思いますが。
No.4
- 回答日時:
>どうしたらいいのか頭を悩ませています。
「見た目」だけでしょ、悩むこと無いと思います。
>本来は「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桁電卓の表示方法について」が良かったかも・・。

No.3
- 回答日時:
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と比較して速度性能は犠牲になります。
No.2
- 回答日時:
0.09 999 999 999 999
___1 234 567 890 123
と、13桁ですけど。これを15桁と思うと言うことは、1の位のゼロから数え始めてますね。先行するゼロを無視して有効数字の最初から数えないと駄目です。
No.1
- 回答日時:
doubleは
32bit機と64bit機では長さが違います。
32bit機で普通のdoubleより長くしたいのであれば、
long double
という型があります。
長さが2倍になります。
これを使うと良いと思います。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
PS4コントローラーをPCでゲーム...
-
Microsoft365で作ったword文書...
-
Accessのテーブルからcsv出力す...
-
4Kの外部モニターに出力すると...
-
プログラムについての質問です...
-
cout と cerrの違い
-
C#でアクセス権限の取得方法が...
-
センサーのタンパー出力について
-
PHPのGDで折れ線グラフを作成中...
-
coutで出力した文字を消去する...
-
C# 標準出力のencodingをutf8に...
-
リッチテキストをテキストに変換
-
ActiveReportsで縦書きの帳票
-
TV出力ポートをOFFにすれば良い...
-
printfの書式%.*s
-
COBOLで可変長ファイルの出力に...
-
スマホ充電器購入にあたり
-
FLEXSOLARパネルからAmazonで買...
-
makeで文字化けする。migwのmak...
-
PD充電器について
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
PS4コントローラーをPCでゲーム...
-
4Kの外部モニターに出力すると...
-
プログラムについての質問です...
-
Accessのテーブルからcsv出力す...
-
FLEXSOLARパネルからAmazonで買...
-
真空管 300 B の前段について
-
cout と cerrの違い
-
printfとputcharの違いは
-
スマホ充電器購入にあたり
-
PD充電器について
-
TV出力ポートをOFFにすれば良い...
-
COBOLのMOVEで桁数が異なる場合
-
Windows Formアプリからコンソ...
-
CrystalReportの文字列の折返し...
-
C#でアクセス権限の取得方法が...
-
coutで出力した文字を消去する...
-
【UWSC】WEBページ中の特定文字...
-
KEYENCEのシーケンスプログラム...
-
CRC16計算について
-
標準出力の上書き
おすすめ情報