傷病手当金(1日あたりの金額と総額)

0.5 の丸め方日付とタイムゾーン

言語ごとの結果

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

PHPPHP 8.4.26

よくある書き方23 / 23

直した書き方23 / 23

PythonPython 3.14.7

よくある書き方21 / 23

直した書き方23 / 23

GoGo 1.27.2

よくある書き方23 / 23

直した書き方23 / 23

JavaScriptNode 24.15.0

よくある書き方23 / 23

直した書き方23 / 23

JavaJava 21.0.12

よくある書き方23 / 23

直した書き方23 / 23

C#.NET 10.0.402

よくある書き方21 / 23

直した書き方23 / 23

RustRust 1.99.0

よくある書き方22 / 23

直した書き方23 / 23

RubyRuby 4.0.7

よくある書き方22 / 23

直した書き方23 / 23

KotlinKotlin 2.4.21

よくある書き方23 / 23

直した書き方23 / 23

SwiftSwift 6.3.3

よくある書き方22 / 23

直した書き方23 / 23

なぜ割れたのか

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

0.5 の丸め方

傷病手当金の「5円ちょうど」を、Python と C# だけが10円切り下げた(1日 6,687円が 6,680円に)

対象のケースc002c012 落ちた言語PythonC#

傷病手当金の1日あたりの金額は、標準報酬月額の平均を 30 で割って 10円未満を四捨五入し、その 2/3 を 1円未満を四捨五入して出します(協会けんぽの計算例のとおり)。

11か月が 300,000円、1か月が 309,000円の人(c002)は、平均が 300,750円で、÷ 30 = 10,025円。ちょうど 5円なので、四捨五入すると 10,030円、1日の支給額は 6,687円です。ところが、よく見かける書き方で、Python と C# だけが 10,020円・6,680円を返しました。30日分では 200,610円のところが 200,400円で、210円少なくなります。

言語よく見かける書き方÷30(c002)1日(c002)
Pythonround(x / 10) * 1010,0206,680
C#Math.Round(x / 10) * 1010,0206,680
ほかの8言語各言語の標準の四捨五入10,0306,687

12か月に満たない人(c012。4か月の平均 300,750円)でも同じでした。

なぜずれたのか

Python の round と C# の Math.Round は、何も指定しないと、ちょうど半分を偶数の側へ丸めます(銀行丸め)。1,002.5 は 1,002 に、1,003.5 は 1,004 になります。

$ python3 -c "print(round(1002.5), round(1003.5))"
1002 1004

C# も Math.Round(1002.5) は 1002 で、MidpointRounding.AwayFromZero を付けると 1003 になります。

今回は2進数の小数の誤差は関係ありません。300,750 ÷ 30 ÷ 10 = 1,002.5 は、2進数の小数でもちょうど表せます。JavaScript の Math.round・Java の Math.round・Kotlin の roundToLong・PHP の round・Ruby の round・Go の math.Round・Rust の round・Swift の rounded() は、正の数のちょうど半分を大きい方へ丸めるので、float のままでも正しく出ました。

なお、× 2/3 の段階は、÷30 の結果が10の倍数なので、ちょうど 0.5円になることはありません。ずれるのは ÷30 の段階だけです。

どう直したか

平均を割り算せず、「各月の合計 S」と「月数 n」の整数のまま持ち、四捨五入も整数だけで計算しました。

  • ÷30 を10円単位に: (S + 150n) ÷ 300n を整数の割り算で求めて 10 倍する(S ÷ 300n を、半分なら大きい方へ丸めたもの)
  • × 2/3 を1円単位に: (標準報酬日額 × 4 + 3) ÷ 6 を整数の割り算で求める

10言語とも、この形で23件中23件になりました。言語の丸めの既定に頼らないので、書き方が言語で変わりません。

日付とタイムゾーン

支給開始日の「2025-6-1」を Ruby と Swift が読み、「2025-02-29」を手で読んだ Rust が通した

対象のケースc016c017 落ちた言語RustRubySwift

傷病手当金の spec では、支給開始日を YYYY-MM-DD で受け取り、形が違う日付や実在しない日付は invalid_input にします。支給開始日で、12か月に満たない人の上限(令和6年度は30万円、令和7・8年度は32万円)が変わるので、日付を正しく読めることが計算の前提です。

よく見かける書き方で、3言語が日付の確かめを漏らしました。

