Re:Eric Lippertのクイズ

ネタ元:Eric Lippertのクイズ – 猫とC#について書くmatarilloの雑記

さて、どこにバグがあるでしょうか。

ヒント:ためしに実行してみればわかるかもよ。

ということなので、用意されたIdeoneのページで実行してみた結果、次のようになりました。

 0: 1988
 1: 996
 2: 969
 3: 1050
 4: 980
 5: 1001
 6: 1037
 7: 1031
 8: 1012
 9: 1056

結果を見ると、大体それぞれ1000前後分布していますが、0のところだけ2000前後、つまり倍になっています。

この結果を踏まえてコードをよく見ると、次のところが怪しいですね。

 

// 幅で割って(=幅の逆数を掛けて)整数化。
int index = (int)((datum – min) * multiplier);

 

まず、(datum – min) * multiplierの型はdouble型です。そして、その結果をint型にキャストしています。

double型からint型へのキャストの動作を確認してみましょう。

明示的な数値変換の一覧表 (C# リファレンス)

double 値または float 値を整数型に変換すると、値が切り捨てられます。

「切り捨て」の具体的な動作については記載されていませんが、「0に近い方の整数に丸められる」ということです。つまり、次の値のいずれも、切り捨てられて0になってしまうことになります。

  • 0.1
  • 0.9
  • -0.1
  • -0.9

したがって、「(datum – min) * multiplierの結果が負の1未満の小数である時も、0に分布するとカウントされてしまう」というバグがある、ということになります。

 

この問題を解消するには、例えばMath.Floorメソッドを使い、常に小さい整数に丸めてやるなどの方法があります。

 

int index = (int)Math.Floor((datum – min) * multiplier);

 

今回の例に限らず、明示的なキャストは常に値の精度の変更が伴うので、その挙動を十分に理解したうえで、慎重に使う必要があるということを再確認できた、良い問題でした。

id:matarilloさん、素敵な問題の紹介、ありがとうございました。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中