干支(十干・十二支・六十干支)

西暦年から、十干・十二支・六十干支(甲子など)とその読みを出す。

負の数

言語ごとの結果

正解表 11件のうち、正解と一致した件数です。

PHPPHP 8.4.26

よくある書き方8 / 11

直した書き方11 / 11

PythonPython 3.14.7

よくある書き方11 / 11

直した書き方11 / 11

GoGo 1.27.2

よくある書き方8 / 11

直した書き方11 / 11

JavaScriptNode 24.15.0

よくある書き方8 / 11

直した書き方11 / 11

なぜ割れたのか

よくある書き方が落ちたケースを、原因ごとにまとめた解説です(1本)。書いてある「落ちた言語」が今回の検証結果と一致することを確かめてから載せています。

負の数

西暦1〜3年で、Go は止まり、JavaScript は NaN、PHP は空欄。Python だけが正しかった

対象のケースc006c007c008 落ちた言語PHPGoJavaScript

干支は「(西暦 − 4) を 10 と 12 で割った余り」で決まります。西暦3年なら 3 − 4 = −1 なので、余りは数学では 10 で割って 9(癸)、12 で割って 11(亥)、答えは 癸亥 です。

同じ (year - 4) % 12 という1行が、4言語で4通りの結果になりました。

言語(3 - 4) % 12配列の −1 番目を読むと返した干支
Python11正しく「亥」癸亥(正解)
Go−1index out of range [-1] でパニックなし(処理が止まる)
JavaScript−1undefinedundefined + undefined = NaN
PHP−1警告(Undefined array key -1)と null空文字

なぜ割れたのか

割り算の余り(%)を、負の数でどう定義するかが言語で違います。

  • Python: 余りは割る数と同じ符号になる。12 で割れば必ず 0〜11
  • Go・JavaScript・PHP: 余りは割られる数と同じ符号になる。−1 を 12 で割った余りは −1

この題材では、Python の「よくある書き方」がそのまま正解でした。ほかの3言語は、負の余りを配列の番号に使ったことで、それぞれの言語らしい壊れ方をしました。Go は止まり、JavaScript は黙って NaN を返し、PHP は警告を出しつつ空の答えを返しています。

西暦1〜3年は生まれ年としては現れませんが、「◯年前の干支」や紀元前をさかのぼる計算では、同じ形で負の数が入ります。

どう直したか

余りを数学の剰余(0 以上)にそろえる関数を通してから、配列の番号に使いました。

const mod = (n, m) => ((n % m) + m) % m;

仕様の元になったシルギアのツールは、最初からこの書き方で正しく計算しています。直した書き方は、西暦1〜9999年の全9,999通りで4言語とも正解と一致しました。

正解表と、よくある書き方の答え

正解は実装より先に決めて固定しています。言語の列の ✓ は正解と一致、赤い値はその言語が実際に返した間違った答えです。

ケース入力正解 PHPPythonGoJavaScript
c0012026年
year
2026
eto
丙午
reading
ひのえうま
kan
丙
shi
午
animal
うま
✓✓✓✓
c0021984年(甲子)
year
1984
eto
甲子
reading
きのえね
kan
甲
shi
子
animal
ねずみ
✓✓✓✓
c0032024年
year
2024
eto
甲辰
reading
きのえたつ
kan
甲
shi
辰
animal
たつ
✓✓✓✓
c0041966年(丙午)
year
1966
eto
丙午
reading
ひのえうま
kan
丙
shi
午
animal
うま
✓✓✓✓
c005西暦4年 西暦 − 4 = 0。甲子
year
4
eto
甲子
reading
きのえね
kan
甲
shi
子
animal
ねずみ
✓✓✓✓
c006西暦3年 西暦 − 4 = −1。(−1) mod 10 = 9 → 癸、(−1) mod 12 = 11 → 亥。言語の % だと −1 になりうる なぜ? 解説を読む
year
3
eto
癸亥
reading
みずのとい
kan
癸
shi
亥
animal
いのしし
eto""reading""kannullshinullanimalnull✓エラーetonullreadingnullkannullshinullanimalnull
c007西暦1年 西暦 − 4 = −3 → 辛酉 なぜ? 解説を読む
year
1
eto
辛酉
reading
かのととり
kan
辛
shi
酉
animal
とり
eto""reading""kannullshinullanimalnull✓エラーetonullreadingnullkannullshinullanimalnull
c008西暦2年 西暦 − 4 = −2 → 壬戌 なぜ? 解説を読む
year
2
eto
壬戌
reading
みずのえいぬ
kan
壬
shi
戌
animal
いぬ
eto""reading""kannullshinullanimalnull✓エラーetonullreadingnullkannullshinullanimalnull
c0099999年 上限
year
9999
eto
己亥
reading
つちのとい
kan
己
shi
亥
animal
いのしし
✓✓✓✓
c0102000年
year
2000
eto
庚辰
reading
かのえたつ
kan
庚
shi
辰
animal
たつ
✓✓✓✓
c0111900年
year
1900
eto
庚子
reading
かのえね
kan
庚
shi
子
animal
ねずみ
✓✓✓✓