ケース入力正解通してしまった言語
c017 形が違う2025-6-1invalid_inputRuby(Date.strptime)・Swift(DateFormatter)
c016 実在しない2025-02-29invalid_inputRust(手で分けて、日を 1〜31 だけ確かめた)

どれもエラーにならず、日付を読めたものとして計算まで進み、1日 6,667円という答えを返しました。

なぜずれたのか

  • Ruby の Date.strptime(s, "%Y-%m-%d") は、%m・%d に1桁の数も受け付けます。2025-6-1 は 2025年6月1日として読まれました。実在しない 2025-02-29 は Date::Error になります
  • Swift の DateFormatter(dateFormat = "yyyy-MM-dd")も、MM・dd に1桁の数を受け付け、2025-6-1 を読みました
  • Rust は標準ライブラリに日付の型がありません。- で分けて月 1〜12・日 1〜31 を確かめる書き方では、2月29日が平年に無いことまでは確かめられません

ほかの言語は、よく見かける書き方でも2つとも弾きました。Python の date.fromisoformat・Java と Kotlin の LocalDate.parse・Go の time.Parse("2006-01-02", …)・C# の DateTime.TryParseExact("yyyy-MM-dd") は、2桁でない月・日を受け付けず、実在しない日付もエラーにします。

$ ruby -rdate -e 'p Date.strptime("2025-6-1","%Y-%m-%d")'
#<Date: 2025-06-01 ((2460828j,0s,0n),+0s,2299161j)>
$ python3 -c "from datetime import date; date.fromisoformat('2025-6-1')" 2>&1 | tail -1
ValueError: Invalid isoformat string: '2025-6-1'

どう直したか

先に正規表現で 4桁-2桁-2桁 の形を確かめ、そのあとで実在する日付かを確かめました(Ruby は Date.valid_date?、Swift と Rust はうるう年を含めて月の日数を計算)。上限の期間(2024-04-01〜2027-03-31)との比較は、YYYY-MM-DD の形がそろっていれば文字列の大小で比べられるので、Swift と Rust はそうしています。

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

正解は実装より先に決めて固定しています。右の欄は、よくある書き方の答えを言語ごとにまとめたものです(赤い値は実際に返した間違った答え)。

ケース入力正解よくある書き方の答え(10言語)
c001基本・12か月とも30万円 300,000 ÷ 30 = 10,000。× 2/3 = 6,666.66… → 6,667(整数の割り算だと 6,666)
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000]
start
2025-06-01
days
30
standard_daily
10000
daily
6667
total
200010
capped
false

✓10言語すべて正解

c002÷30 が 5円ちょうど 平均 300,750 ÷ 30 = 10,025 → 10,030。偶数へ丸める関数だと 10,020 になり、1日 6,680円(正しくは 6,687円) なぜ? 解説を読む
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 309000]
start
2025-06-01
days
30
standard_daily
10030
daily
6687
total
200610
capped
false
  • Python・C#standard_daily10020daily6680total200400
  • PHP・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swift✓ 正解
c003平均が割り切れない 平均 300,833.33… ÷ 30 = 10,027.77… → 10,030。平均を先に円に丸めても同じ
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 305000, 305000]
start
2025-06-01
days
30
standard_daily
10030
daily
6687
total
200610
capped
false

✓10言語すべて正解

c004× 2/3 の端数が 0.33…(下に丸める) 平均 300,250 ÷ 30 = 10,008.33… → 10,010。× 2/3 = 6,673.33… → 6,673
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 303000]
start
2025-06-01
days
1
standard_daily
10010
daily
6673
total
6673
capped
false

✓10言語すべて正解

c00512か月に満たない・上限32万円 平均 500,000 は上限 320,000 より大きいので 320,000 を使う。÷ 30 = 10,666.66… → 10,670
months
[500000, 500000, 500000, 500000, 500000, 500000]
start
2025-05-01
days
10
standard_daily
10670
daily
7113
total
71130
capped
true

✓10言語すべて正解

c006上限の切り替えの前日(30万円) 令和6年度なので上限は 300,000
months
[500000, 500000, 500000, 500000, 500000, 500000]
start
2025-03-31
days
10
standard_daily
10000
daily
6667
total
66670
capped
true

✓10言語すべて正解

c007上限の切り替えの当日(32万円)
months
[500000, 500000, 500000, 500000, 500000, 500000]
start
2025-04-01
days
10
standard_daily
10670
daily
7113
total
71130
capped
true

