PHP
プログラミングコンテスト
整数
切り捨て
Floor

AGC019のAでコケました。

問題としては、

0.25リットルのジュースはQ円、
0.5リットルのジュースはH円、
1リットルのジュースはS円、
2リットルのジュースはD円。

ぴったりNリットルのジュースが欲しい場合、
一番安い組み合わせで買ったらいくらになりますか?(意訳)
ただし、Q、H、S、D、Nはすべて整数とする。

というもの。

解法としては、Nは整数なので、1リットルで一番安いものを計算します。具体的には

LITTER_1 = min(Q * 4, H * 2, S)

こんな感じ。
で、後は、2リットルと1リットルの単価を比較して計算する、という流れなんですけれども。

その中で、なぜか通らないコードの一部を抜粋したのが、以下です。

<?php

$litter1 = 99999995;
$litter2 = 99999997;
$question = 999999999;

// 1リッターが2つの方が高くつく場合
if ($litter1 * 2 > $litter2) {
    // 問題が偶数の場合
    if ($question % 2 == 0) {
        printf("%d\n", $question / 2 * $litter2);
    } else {
        printf("%d\n", floor($question / 2) * $litter2 + $litter1);
    }
} else {
    printf("%d\n", $question * $litter1);
}

正解は 49999998499999998 なんですが……

image.png

実行してみると、なんか 6 ずれてるんです。

image.png

なんでだろう、と思って、細かく見てみて、唖然。

$step1 = floor($question / 2);
printf("[1]%d\n", $step1);
printf("[2]%s\n", gettype($step1));
$step2 = $step1 * $litter2;
printf("[3]%d\n", $step2);
printf("%d\n", floor($question / 2) * $litter2 + $litter1);

image.png

999999999 を 2 で割って、床関数で切り捨てて、 499999999 を得ています。[1]
これはOK。
そこに 99999997 を掛けて…… [3]を見ると、明らかにおかしい。499999999 × 99999997 で最後にこんなに 0 が並ぶことは、ありえない。

[1]の時点での変数の型を調べてみると、doubleになってます。[2]

この時点で、何かちょっとおかしい。床関数を使って切り捨てている。整数を得ているはずなのに、なぜ double なのか。
マニュアルを見ると、
value をこえない最大の整数の値を (float 型で) 返します。
と書いてあります。
なぜfloat型なのか。
これは、 float の範囲が int よりも広いためです。
……???
ちょっと何を言ってるのか分かりませんが、浮動小数点の形で返されているせいで、正しい数を得ることができていません。

(int)でキャストしても良いですが、とりあえず整数型で返す関数を定義します。

function ifloor($value) {
    return intval($value);
}

こうすることによって、正しい値を得ることができました。

image.png

結論

floorceilを使うときは、いちいちintにキャストした方が良いよ。

いやぁ、こんな罠が潜んでいるとは……