C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[48]: 四捨五入でバグ発見


(過去ログ 49 を表示中)

[トピック内 65 記事 (1 - 20 表示)]  << 0 | 1 | 2 | 3 >>

■26728 / inTopicNo.1)  四捨五入でバグ発見
  
□投稿者/ ラウンドワン (1回)-(2008/10/20(Mon) 08:06:39)

分類:[.NET 全般] 

お世話になります

指定した精度の数値に四捨五入する
http://jeanne.wankuma.com/tips/vb.net/math/halfadjust.html
のコードで

ToHalfAdjust(555.555, 2)
とすると
555.56
ではなく
555.55
と返ります。

報告まで






引用返信 編集キー/
■26729 / inTopicNo.2)  Re[1]: 四捨五入でバグ発見
□投稿者/ 774RR (227回)-(2008/10/20(Mon) 08:55:47)
バグといったら誤解を招きかねないな。
これは Double つまり二進表現による浮動小数点数を使う場合には不可避な
「精度」というか「表現力」の違いでしかない。
555.555 は二進数表現では無限桁が必要な循環小数になり、
プログラムの内部では5.55554999999999949e+002と扱われているわけだ。
四捨五入の結果は正しい。
引用返信 編集キー/
■26739 / inTopicNo.3)  Re[2]: 四捨五入でバグ発見
□投稿者/ ラウンドワン (2回)-(2008/10/20(Mon) 12:34:02)
2008/10/20(Mon) 12:34:52 編集(投稿者)

No26729 (774RR さん) に返信
> 四捨五入の結果は正しい。

どんな理屈ですか?
内部計算がどうあれ四捨五入ですから
vb.netが555.56の結果を返してこそ正解でしょう

引用返信 編集キー/
■26740 / inTopicNo.4)  Re[3]: 四捨五入でバグ発見
□投稿者/ みきぬ (151回)-(2008/10/20(Mon) 12:50:22)
Math.Round() が同じ結果を返すんで、その意味では正しいですね。
とか言ってみるテスト。

' コード
Console.WriteLine(ToHalfAdjust(555.555, 2))
Console.WriteLine(Math.Round(555.555, 2, MidpointRounding.AwayFromZero))

' 結果
555.55
555.55

引用返信 編集キー/
■26741 / inTopicNo.5)  Re[4]: 四捨五入でバグ発見
□投稿者/ R・田中一郎 (1回)-(2008/10/20(Mon) 12:57:47)
R・田中一郎 さんの Web サイト
>指定した精度の数値に四捨五入する

というタイトル通りの結果ではないかと思います。

引用返信 編集キー/
■26742 / inTopicNo.6)  Re[5]: 四捨五入でバグ発見
□投稿者/ やじゅ (711回)-(2008/10/20(Mon) 13:04:06)
やじゅ さんの Web サイト
2008/10/20(Mon) 13:15:05 編集(投稿者)

コンピュータによる四捨五入は、誤差の仕様があるってことを知らないと
結果が想定外になるので、不具合(バグ)と言われても仕方ないかも知れません。

これを機に勉強になって良かったんではないでしょうか。

じゃんぬねっとさんのが、小学生でならってきた四捨五入では無い
ということですね。
その旨を、サイト上に明記あれば良かったかも。


.NET Framework 2.0以降では、日本では一般的な、4以下を切り捨て5以上を切り上げる
「四捨五入」に対応しているでの、素直にMath.Roundを使えばいいんじゃない。
http://www.atmarkit.co.jp/fdotnet/dotnettips/700mathround/mathround.html
引用返信 編集キー/
■26744 / inTopicNo.7)  Re[4]: 四捨五入でバグ発見
□投稿者/ みきぬ (152回)-(2008/10/20(Mon) 13:14:19)
2008/10/20(Mon) 14:58:28 編集(投稿者)

' コード(VB2005にて確認)
Console.WriteLine(ToHalfAdjust(555.555D, 2))
Console.WriteLine(Math.Round(555.555D, 2, MidpointRounding.AwayFromZero))

' 結果
555.55
555.56

----- 後になって気づいたこと
DはDecimalだった!
アホみたいな確認ですが結果だけ残しておきます o... |rz
引用返信 編集キー/
■26745 / inTopicNo.8)  Re[3]: 四捨五入でバグ発見
□投稿者/ 裕猫 (83回)-(2008/10/20(Mon) 13:15:22)
No26739 (ラウンドワン さん) に返信
> 2008/10/20(Mon) 12:34:52 編集(投稿者)
>
> ■No26729 (774RR さん) に返信
>>四捨五入の結果は正しい。
>
> どんな理屈ですか?
> 内部計算がどうあれ四捨五入ですから
> vb.netが555.56の結果を返してこそ正解でしょう
>
パソコンの四捨五入は内部計算によるものなので774RRさんの 555.555 は二進数表現では無限桁が必要な循環小数になり、
プログラムの内部では5.55554999999999949e+002なので555.555になるのが正しいです。これはずいぶん昔からある現象で、プログラマーは算数の
四捨五入とプログラムの四捨五入は違うということを認識していなければならないことを覚えておいてください。業務系の計算プログラムで確実な
四捨五入をするためにはint型の計算にし、5を足したりして計算値をずらす処理をしなければなりません。パソコンは万能ではないので、まあ、愛嬌で
笑って済ませましょう。
引用返信 編集キー/
■26749 / inTopicNo.9)  Re[5]: 四捨五入でバグ発見
□投稿者/ はつね (863回)-(2008/10/20(Mon) 14:59:28)
はつね さんの Web サイト
2008/10/21(Tue) 09:06:33 編集(投稿者)