✓10言語すべて正解

c00812か月に満たない・平均が上限より小さい 平均 200,000 をそのまま使う。÷ 30 = 6,666.66… → 6,670。× 2/3 = 4,446.66… → 4,447
months
[200000, 200000, 200000]
start
2025-05-01
days
10
standard_daily
6670
daily
4447
total
44470
capped
false

✓10言語すべて正解

c00912か月に満たない・平均が上限と同じ 平均が上限より大きくないので capped は false
months
[320000, 320000, 320000, 320000, 320000, 320000]
start
2025-05-01
days
10
standard_daily
10670
daily
7113
total
71130
capped
false

✓10言語すべて正解

c01012か月なら上限はない 最大の月額・最大の日数。1,390,000 ÷ 30 = 46,333.33… → 46,330
months
[1390000, 1390000, 1390000, 1390000, 1390000, 1390000, 1390000, 1390000, 1390000, 1390000, 1390000, 1390000]
start
2025-05-01
days
549
standard_daily
46330
daily
30887
total
16956963
capped
false

✓10言語すべて正解

c011日数が0
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000]
start
2025-06-01
days
0
standard_daily
10000
daily
6667
total
0
capped
false

✓10言語すべて正解

c01212か月に満たない・5円ちょうど 平均 300,750 は上限より小さい。÷ 30 = 10,025 → 10,030 なぜ? 解説を読む
months
[300000, 300000, 300000, 303000]
start
2026-05-01
days
30
standard_daily
10030
daily
6687
total
200610
capped
false
  • Python・C#standard_daily10020daily6680total200400
  • PHP・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swift✓ 正解
c013期間の最初の日
months
[500000, 500000, 500000, 500000, 500000, 500000]
start
2024-04-01
days
10
standard_daily
10000
daily
6667
total
66670
capped
true

✓10言語すべて正解

c014期間より前
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000]
start
2024-03-31
days
10
error
out_of_range

✓10言語すべて正解

c015期間より後
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000]
start
2027-04-01
days
10
error
out_of_range

✓10言語すべて正解

c016実在しない日付 なぜ? 解説を読む
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000]
start
2025-02-29
days
10
error
invalid_input
  • Rusterrornull
  • PHP・Python・Go・JavaScript・Java・C#・Ruby・Kotlin・Swift✓ 正解
c017日付の形が違う なぜ? 解説を読む
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000]
start
2025-6-1
days
10
error
invalid_input
  • Ruby・Swifterrornull
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Kotlin✓ 正解
c018月の額が0個
months
[]
start
2025-06-01
days
10
error
invalid_input

✓10言語すべて正解

c019月の額が13個
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000]
start
2025-06-01
days
10
error
invalid_input

✓10言語すべて正解

c020月の額に0がある
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 0]
start
2025-06-01
days
10
error
invalid_input

✓10言語すべて正解

c021月の額が上限を超える
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 1390001]
start
2025-06-01
days
10
error
out_of_range

✓10言語すべて正解

c022日数が負
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000]
start
2025-06-01
days
-1
error
invalid_input

✓10言語すべて正解

c023日数が上限を超える
months
[300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000, 300000]
start
2025-06-01
days
550
error
out_of_range

✓10言語すべて正解

コード

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

Allowance.php+18 −6よくある書き方(c9b0d03)→ 直した書き方