コード

「違い」では、よくある書き方から直した書き方へ、どの行をどう変えたかを示しています(赤が消した行、緑が足した行、濃い色が行の中で変わった部分)。計算の部分だけを載せ、入出力を受け渡す部分は除いています。

Eto.php+5 −3よくある書き方(b7bba9a)→ 直した書き方

@@ 変更前 3行目〜 / 変更後 3行目〜 @@
33
44 /**
55 * 干支(十干・十二支・六十干支)。
6− * v1: よくある書き方((西暦 − 4) % 10・% 12 を配列の番号に使う)。
6+ * v2: % は負の数に負の余りを返す((−1) % 12 = −1)。数学の剰余(0 以上)に直してから配列の番号に使う。
7+ * (PHP 8 の fdiv / intdiv にも、負に強い剰余はない)
78 * @return array<string, string>
89 */
910 function calcEto(int $year): array
@@ 変更前 13行目〜 / 変更後 14行目〜 @@
1314 $shi = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
1415 $shiRead = ['ね', 'うし', 'とら', 'う', 'たつ', 'み', 'うま', 'ひつじ', 'さる', 'とり', 'いぬ', 'い'];
1516 $animal = ['ねずみ', 'うし', 'とら', 'うさぎ', 'たつ', 'へび', 'うま', 'ひつじ', 'さる', 'とり', 'いぬ', 'いのしし'];
16− $k = ($year - 4) % 10;
17− $s = ($year - 4) % 12;
17+ $mod = fn (int $n, int $m): int => (($n % $m) + $m) % $m; // 数学の剰余(0 <= 結果 < m)
18+ $k = $mod($year - 4, 10);
19+ $s = $mod($year - 4, 12);
1820 return ['eto' => $kan[$k] . $shi[$s], 'reading' => $kanRead[$k] . $shiRead[$s], 'kan' => $kan[$k], 'shi' => $shi[$s], 'animal' => $animal[$s]];
1921 }

仕様

入力

項目型説明
year整数西暦年。1〜9999

出力

項目型例(2026)
eto文字列六十干支(十干+十二支)"丙午"
reading文字列読み "ひのえうま"
kan文字列十干 "丙"
shi文字列十二支 "午"
animal文字列十二支の動物 "うま"

計算

  • 十干 = 甲 乙 丙 丁 戊 己 庚 辛 壬 癸(きのえ きのと ひのえ ひのと つちのえ つちのと かのえ かのと みずのえ みずのと)の (西暦 − 4) mod 10 番目(0 始まり)
  • 十二支 = 子 丑 寅 卯 辰 巳 午 未 申 酉 戌 亥(ね うし とら う たつ み うま ひつじ さる とり いぬ い)の (西暦 − 4) mod 12 番目
  • 動物は ねずみ うし とら うさぎ たつ へび うま ひつじ さる とり いぬ いのしし
  • mod は数学の剰余(0 以上、割る数より小さい)。西暦1〜3年では 西暦 − 4 が負になるが、結果は 0〜9・0〜11 に収める(西暦1年 → (−3) mod 12 = 9 → 酉)

元ツールは年齢(満年齢・数え年)も出すが、今日の日付に左右されるため本 spec では扱わない。

期待値の作り方

expect は oracle.py で上の定義をそのまま計算して生成した(2026-10-10)。実装ではない。runner は使わない。