小数点以下切捨てについて


  2006-07-06 03:31:45  No: 22402

お世話になっております。
浮動小数点の切捨てについて、質問です。

小数点13位以下を切り捨てる処理を行っております。
切捨ては小数点の位置から、文字列として処理を行い、ダブルに戻す処理を行っております。

Arg1: Double;
UsWork: String;
UsTen : Integer;

Arg1    := 0.000002 / 12;
UsWok   := FormatFloat('',Arg1);
UsTen   := Pos('.',UsWok);
Result  := StrToFloat(Copy(UsWok,1,UsTen + Arg2))

当初上記の方法で行っていたのですが、指数表記の場合うまく動かなくなりました。
小数点以下切捨ての方法としてどのような方法を取るのが、正しいのかどなたか、ご教授ください。
よろしくお願いいたします。

ちなみに、

Arg1    := 0.000002 / 12;
UsWok  := FloatToStrF(Arg1,ffFixed,18,18);
UsTen   := Pos('.',UsWok);
Result := StrToFloat(Copy(UsWok,1,UsTen + Arg2))

こんな方法は正しいのでしょうか。


  2006-07-06 03:33:21  No: 22403

追記です。
Arg2には有効桁数が入ります。

よろしくお願いいたします。


ママん  2006-07-06 06:13:36  No: 22404

ちと沼にはまりかけました。

function CutOffDecimalPlace(Value:Extended  ; DP:Byte):Extended;
var
  i:Integer;
  i64,p:Int64;
begin
  p:=1;
  for i:=0 to DP-1 do
    p:=p*10;
  i64:= Trunc(Value * p);
  Result := i64 / p;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  value:Extended;
begin
  value:= StrToFloat(Edit1.Text);
  Edit2.Text:=FloatToStr(CutOffDecimalPlace(value,13));
end;


  2006-07-07 00:42:32  No: 22405

返信ありがとうございます。
ママんさんの方法で切り捨てを行うと、誤差が発生します。
例えば、

var
  i:Integer;
  i64,Int64;

  d1, d2, D3, d4: double;
begin
  d1 := 10.2;
  d2 := 100;
  D3 := D1 * d2 / 100;

  i64:= Trunc(D3);
  ShowMessage( IntToStr(i64));

だと、1020ではなく1019となってしまいます。

実際にこちらで、行っている数値演算なのですが、

  UsD1 := 0.000000083333;
  UsD2 := 1.000000333332;
  UsD4 := UsD2 - 1;
  UsD5 := 0.000000083333 / UsD4;
  ShowMessage(FormatFloat('',UsD5));  →  0.25

  UsD6 := 1;
  UsD1 := 0.000000083333;
  UsD4 := 0.000000333332;
  UsD5 := 0.000000083333 / UsD4;
  ShowMessage(FormatFloat('',UsD5));  →  0.24999999999999

このような状態を回避するにはどうすればよいのでしょうか。


  2006-07-07 00:44:05  No: 22406

またまた訂正です...
数値は逆で、上の計算結果が、0.24999999999999999  下が0.25です。


ママん  2006-07-07 03:51:30  No: 22407

本題と違うべさ!
私のレスは小数の切り捨て処理です。
浮動小数の計算上の誤差は全く別問題です。
で、鳥さんが行いたいのは浮動小数の誤差を無くしたいって事なので
簡単にできる方法としては、整数を使う方法があります。
保障したい桁数だけ小数点をシフトさせて計算すればOKです。
ただしこの際も桁あふれには注意してください。

ちなみに
>>だと、1020ではなく1019となってしまいます。
10だろ〜!!!!
とりあえずこの手の計算をするのであれば"浮動小数 誤差"でググってください。当然ながらExcelで同じ計算をさせても同じ現象が起こります。

回避例
procedure TForm1.Button2Click(Sender: TObject);
var
  D1, D2, D3, D4, D5: Extended;
  i1, i2, i3, i4, i5: Int64;
  i0:Int64;
begin
  i0:= 10000000000000;
  D2 := 1.000000333332;
  i2 := Trunc(D2 * i0);
  //D4:=D2-1; //0.000000333332
  //D5 := 0.000000083333 / D4;
  i4 := i2 - 1*i0;
  D5 := Trunc(0.000000083333*i0) / i4;
  Edit1.Text:= FormatFloat('',D5);

  D4 := 0.000000333332;
  //D5 := 0.000000083333 / D4;
  i4 := Trunc(D4*i0);
  D5 := Trunc(0.000000083333*i0) / i4;
  Edit2.Text:= FormatFloat('',D5);
end;


HOta  2006-07-07 05:18:52  No: 22408

BCD型で計算すればどうですか?


  2006-07-07 09:41:20  No: 22409

度々の返信ありがとうございます。

