干支は「(西暦 − 4) を 10 と 12 で割った余り」で決まります。西暦3年の干支を求めるために、4言語で同じ1行を書きました。
(year - 4) % 12
西暦3年なら 3 − 4 = −1 です。結果は4通りに割れました。
| 言語 | (3 - 4) % 12 | 干支の配列の −1 番目を読むと | 返した干支 |
|---|---|---|---|
| Python | 11 | 正しく「亥」 | 癸亥(正解) |
| Go | −1 | index out of range [-1] でパニック | なし(処理が止まる) |
| JavaScript | −1 | undefined | NaN |
| PHP | −1 | 警告(Undefined array key -1)と null | 空文字 |
余り(%)の符号の決め方が違う
割り算の余りを、負の数でどう決めるかが言語で違います。
- Python: 余りは割る数と同じ符号になる。12 で割れば、必ず 0〜11
- Go・JavaScript・PHP: 余りは割られる数と同じ符号になる。−1 を 12 で割った余りは −1
(3 - 4) % 12 # 11(Python)
(3 - 4) % 12 // -1(JavaScript。Go・PHP も -1)
この題材では、Python の書き方がそのまま正解でした。ほかの3言語は、負の余りを配列の番号に使ったことで、それぞれの言語らしい壊れ方をしています。
- Go は止まります。間違った答えは返しません
- JavaScript は止まらず、
undefined + undefinedを計算して NaN を返します - PHP は警告を出しつつ、空の答えを返します
一番困るのは、黙って続く JavaScript と PHP です。画面に「NaN」や空欄が出ても、エラーとして記録されません。
西暦1〜3年は生まれ年としては現れませんが、「◯年前の干支」や紀元前をさかのぼる計算では、同じ形で負の数が入ります。
どう直したか
余りを、数学の剰余(0 以上)にそろえる関数を通してから使いました。
const mod = (n, m) => ((n % m) + m) % m;
mod(3 - 4, 12); // 11
直した後は、西暦1〜9999年の全9,999通りで、4言語とも正解と一致しました。
返品の「切り捨て」が、逆向きになった
負の数で困るのは、余りだけではありません。請求書の消費税の仕様では、返品のマイナスの金額で、4言語がそろって切り捨てと切り上げを逆にしました。
返品 −1,055円・10%の消費税は −105.5円です。日常の意味での切り捨ては −105円、切り上げは −106円 です(同じ額の請求をちょうど打ち消す値)。
| 正解 | 4言語の答え | |
|---|---|---|
| 切り捨て | −105円 | −106円 |
| 切り上げ | −106円 | −105円 |
各言語の floor(切り捨て)と ceil(切り上げ)は、数学の定義どおりに動きます。
floorは負の無限大の方へ丸める:−105.5 → −106ceilは正の無限大の方へ丸める:−105.5 → −105
正の数では、日常の切り捨て・切り上げと同じ結果になります。そのため、プラスの金額だけで確かめていると気づけません。
どう直したか
日常の意味は「0 の方へ切り捨てる」「0 から遠い方へ切り上げる」です。これに合う丸め方を名前で指定しました。
| 言語 | 切り捨て | 切り上げ |
|---|---|---|
| PHP | RoundingMode::TowardsZero | RoundingMode::AwayFromZero |
| Python | decimal の ROUND_DOWN | decimal の ROUND_UP |
| Go・JavaScript | 0 の方へ切り捨てる整数の割り算 | その結果に、符号を見て 1 を足す |
Python の ROUND_DOWN と ROUND_UP は、名前に DOWN・UP とありますが、どちらも絶対値を基準に丸めます(−105.5 → −105、−106)。
まとめ
- 余り(%)の符号は、Python は割る数に、Go・JavaScript・PHP は割られる数にそろう。配列の番号に使う前に 0 以上にそろえる
- 負の数で壊れたとき、Go は止まり、JavaScript は NaN、PHP は空文字を返した。黙って続く方が見つけにくい
floorとceilは負の数で、日常の切り捨て・切り上げと逆向きになる。マイナスの金額をテストに入れる
この記事のケースの正解表・4言語の結果・直す前と後のコードの違いは、次のページで見られます。
- 干支(十干・十二支・六十干支)(c006・c007・c008)
- 請求書の消費税と端数処理(c015・c017)
次回は、大きな整数で PHP が黙って上限に張りつき、JavaScript の桁が化けたケースを取り上げます。
- 第1回:「四捨五入」と書いたのに、4言語で答えが割れた
- 第2回:1,100 ÷ 1.1 が 1,000 にならない
- 第3回:負の数を入れたら、Go は止まり、JavaScript は NaN を返した(この記事)
- 第4回:大きな整数で、PHP は黙って上限に張りつき、JavaScript は桁が化けた
- 第5回:日本で動かしたときだけ、JavaScript の日付が1日ずれた
- 第6回:1月31日の「1か月後」はいつか
Analyzegear


