喫茶XRAYトップページ

[スレッド全体
]

[8071] 四捨五入返信 削除
2014/10/8 (水) 16:50:38 Mr.XRAY
四捨五入と丸めに関するサンプルです.
例えば以下のコードを実行します.
コードの下の図を見てください.
動作確認は,Windows 7 U64(SP1) + Delphi XE5 Pro で,32 ビットのEXEです.


//-----------------------------------------------------------------------------
//  (1)と(2)の計算式は,数学的には等しく,179.935となる
//  SimpleRoundToの使用にはusesにMathが必要
//
//  Math.GetRoundModeで取得したモードはrmNearest
//  Extendedの扱いは
//  Math.SetPrecisionMode(pmDouble);
//  でDouble型として扱うようにできる
//-----------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
  a,b,c,d,e,f:Extended;
  strText : String;
begin
  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)

  StrText :=  FloatToStr(e)+#13+
              FloatToStr(f)+#13+ sLineBreak +
              FloatToStr(SimpleRoundTo(e,-2))+#13+
              FloatToStr(SimpleRoundTo(f,-2));
  MessageBox(Handle, PChar(strText), '結果', MB_OK);
end;

//-----------------------------------------------------------------------------
//  変数の型をDoubleとした場合
//-----------------------------------------------------------------------------
procedure TForm1.Button2Click(Sender: TObject);
var
  a,b,c,d,e,f:Double;
  strText : String;
begin
  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)

  StrText :=  FloatToStr(e)+#13+
              FloatToStr(f)+#13+ sLineBreak +
              FloatToStr(SimpleRoundTo(e,-2))+#13+
              FloatToStr(SimpleRoundTo(f,-2));
  MessageBox(Handle, PChar(strText), '結果', MB_OK);
end;


数学的に正確な値は,どちらも 179.94 です.
  e:=(a+b+c)*d;    //(1)
  f:=a*d+b*d+c*d;  //(2)

しかし,結果の図のように Extended 型では e と f が違っています.
Double 型では同じ値になっていますが,数学的に正しい値ではありません.


[8072] Re:四捨五入返信 削除
2014/10/8 (水) 16:51:20 Mr.XRAY
今度は,64 ビットのEXE を作成して実行してみます.
やはり,e と f の表示が違います.


[8073] Re2:四捨五入返信 削除
2014/10/8 (水) 16:52:07 Mr.XRAY
>今度は,64 ビットのEXE を作成して実行してみます.
>やはり,e と f の表示が違います.


当サイトに次のような記事があります.
これとも関係してきます.

[890_計算誤差 ( 数値計算の誤差 ) と多倍長演算]
http://mrxray.on.coocan.jp/Delphi/plSamples/890_CalcError.htm

サンプルの

  e:=(a+b+c)*d;    //(1)
  f:=a*d+b*d+c*d;  //(2)

e と f の値は,数学的には同じでも,コンピュータ内部では異なる場合があります.
FloatToStr 関数は,数値を文字列にする際,15 桁で丸めます.
一方,SimpleRoundTo 関数は,元の値を使用して四捨五入します.

このことを利用すると,数値を一度 FloatToStr 関数で文字列にして,
再度実数にすれば,16 桁以上の精度があれば,計算結果の違いを回避すること
ができることになります.
今回のような単純な計算であれば利用可能でしょう.
そのテスト用コードです.

//-----------------------------------------------------------------------------
//  (1)と(2)の計算式は,数学的には等しく,179.935となる
//-----------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
  a,b,c,d,e,f:Extended;
  strText : String;
begin
  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)

  //StrToFloatは15桁で丸めを行うので15桁に丸めた文字列を取得する
  //その文字列を再度実数にする
  e := StrToFloat(FloatToStr(e));
  f := StrToFloat(FloatToStr(f));

  StrText :=  FloatToStr(e)+#13+
              FloatToStr(f)+#13+ sLineBreak +
              FloatToStr(SimpleRoundTo(e,-2))+#13+
              FloatToStr(SimpleRoundTo(f,-2));
  MessageBox(Handle, PChar(strText), '結果', MB_OK);
end;