該当のURLを読むと次の説明があります。

|数値を丸めるのは、System.Math クラスの Round メソッドで可能です。
|しかし、この Round メソッドは「偶数丸め」(JIS 丸めともいう) と呼ばれる手法で数値を丸めます。

|この「偶数丸め」は日本国でも標準とされているのですが、残念ながらあまり知られていません。

偶数丸めとは、555.555だったら555.56、555.565だったら55.56になるものです。
# ですよね?


また、同じページにToHalfAdjustの説明もあります。

|この「偶数丸め」は日本国でも標準とされているのですが、残念ながらあまり知られていません。
|やはり、四捨五入が必要な業務が多かったりするわけです。
|以下は、指定した桁で四捨五入をするメソッドです。

作者の目論みとしてはToHalfAdjustの動作としては、555.555だったら555.56、555.565だったら555.57
なんじゃないかと思います。

ただし、私の解釈が誤っているのか(ページの説明文の可能性も含め)、それともプログラムの動作
がおかしいのかの判断がつききません(実行例が555.565相当の法なので)。
なので、バグかどうかの判断はできません。

じゃんぬねっとさんが見てくれているといいのですが。


#55.57を555.57に修正

引用返信 編集キー/
■26750 / inTopicNo.10)  Re[6]: 四捨五入でバグ発見
□投稿者/ みきぬ (154回)-(2008/10/20(Mon) 15:10:29)
2008/10/20(Mon) 15:12:14 編集(投稿者)
Console.WriteLine(ToHalfAdjust(555.555R, 2))
Console.WriteLine(Math.Round(555.555R, 2, MidpointRounding.AwayFromZero))

↑のコードを、MSILで覗いてみると、

  IL_0001:  ldc.r8     555.55499999999995
  IL_000a:  ldc.i4.2
  IL_000b:  call       float64 WindowsApplication1.Program::ToHalfAdjust(float64,
                                                                         int32)
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(float64)


  IL_0016:  ldc.r8     555.55499999999995
  IL_001f:  ldc.i4.2
  IL_0020:  ldc.i4.1
  IL_0021:  call       float64 [mscorlib]System.Math::Round(float64,
                                                            int32,
                                                            valuetype [mscorlib]System.MidpointRounding)
  IL_0026:  call       void [mscorlib]System.Console::WriteLine(float64)

なんですよね。555.555 という値では持っていないんですよ。

# MSILで見ることで、1つ前の書き込みの大ポカに気がついたのは余談ですが

引用返信 編集キー/
■26752 / inTopicNo.11)  Re[6]: 四捨五入でバグ発見
□投稿者/ 774RR (228回)-(2008/10/20(Mon) 15:13:06)
なにかいろいろ誤解している人が多いようだから蛇足

Q1. 555.555 という値があったとして、小数点下3桁目を四捨五入なら 555.56 になるべきか?
A1. Yes

Q2. では ToHalfAdjust(555.555, 2) が 555.55 を返すのは ToHalfAdjust のバグか?
A2. No

Q3. ではなぜ 555.55 になるのか?
A3. ソースコード上に書いた 555.555 という数値は、二進表記浮動小数点数では正確に表記できない。
コンパイル時点で正確さが失われ、プログラム内部では 555.554999999999949 という数値になっている。
555.55499 を小数点下3桁目で四捨五入すると 555.55 になるのは仕様どおり。

Q4. ソースコード上に書いた 555.555 という数値が 555.554999999999949 になるのはバグか?
A4. バグではなくて Double の仕様

つーことだ。

Double という「10進表記浮動小数点数を正確に表記できない型」を使うのをやめれば、
元発言者の期待通りの結果が得られるであろう。
というわけで答えを直接書かずに探して味噌、と書いておく。

引用返信 編集キー/
■26753 / inTopicNo.12)  Re[7]: 四捨五入でバグ発見
□投稿者/ やじゅ (712回)-(2008/10/20(Mon) 15:27:03)
やじゅ さんの Web サイト
2008/10/20(Mon) 15:27:21 編集(投稿者)

Double型の仕様ではあるけれど、
循環小数を考慮したうえで、555.56を返すべきじゃないのでだろうか
とも思ったりする。

