LoginSignup
1
3

More than 3 years have passed since last update.

まだまだ丸め誤差と闘わないといけない件について

Last updated at Posted at 2020-07-01

Difficulty 24の問題でコケてしまった悲しみ。

AtCoder Beginner Contest 165のB問題、問題名「1%」は、高橋くんが100円持っていて、銀行に預けると年利1%(複利)の利子がつくのですが、ある金額X円に達するには何年かかるか答えよ、という問題です。

自分で作った回答コードはこちら。

<?php
fscanf(STDIN, "%d", $X);
// 預金額
$money = 100;
// 利子(%)
$risoku = 1;
// 答え
$ans = 0;
while($X > $money) {
    $money += intval($money * ($risoku / 100));
    $ans++;
}
printf("%d", $ans);

結果、不正解となったコードですが、どこが間違っているか分かりますでしょうか?

利子を変数にして1%じゃなくても答えが出せるんだぜ的にしてしまったのが悪かった。

正解になったのはこちら。

<?php
fscanf(STDIN, "%d", $X);
// 預金額
$money = 100;
// 答え
$ans = 0;
while($X > $money) {
    $money += intdiv($money, 100);
    $ans++;
}
printf("%d", $ans);

変数とか使った私が愚かでした。単純に100で割って端数を切り捨てた整数を返してくれる intdiv を素直に使いました。

ちなみに、不正解となったテストケースは「99-after-contest-01.txt」という名前で、値Xは 974755271730884810 です。
具体的にいくらかというと、 97京4755兆2717億3088万4810円 ということなので、 ビル・ゲイツも真っ青なお金なので1年の誤差なんか気にすんな という気分になりますが、不正解は不正解。
テストケース名から分かる通り、コンテスト後に作られたテストケースなので、コンテスト時はいわゆる 嘘解法(テストケース的にはOKだけどコードとしては実際には間違っている、の意) で通した人も少なからずいるんでしょうね。

不正解のコードと正解のコードでは、3432年後から値に差が出始めます。(■がついているところは同値)
左が不正解のコードで、3431年後の金額、…66199円の1%は…661円なので、3432年後は …66199+…661=…860となるべきところ、…861となっています。

image.png

この影響で、差がどんどん広がり、不正解のコードでは 974755271730884810 に達するのは3757年後と算出するのに対し、正解のコードでは3758年後となるので、不正解となるのです。

image.png

いやぁ、不正解のコード、そんなに悪いコードですかねぇ🤒、という気分になってしまいますが、いまだに浮動小数点を含む演算には気をつけなければいけないケースがあるんだなぁ、ということは留意しておいた方が良いんですね、というお話でした。

1
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3