下図が実行結果です.
指定小数点以上の精度が必要ないか,計算の結果の精度が分かっている場合,
次のように,計算の結果をその小数点の桁数に四捨五入してしまう方法も
考えられます.

  e := SimpleRoundTo(e, -10);
  f := SimpleRoundTo(f, -10);

この他,FloatToStrF 関数, FloatToText 関数 や FloatToTextFmt 関数で,
精度を指定する方法もあります.

   FloatToStrF(f, ffFixed, 10, 2))

は,10 桁の精度で,固定小数点形式の小数点 2 桁までの値の文字列を取得します.

[System.SysUtils.FloatToStrF - RAD Studio API Documentation]
http://docwiki.embarcadero.com/Libraries/XE6/ja/System.SysUtils.FloatToStrF




[8074] Re3:四捨五入返信 削除
2014/10/8 (水) 17:06:18 Mr.XRAY
> 下図が実行結果です.

重要なことは,サンプルコードの値を四捨五入した結果が違っていても,
これは文字列に変換した時の現象,問題です.
計算結果としての e, f の値を,次の計算に使用することには何ら問題はない
ことになります.


[8078] 計算結果をバイト値で確認返信 削除
2014/10/9 (木) 12:59:10 Mr.XRAY
>一方,SimpleRoundTo 関数は,元の値を使用して四捨五入します.
>そのために違いが生じることになります.


SimpleRoundTo 関数で使用する元の値を確認してみます.
元の値とは計算式による値のことです.
Delphi XE2 で実装された浮動小数点用レコードで計算結果の値をバイト値
で確認してみました.
図のように,e の値が 1 だけ小さい値になっています.
つまり,FloatToStr(SimpleRoundTo) の結果は,計算結果を正確に反映していると言えます.

[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

で,Extended 型のレコード型は TExtended80Rec なので,これを使用しています.
動作確認は,Windows 7 U6(SP1) + Delphi XE5 Pro (VCL-32) です.


//-----------------------------------------------------------------------------
//  (1)と(2)の計算式は,数学的には等しく,179.935となる
//  SimpleRoundToの使用にはusesにMathが必要
//  Delphi XE2 で実装された浮動小数点用レコードで計算結果の値を確認
//-----------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
  a,b,c,d,e,f:Extended;
   FF: TExtended80Rec;
   I: Integer;
   StrText : string;
 begin
  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)

  FF := TExtended80Rec(e);
  StrText := '';
  for I := 9 downto 0 do begin
    StrText := StrText + IntToHex(FF.Bytes[I], 2) + ' ';
  end;

  FF := TExtended80Rec(f);
  StrText := StrText + sLineBreak + '';
  for I := 9 downto 0 do begin
    StrText := StrText + IntToHex(FF.Bytes[I], 2) + ' ';
  end;

  StrText := StrText + sLineBreak + sLineBreak +
             FloatToStr(e)+#13+
             FloatToStr(f)+#13+ sLineBreak +
             FloatToStr(SimpleRoundTo(e,-2))+#13+
             FloatToStr(SimpleRoundTo(f,-2));

  MessageBox(Handle, PChar(StrText), '結果', MB_OK);
end;



[8082] Re:計算結果をバイト値で確認返信 削除
2014/10/9 (木) 15:47:49 Mr.XRAY
>Delphi XE2 で実装された浮動小数点用レコードで計算結果の値をバイト値
>で確認してみました.


Double 型であれば,FormatFloat を使用すれば実際の値を確認できます.
数学的な値は,197.935 です.
しかし,これがコンピュータによる計算の結果なのです.
当然ですが,どちらも,小数点 2 桁で四捨五入する(丸めると)
179.93 となります.


procedure TForm1.Button1Click(Sender: TObject);
var
  a,b,c,d,e,f:Double;
  strText : String;
begin
  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)

  StrText :=  FormatFloat('000.0000000000000000', e) + sLineBreak +
              FormatFloat('000.0000000000000000', f);
  MessageBox(Handle, PChar(strText), '結果', MB_OK);
end;



[▼次のスレッド]
INCM/CMT
Cyclamen v3.82