引用返信 編集キー/
■26757 / inTopicNo.13)  Re[8]: 四捨五入でバグ発見
□投稿者/ 長月葵 (37回)-(2008/10/20(Mon) 15:56:07)
長月葵 さんの Web サイト
> Q2. では ToHalfAdjust(555.555, 2) が 555.55 を返すのは ToHalfAdjust のバグか?
> A2. No
 バグでしょう。やじゅさんに同じく555.56を返すべきだと思います。
 浮動小数点数の情報落ちは考慮しないと明記しておけばバグじゃないと思いますが。
引用返信 編集キー/
■26759 / inTopicNo.14)  Re[9]: 四捨五入でバグ発見
□投稿者/ Hongliang (306回)-(2008/10/20(Mon) 16:09:08)
ToHalfAdjust(555.55499999999999999, 2)
と区別がつけられない点について
引用返信 編集キー/
■26760 / inTopicNo.15)  Re[9]: 四捨五入でバグ発見
□投稿者/ 774RR (229回)-(2008/10/20(Mon) 16:09:10)
うーん。
・555.555 という値が *正確に* 渡されてきた場合に 555.56 という結果が得られない
・555.5549999 という値が渡されてきた場合に 555.56 という結果が得られた
のであれば、ToHalfAdjust のバグでしょう。
今ToHalfAdjustに渡されている値は 555.55499... なので、ToHalfAdjustのバグではないです。
Double を使うこと自体がバグ、ないしは仕様ミスであるというべきです。

> 循環小数を考慮したうえで、555.56を返すべきじゃないのでだろうか
そこんところは御意。
でも「いかなる数値に対しても」期待通りの結果を返す実装が、果たして現実的に可能か?
という問題が出てくるだけですな。

+0.5 ではなくて +0.5000001 とか書けば「この例では」 555.56 が得られる
けれども、いかなる場合でも 0.5000001 でうまくいくかはまた話が違う。
そのへんを考えていくとこの話は難しい問題をはらんでいるわけで。

数値を人間が手書きしたところの10進数表記との誤差が出てはならない計算を行う場合には
(金額計算とか)
Double (二進表記浮動小数点数) は使うべきでない、という当たり前の結論が出るだけだね。

引用返信 編集キー/
■26763 / inTopicNo.16)  Re[10]: 四捨五入でバグ発見
□投稿者/ みきぬ (155回)-(2008/10/20(Mon) 16:15:30)
No26759 (Hongliang さん) に返信
> ToHalfAdjust(555.55499999999999999, 2)
> と区別がつけられない点について

VisualStudio 2005 の IDE(たぶん Visual Basic Compiler)が勝手に555.555に置き換えてくれました。
引用返信 編集キー/
■26770 / inTopicNo.17)  Re[10]: 四捨五入でバグ発見
□投稿者/ f_yamaki (1回)-(2008/10/20(Mon) 18:32:09)
No26760 (774RR さん) に返信

> +0.5 ではなくて +0.5000001 とか書けば「この例では」 555.56 が得られる
> けれども、いかなる場合でも 0.5000001 でうまくいくかはまた話が違う。

情報処理の数値計算系の入門書にも出ていると思いますが、計算上の有効桁を
考慮して、”十分小さな値を加える”方法は広く使われています。
(774RRさんの仰るとおり、この”十分”がまた面倒な事ではありますが)
引用返信 編集キー/
■26771 / inTopicNo.18)  Re[11]: 四捨五入でバグ発見
□投稿者/ ラウンドワン (4回)-(2008/10/20(Mon) 18:48:59)
ひょっとしたらエクセルにもできないのかな?
と思ってエクセルで試してみました

=ROUND(555.555,2)

555.56
を返すんで

ToHalfAdjust(555.555, 2)

555.55
を返せば

これはバグと思う

引用返信 編集キー/
■26773 / inTopicNo.19)  Re[12]: 四捨五入でバグ発見
□投稿者/ みきぬ (156回)-(2008/10/20(Mon) 19:17:45)
No26771 (ラウンドワン さん) に返信

Excel は Excel で、ROUNDDOWN(58596.723, 3) が 58596.723 ではなく、58596.722 を返してくれるんですけどね。
あんまし Excel を盲信しちゃだめですよ?

参考URL:
http://pc.nikkeibp.co.jp/pc21/special/gosa/index.shtml
引用返信 編集キー/
■26774 / inTopicNo.20)  Re[13]: 四捨五入でバグ発見
 
□投稿者/ ラウンドワン (5回)-(2008/10/20(Mon) 19:49:40)
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=19008&forum=7



yamasaさんの

こういう場合は
「5万を加えて、10万で割り(int型のまま)、10万倍する」
のが定石です。

というのはどうなのでしょうか?
定石ということなのですが
引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 | 2 | 3 >>

管理者用

- Child Tree -