>私のレスは小数の切り捨て処理です。
>浮動小数の計算上の誤差は全く別問題です。

どうも私はここを混同しているようで、また、ここがものすご−−−く疑問なのです。

先ほどの例ではD3 := D1 * d2 / 100;
と100で割っていますが、これはD3 := D1 * d2;
の間違いです。

で、この値を切り捨てると、1019となります。
これは、切捨て前の計算での誤差であるとすると、
小数点以下12位までを有効とした数値演算は
どうすればいいのでしょうか....
切り捨てる値が、本来の切捨てであればいいのですが、
誤差の値を切り捨ててしまうことも当然あるのではないでしょうか。
実値が1019.9999999であるものと、
誤差で1019.9999999であるものとの違いとはなんなのでしょうか。

なにかチグハグですが、よろしくお願いします。

整数部分の桁数が9桁あるためシフトで整数にすることができません..(できるのかな??)

HOTAさん、返信ありがとうございます。
BCDのこと早速しらべてみます。


kkk  2006-07-07 11:31:03  No: 22410

Delphiのバージョンによってサポートしてない場合がありますが SimpleRoundTo はどうでしょうか?
---
function SimpleRoundTo(const AValue: Double; const ADigit: TSimpleRoundToRange = -2): Double;
---
負の値の処理は注意が必要

https://www.petitmonte.com/bbs/answers?question_id=1435


kkk  2006-07-07 11:34:17  No: 22411

Delphi7 Helpより
--------------------------------------------------
SimpleRoundTo 関数は,非対称丸めを使って浮動小数点値を指定された桁数(10 の累乗)に丸めます。
説明
SimpleRoundTo 関数を呼び出すと,AValue を任意の桁で丸めることができます。
AValue は丸める値です。
ADigit は,AValue を丸める桁位置を 10 の累乗で示します。これは,- 37 から 37 まで( - 37 と 37 を含む)の任意の値とすることができます。
SimpleRoundTo は,非対称丸めを使って,適切な有効桁数を持つ 2 つの値のちょうど中間にある値の丸め方法を判断します。この方法では,常に大きい方の値に丸められます。
以下の例は,SimpleRoundTo の使い方を示しています。
--------------------------------------------------
[Expression]  値
SimpleRoundTo(1234567, 3)  1234000
SimpleRoundTo(1.234, -2)  1.23
SimpleRoundTo(1.235, -2)  1.24
SimpleRoundTo(-1.235, -2)  -1.23


kkk  2006-07-07 11:51:31  No: 22412

おまけ
-------------------
var
  i64,Int64;
  d1, d2, D3, d4: double;
begin
  d1 := 10.2;
  d2 := 100;
  D3 := D1 * d2 ;
  i64:= Trunc(D3);
  ShowMessage( IntToStr(i64));
-------------------
で1019になる件

Truncは function Trunc(X: Extended): Int64;
と定義されています。
doubleからExtendedに変換するときに誤差が発生しています。
  d1, d2, D3, d4: extended;
としてみてください。1020と表示されます


kkk  2006-07-07 12:06:21  No: 22413

連投スマソ
上の件 Truncで誤差が出ているのではないようです。
  d1 := 10.2;
  d2 := 100;
  D3 := D1 * d2 ;
ここまでですでに誤差が発生しています。
d1...d4をextendedにすることによって精度が上がるだけです。
同様にUsD1..UsD6も extended にすると見かけ上誤差が出てきません。
(こういうときはやはりBCDの方が良いかな)


  2006-07-11 18:09:49  No: 22414

みなさんありがとうござます。
処理の度に小数点13位以下を切り捨てとなると、
その13以下の値が、誤差なのか正当な数値なのか分からない限り、どうしようもないようです。

又、HOtaさんがおっしゃっておられた、BCD型を調べて、使用してみたところ、関数電卓の精度に限りなく近くなりました。
もう少し調べてみたいと思います。

返信が遅くなって申し訳ありませんでした。


ママん  2006-07-11 21:47:03  No: 22415

だから、浮動小数を調べれば分かることです。
浮動小数を使ってる限り真の正当な値が保障されることはありません。
でも、これは10進数を使っていても同じことが言えますけど。
1/3は表現できませんよね。
http://ja.wikipedia.org/wiki/%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0
だから有効桁数を決定する必要があるのです。
整数、浮動小数の特徴を知らないでこのような数値計算をすべきで無いというのが持論です。


jok  2006-07-11 22:54:20  No: 22416

> その13以下の値が、誤差なのか正当な数値なのか分からない限り、どうしようもないようです。

これはプログラミング以前の問題です。もちろん、精度は実装に依存しますが
どこまでの精度を必要とするか、はプログラミングとは関係ないです。それは
その計算をする人が決めることなのであって、切り捨てか四捨五入かなどの
具体的な実装方法とは、直接関係ありません。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加