元のタイトル「実数の計算誤差」と関係がありますので,こちらに投稿します.
コンピュータにおける計算 (数値計算) の誤差と四捨五入に関する話題です.
以下の記事も参考にしてください.
[890_計算誤差 ( 数値計算の誤差 ) と多倍長演算]
http://mrxray.on.coocan.jp/Delphi/plSamples/890_CalcError.htm
まず,次のコードを実行してみます.
Extended 型の変数に実数値を代入して,その値を指定の小数点以下の桁数で四捨五入 (丸めて) 表示するものです.
実行には,uses に Math が必要です.
以下,全て Windows 7 U64(SP1) + Delpi XE5(UP2) Pro VCL-32 で実行しています.
procedure TForm1.Button1Click(Sender: TObject); var E, F : Extended; begin Memo1.Lines.Clear; Memo1.Font.Name := 'MS ゴシック'; Memo1.Font.Size := 10; Memo1.Lines.Add(' 元の値 小数点3桁 小数点2桁'); Memo1.Lines.Add('---------------------------------');
E := 179.9349; F := 179.9350;
Memo1.Lines.Add(FloatToStr(E) + ' ' + FloatToStr(SimpleRoundTo(E, -3)) + ' ' + FloatToStr(SimpleRoundTo(E, -2)));
Memo1.Lines.Add(FloatToStr(F) + ' ' + FloatToStr(SimpleRoundTo(F, -3)) + ' ' + FloatToStr(SimpleRoundTo(F, -2))); end;
結果は上の図のようになります.
179.9349 を SimpleRoundTo で小数点 2 桁目で「丸めた」結果は 179.93 となっています.
これは,元の値の小数点 3 桁目の値が 4 です.4 は 5 よりも小さい値です.
したがって,切り捨てられた結果です.
[System.Math.SimpleRoundTo - RAD Studio API Documentation]
http://docwiki.embarcadero.com/Libraries/XE6/ja/System.Math.SimpleRoundTo
今度は次のコードを実行してみます.
変数に値を直接代入するのではなく,計算結果を代入します.この計算は,計算式は違いますが,数学的には同じ値になります.
同じく uses に Math が必要です.
//----------------------------------------------------------------------------- // (1)と(2)の計算式は,数学的には等しく,179.935となる //----------------------------------------------------------------------------- procedure TForm1.Button2Click(Sender: TObject); var A, B, C, D, E, F : Extended; begin Memo1.Lines.Clear; Memo1.Font.Name := 'MS ゴシック'; Memo1.Font.Size := 10; Memo1.Lines.Add(' 計算値 小数点3桁 小数点2桁'); Memo1.Lines.Add('---------------------------------');
A := 389.7; B := 94.4; c := 30; D := 0.35; E := (A + B + C) * D; //(1) F := A * D + B * D + C * D; //(2)
Memo1.Lines.Add(FloatToStr(E) + ' ' + FloatToStr(SimpleRoundTo(E, -3)) + ' ' + FloatToStr(SimpleRoundTo(E, -2)));
Memo1.Lines.Add(FloatToStr(F) + ' ' + FloatToStr(SimpleRoundTo(F, -3)) + ' ' + FloatToStr(SimpleRoundTo(F, -2))); end;
結果の図をみると,計算結果が同じにも関わらず,E の値を小数点 2 桁目まで取得した値が 179.93 となってしまっています.
そこで,Delphi XE2 で実装で実装された浮動小数点用レコードで計算結果の値をバイト値で確認してみました.
Extended 型のレコード型は TExtended80Rec なので,これを使用しています.
[Delphi のデータ型 - RAD Studio]
http://docwiki.embarcadero.com/RADStudio/XE6/ja/Delphi_%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B#.E6.B5.AE.E5.8B.95.E5.B0.8F.E6.95.B0.E7.82.B9.E3.83.87.E3.83.BC.E3.82.BF.E5.9E.8B
//----------------------------------------------------------------------------- // (1)と(2)の計算式は,数学的には等しく,179.935となる //----------------------------------------------------------------------------- procedure TForm1.Button3Click(Sender: TObject); var A, B, C, D, E, F : Extended; ValueByte : TExtended80Rec; StrText : String; i : Integer; begin Memo1.Lines.Clear; Memo1.Font.Name := 'MS ゴシック'; Memo1.Font.Size := 10; Memo1.Lines.Add('---------------------------------');
A := 389.7; B := 94.4; c := 30; D := 0.35; E := (A + B + C) * D; //(1) F := A * D + B * D + C * D; //(2)
ValueByte := TExtended80Rec(E); StrText := ' '; for i := 9 downto 0 do begin StrText := StrText + IntToHex(ValueByte.Bytes[I], 2) + ' '; end; Memo1.Lines.Add(StrText);
ValueByte := TExtended80Rec(F); StrText := ' '; for i := 9 downto 0 do begin StrText := StrText + IntToHex(ValueByte.Bytes[I], 2) + ' '; end; Memo1.Lines.Add(StrText); end;
上の結果を見ると,E と F は最後のバイト値,それも最後のビットだけが違います.
つまり,E の元の値は 179.935 ではなく,本当は 179.9349999999999… ということになります.
FloatToStr 関数は,数値を文字列にする時に有効桁数の 15 桁目で「丸め」て文字列を取得します.そのため [計算結果] の値は, E も F も 179.935 と表示されます.
一方,SimpleRoundTo で四捨五入する際は,元の引数の値で「丸め」の操作を行うものと思われます.したがって,179.9349999… を小数点 2 桁で四捨五入すると,は 179.93 となります.
64 ビットのアプリでは,Extended 型は Double 型と同じですので,FormatFloat で文字列に変換すれば実際の数値を確認できます.
以下のそのテスト用のコードです.
[System.SysUtils.FloatToStr - RAD Studio API Documentation]
http://docwiki.embarcadero.com/Libraries/XE6/ja/System.SysUtils.FloatToStr
//----------------------------------------------------------------------------- // (1)と(2)の計算式は,数学的には等しく,179.935となる //----------------------------------------------------------------------------- procedure TForm1.Button4Click(Sender: TObject); var A, B, C, D, E, F : Double; ValueByte : TExtended80Rec; StrText : String; i : Integer; begin Memo1.Lines.Clear; Memo1.Font.Name := 'MS ゴシック'; Memo1.Font.Size := 10; Memo1.Lines.Add('-----------------------------------------');
A := 389.7; B := 94.4; c := 30; D := 0.35; E := (A + B + C) * D; //(1) F := A * D + B * D + C * D; //(2)
Memo1.Lines.Add(' E: ' + FormatFloat('000.000000000000000000', E)); Memo1.Lines.Add(' F: ' + FormatFloat('000.000000000000000000', F)); end;
結果を見ると,Double 型の計算でさえ少なくとも有効桁数の 15 桁目までは 9 となっています.
したがって,15 桁目で丸めると 179.935 となることが分かります.
Extended 型については以下も参考してください.
[W1066 Extended 型の浮動小数値の精度が失われます。Double 型に丸めました(Delphi) - RAD Studio]
http://docwiki.embarcadero.com/RADStudio/XE7/ja/W1066_Extended_%E5%9E%8B%E3%81%AE%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E5%80%A4%E3%81%AE%E7%B2%BE%E5%BA%A6%E3%81%8C%E5%A4%B1%E3%82%8F%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82Double_%E5%9E%8B%E3%81%AB%E4%B8%B8%E3%82%81%E3%81%BE%E3%81%97%E3%81%9F%EF%BC%88Delphi%EF%BC%89
[System.Extended - RAD Studio API Documentation]
http://docwiki.embarcadero.com/Libraries/XE7/ja/System.Extended
|