「境界値判定ややこしいなぁ」どころじゃない
多分相当今更なネタなんですけど、わたしはあまり深く考えてなかったので。
この広告のやつね。ちょまどさんの絵かわいい。
リンク踏んで見ると、
「難易度 当該領域で、基礎的なことができれば解けると思われるレベル」
「JavaScriptの基礎知識を問います」
と書かれてるんですね。
それを踏まえると、
- 最初はiもcntも0
- ループ1回目、iは1より小さいのでcntが1になってiは0.1になる
- ループ2回目、iは1より小さいのでcntが2になってiは0.2になる
- …
- ループ10回目、iは1より小さいのでcntが10になってiは1.0になる
- ループ11回目…はiが1なので実行されず、cntは10
…というわけで、この問題の正答としては「10」でよいと思います。
と思ったら、正答は「11」になっているそうです。(解説はないらしいですが)
まあここからが問題なんですけど、
このコードを実際に動かすと、実は確かに「11」になります。
というのも、コンピュータは情報を2進数で扱っているので、小数の計算には弱いのです。
10進数と2進数は例えばこんな風に対応します。
10進数 | 2進数 |
1 | 1 |
2 | 10 |
3 | 11 |
4 | 100 |
8 | 1000 |
0.1 | 0.0001100110011... |
0.2 | 0.00110011... |
0.5 | 0.1 |
0.25 | 0.01 |
0.125 | 0.001 |
このように、10進数の0.1や0.2を2進数に変換すると、どこまでも続く小数。
でも無限の桁を計算しつづけるわけにはいかないので、
コンピュータはこれらの数字を正確に扱うことはできないのです。どこかで打ち切るしかない。
なので、小数の足し引きを行ううちに誤差が発生し、
> ループ10回目、iは1より小さいのでcntが10になってiは1.0になる
と思ったらiが実は0.999999999…になっていて、
意図しないループ11回目が発生してしまったりするわけです。
回避策としては、妥当なタイミングで「小数点以下第○位を四捨五入」のような処理を入れてやるのが妥当かと思います。
あとは「整数にして計算してから元に戻す」というのも手堅いですね。10倍して計算してから10で割るみたいな。
(個人的には、かえってバグの要因になりやすいと思うのでこの方法は好きではないです)
昔銀行のシステム開発やってたので、そーいやこのへんは相当厳密に計算してたわーというのを思い出した。最近は小数の計算とかやってないなー
…で、最初に書いたとおり正答が「11」になってるそうですが、
これ
「難易度 当該領域で、基礎的なことができれば解けると思われるレベル」
「JavaScriptの基礎知識を問います」
なんですか?
「小数演算の誤差は基礎知識のうち」なのか
「これはややこしいな! 何も考えず実行したら11になったから答えは11に設定するやで」なのかが問題。
後者だったらやばい。CodeIQの中の人のレベルが。