@@ 変更前 3行目〜 / 変更後 3行目〜 @@
33
44 /**
55 * 傷病手当金(1日あたりの金額と総額)。
6− * v1: 平均は float(array_sum / count)、10円単位は round($x / 10) * 10、1円単位は round($x)。
6+ * v2: 平均を float にせず、合計(分子)と個数(分母)の int のまま持つ。上限との比較も 合計 > 上限 × 個数 で比べる。
7+ * 10円未満の四捨五入は 分子 ÷ (分母 × 300) を、1円未満は standard_daily × 2 ÷ 3 を、
8+ * intdiv(2 × 分子 + 分母, 2 × 分母) の整数の割り算で丸める(ちょうど半分は大きい方へ)。
79 * 日付は preg_match で形を確かめてから checkdate。
10+ * v1(平均は array_sum / count、round($x / 10) * 10)でも23件中23件だった。PHP の round は 0.5 を0から遠い方へ丸め、
11+ * 1002.5 も float で正確に表せるため。平均が float で割り切れない値になると誤差が入りうるので整数にした。
812 */
13+
14+/** $num / $den(0 以上)を四捨五入した整数。ちょうど半分は大きい方へ */
15+function roundHalfUp(int $num, int $den): int
16+{
17+ return intdiv(2 * $num + $den, 2 * $den);
18+}
19+
920 const CAPS = [
1021 ['2024-04-01', '2025-03-31', 300000],
1122 ['2025-04-01', '2026-03-31', 320000],
@@ 変更前 45行目〜 / 変更後 56行目〜 @@
4556 return ['error' => 'out_of_range'];
4657 }
4758
48− $avg = array_sum($months) / count($months);
49− $capped = count($months) < 12 && $avg > $cap;
50− $base = $capped ? $cap : $avg;
51− $standardDaily = (int)(round($base / 30 / 10) * 10);
52− $daily = (int)round($standardDaily * 2 / 3);
59+ $total = array_sum($months);
60+ $n = count($months);
61+ $capped = $n < 12 && $total > $cap * $n;
62+ [$num, $den] = $capped ? [$cap, 1] : [$total, $n];
63+ $standardDaily = roundHalfUp($num, $den * 300) * 10;
64+ $daily = roundHalfUp($standardDaily * 2, 3);
5365 return ['standard_daily' => $standardDaily, 'daily' => $daily, 'total' => $daily * $days, 'capped' => $capped];
5466 }

仕様

入力

項目型説明
months整数の配列支給開始日の属する月以前の直近の、継続した各月の標準報酬月額(円)。1〜12個。12個なら12か月以上の加入、それより少なければ加入期間がその月数
start文字列支給開始日 YYYY-MM-DD
days整数支給する日数(待期の3日を除く)

出力

成功したとき(12か月とも 300,000円・30日): {"standard_daily": 10000, "daily": 6667, "total": 200010, "capped": false}

項目定義
cappedmonths が12個より少なく、その平均が下の「上限」より大きいとき true。それ以外は false
standard_daily基準の月額 B ÷ 30 を、10円未満を四捨五入した額(円)。B は months の平均。ただし capped が true なら上限の額
dailystandard_daily × 2/3 を、1円未満を四捨五入した額(1日あたりの支給額・円)
totaldaily × days(円)
  • 平均は正確な値(分数)のまま ÷ 30 する。平均を先に丸めない
  • 四捨五入は、ちょうど半分(5円・0.5円)なら大きい方へ(10,025 → 10,030)
  • standard_daily は10の倍数なので、× 2/3 の値がちょうど 0.5円になることはない

上限(12か月に満たないとき)

協会けんぽの「支給開始日の属する年度の前年度9月30日における全被保険者の標準報酬月額の平均額」。

支給開始日上限
2024-04-01〜2025-03-31(令和6年度)300,000円
2025-04-01〜2026-03-31(令和7年度)320,000円
2026-04-01〜2027-03-31(令和8年度)320,000円

判定できないとき:

  • {"error": "invalid_input"}: months が0個か13個以上、months に1より小さい数がある、start が YYYY-MM-DD の形でない・実在しない日付、days が負
  • {"error": "out_of_range"}: start が上の表の期間の外、months に 1,390,000(健康保険の標準報酬月額の上限)より大きい数がある、days が 549 より大きい(1年6か月の日数を超える)
  • 入力の数は JSON の整数だけを使う(小数の入力はケースに入れない)
  • エラーの判定は、invalid_input を out_of_range より先にする

元ツールとの違い

  • 元ツールは「12か月の平均」を直接入れる。本 spec は各月の額を入れて平均も計算し、12か月に満たないときの上限も扱う
  • 元ツールの丸めは Math.round(0.5 は大きい方へ)なので、5円ちょうどの扱いは本 spec と同じ

期待値の作り方

expect は oracle.py で、Python の fractions.Fraction(正確な分数)で計算して生成した(2026-11-06)。実装ではない。runner は使わない。

出典

  • 協会けんぽ「病気やケガで会社を休んだとき(傷病手当金)」— 計算例「÷30(10円未満四捨五入)」「×2/3(1円未満四捨五入)」と、12か月に満たないときの30万円・32万円(2026-11-06 に確認)
  • 令和8年度の基準額が32万円で変わらないことは、協会けんぽの公表(2025-12-10)を紹介する記事で確認した