子どもの将来の身長の目安

父と母の身長から、子どもの将来の身長の目安(予測と ±5cm の幅)を出す。予測は小数第1位まで四捨五入。

2進小数の誤差0.5 の丸め方

言語ごとの結果

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

PHPPHP 8.4.26

よくある書き方8 / 14

直した書き方14 / 14

PythonPython 3.14.7

よくある書き方8 / 14

直した書き方14 / 14

GoGo 1.27.2

よくある書き方8 / 14

直した書き方14 / 14

JavaScriptNode 24.15.0

よくある書き方10 / 14

直した書き方14 / 14

JavaJava 21.0.12

よくある書き方12 / 14

直した書き方14 / 14

C#.NET 10.0.402

よくある書き方8 / 14

直した書き方14 / 14

RustRust 1.99.0

よくある書き方8 / 14

直した書き方14 / 14

RubyRuby 4.0.7

よくある書き方10 / 14

直した書き方14 / 14

KotlinKotlin 2.4.21

よくある書き方12 / 14

直した書き方14 / 14

SwiftSwift 6.3.3

よくある書き方8 / 14

直した書き方14 / 14

なぜ割れたのか

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

2進小数の誤差

予測は 154.4cm なのに、目安は 149.3〜159.3cm——予測と目安が食い違った

対象のケースc004c005c006c010 落ちた言語PHPPythonGoJavaScriptJavaC#RustRubyKotlinSwift

父 150cm・母 145.7cm の男の子(c004)の予測は (150 + 145.7 + 13) ÷ 2 = 154.35cm、四捨五入して 154.4cm、目安はその ±5cm で 149.4〜159.4cm が正解です。

10言語中7言語が外れ、そのうち6言語(PHP・Go・JavaScript・C#・Rust・Swift)は、予測を 154.4 と正しく出したのに、目安を 149.3〜159.3 と出しました。予測と目安で、丸め方が食い違っています。公開中の元ツールも同じ食い違いを出します(父 150.0〜190.0cm・母 145.0〜175.0cm を 0.1cm 刻み・男女の241,402通り中48,280通り)。Python は予測も 154.3 と、すべて1つ下になりました。女の子(c005)でも同じ7言語が外れました。

父 171.2cm・母 170.1cm(c006)の 177.15cm と、父 183.1cm・母 171.2cm(c010)の 183.65cm は、10言語すべてが予測を 177.1・183.6 と1つ下に出しました。

なぜずれたのか

154.35 は、2進数の小数では正確に表せず、154.349999999999994… として入ります。177.15 は 177.149999999999977…、183.65 は 183.649999999999977… です。どれも 0.05 の境目のわずかに下なので、2進数の中身をそのまま丸めると1つ下になります。

違いは、丸める関数が「2進数の中身」を丸めるか、「短い10進の書き方」(154.35)を丸めるかで生まれました。

書き方154.349999…(c004)149.349999…(c004 の目安)177.149999…(c006)
PHP round・JavaScript Math.round(x*10)/10・Go math.Round(x*10)/10・C# Math.Round(x, 1)・Rust (x*10.0).round()・Swift (x*10).rounded()154.4—177.1
目安の書式:PHP sprintf・JavaScript toFixed(1)・Go・Python・C#・Rust・Swift の %.1f 系—149.3—
Python round(x, 1)154.3—177.1
Java・Kotlin String.format("%.1f")・Ruby format("%.1f")154.4149.4177.1
  • 10倍してから整数に丸める書き方(x*10)は、154.349999… × 10 が 1543.5 ちょうどになるので、たまたま正しく 154.4 になりました。177.149999… × 10 は 1771.4999… のままなので、177.1 になりました
  • 小数1桁の書式(%.1f・toFixed(1))は、2進数の中身を丸めるので、目安の 149.349999… を 149.3 にしました。予測と目安を別の方法で丸めたので、食い違いました
  • Java・Kotlin の String.format と Ruby の format は、149.349999… を 149.4 にしました。短い10進の書き方(149.35)を丸めたのと同じ結果です。この2つは c004・c005 を通しましたが、c006・c010 では予測(Java Math.round(x*10)・Kotlin roundToLong()・Ruby round(1))が 1つ下になりました。Ruby は c006 で、予測 177.1・目安 172.2〜182.2 と、ここでも予測と目安が食い違いました

どう直したか

身長を2進数の小数に直さず、10進のまま計算しました。予測を1回だけ正確に四捨五入し、目安は丸めたあとの予測から ±5 します。こうすると、予測と目安が食い違うことはありません。

  • Python: fractions.Fraction、Go: big.Rat、Ruby: Rational(正確な分数)
  • Java・Kotlin: BigDecimal と RoundingMode.HALF_UP、C#: decimal と MidpointRounding.AwayFromZero
  • JavaScript: BigInt、PHP・Rust・Swift: 小数点の位置をそろえた整数

0.5 の丸め方

174.25cm を、Python と C# は 174.2cm にした——目安は7言語で 0.1cm 下に

対象のケースc007c008 落ちた言語PHPPythonGoC#RustRubySwift

父 175.3cm・母 160.2cm の男の子(c007)の予測は (175.3 + 160.2 + 13) ÷ 2 = 174.25cm、四捨五入して 174.3cm、目安は 169.3〜179.3cm が正解です。174.25 は2進数の小数でも正確に表せるので、誤差はありません。違いは 0.5 ちょうど(ここでは 0.05 ちょうど)の丸め方です。

10言語中7言語が外れました。女の子の 158.25cm(c008)でも、同じ7言語が外れました。

言語予測(正解 174.3)目安(正解 169.3〜179.3)
Java・Kotlin・JavaScript174.3169.3〜179.3(正解)
PHP・Go・Ruby・Rust・Swift174.3169.2〜179.2
Python・C#174.2169.2〜179.2

なぜ割れたのか

  • 予測: Python の round(x, 1) と C# の Math.Round(x, 1) は、0.5 ちょうどを偶数の方へ丸めます(174.25 → 174.2)。ほかの言語の丸め方(10倍して round)は 0 から遠い方へ丸めるので 174.3 でした
  • 目安: 小数1桁の書式(%.1f・format・{:.1} など)は、0.5 ちょうどを偶数の方へ丸めるものが多く、169.25 を 169.2 にしました(PHP sprintf・Go・Ruby・Rust・Swift・Python・C#)。JavaScript の toFixed(1) と Java・Kotlin の String.format は 0 から遠い方へ丸めるので、169.3 でした

PHP・Go・Ruby・Rust・Swift は、予測は 174.3 なのに目安は 169.2〜179.2 と、ここでも予測と目安が食い違いました(解説1 と同じ形)。

どう直したか

解説1 と同じく、10進のまま計算し、0.5 ちょうどを 0 から遠い方へ丸める方法を名前で指定しました(Java・Kotlin RoundingMode.HALF_UP、C# MidpointRounding.AwayFromZero など)。目安は丸めたあとの予測から ±5 します。

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

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

ケース入力正解よくある書き方の答え(10言語)
c001基本・男の子 (170 + 158 + 13) ÷ 2 = 170.5
father_cm
170
mother_cm
158
sex
boy
predict
170.5
low
165.5
high
175.5

✓10言語すべて正解

c002基本・女の子 (170 + 158 − 13) ÷ 2 = 157.5
father_cm
170
mother_cm
158
sex
girl
predict
157.5
low
152.5
high
162.5

✓10言語すべて正解

c003整数ちょうどでも小数第1位まで書く 169 → 169.0
father_cm
171
mother_cm
154
sex
boy
predict
169.0
low
164.0
high
174.0

✓10言語すべて正解

c0040.05 の境目・男の子 154.35 → 154.4。2進数の小数で計算すると 154.349999… になる。元ツールは予測 154.4 なのに目安を 149.3〜159.3 と出す なぜ? 解説を読む
father_cm
150
mother_cm
145.7
sex
boy
predict
154.4
low
149.4
high
159.4
  • PHP・Go・JavaScript・C#・Rust・Swiftlow"149.3"high"159.3"
  • Pythonpredict"154.3"low"149.3"high"159.3"
  • Java・Ruby・Kotlin✓ 正解
c0050.05 の境目・女の子 141.35 → 141.4 なぜ? 解説を読む
father_cm
150
mother_cm
145.7
sex
girl
predict
141.4
low
136.4
high
146.4
  • PHP・Go・JavaScript・C#・Rust・Swiftlow"136.3"high"146.3"
  • Pythonpredict"141.3"low"136.3"high"146.3"
  • Java・Ruby・Kotlin✓ 正解
c0060.05 の境目・2進数で小さく入る 177.15 → 177.2。2進数の小数で計算すると 177.149999… になる なぜ? 解説を読む
father_cm
171.2
mother_cm
170.1
sex
boy
predict
177.2
low
172.2
high
182.2
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Kotlin・Swiftpredict"177.1"low"172.1"high"182.1"
  • Rubypredict"177.1"
c0070.05 ちょうど(2進数でも正確)・男の子 174.25 → 174.3。偶数丸めだと 174.2 なぜ? 解説を読む
father_cm
175.3
mother_cm
160.2
sex
boy
predict
174.3
low
169.3
high
179.3
  • PHP・Go・Rust・Ruby・Swiftlow"169.2"high"179.2"
  • Python・C#predict"174.2"low"169.2"high"179.2"
  • JavaScript・Java・Kotlin✓ 正解
c0080.05 ちょうど(2進数でも正確)・女の子 158.25 → 158.3。偶数丸めだと 158.2 なぜ? 解説を読む
father_cm
170.2
mother_cm
159.3
sex
girl
predict
158.3
low
153.3
high
163.3
  • PHP・Go・Rust・Ruby・Swiftlow"153.2"high"163.2"
  • Python・C#predict"158.2"low"153.2"high"163.2"
  • JavaScript・Java・Kotlin✓ 正解
c009小数第2位までの入力 170.625 → 170.6
father_cm
170.25
mother_cm
158
sex
boy
predict
170.6
low
165.6
high
175.6

✓10言語すべて正解

c0100.05 の境目・高め 183.65 → 183.7。2進数の小数で計算すると 183.649999… になる なぜ? 解説を読む
father_cm
183.1
mother_cm
171.2
sex
boy
predict
183.7
low
178.7
high
188.7
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftpredict"183.6"low"178.6"high"188.6"
c011父 0cm はエラー
father_cm
0
mother_cm
158
sex
boy
error
invalid_input

✓10言語すべて正解

c012母がマイナスはエラー
father_cm
170
mother_cm
-158
sex
girl
error
invalid_input

✓10言語すべて正解

c013数でない入力はエラー
father_cm
abc
mother_cm
158
sex
boy
error
invalid_input

✓10言語すべて正解

c014空の入力はエラー
father_cm
170
mother_cm
sex
girl
error
invalid_input

✓10言語すべて正解

コード

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

Height.php+48 −14よくある書き方(af553d8)→ 直した書き方

@@ 変更前 3行目〜 / 変更後 3行目〜 @@
33
44 /**
55 * 子どもの将来の身長の目安。
6− * v1: 身長を float に読み、(父 + 母 ± 13) ÷ 2 を計算して、予測は round($x, 1)、目安の両端は元ツールと同じく
7− * (丸める前の予測 ± 5) を sprintf('%.1f') で書く素直な書き方。
6+ * v2: 身長の文字列を float に読まず、整数部と小数部の数字の列に分けて10進のまま足す。
7+ * float だと 150 + 145.7 + 13 の半分が 154.349999… になり、目安の sprintf('%.1f') が 149.3 に落ちる
8+ * (round() は補正して 154.4 にするので、予測と目安が食い違う)。174.25 のような 0.5 ちょうどは sprintf が偶数丸めで 174.2 にする。
9+ * 0.1 単位の四捨五入に要るのは「100倍して切り捨てた値」と「その下に 0 でない桁が残るか」だけなので、そこまでを整数にする。
10+ * 目安は丸めた予測 ± 5(0.1 単位の整数で ±50)。
811 */
12+const DECIMAL = '/^-?[0-9]+(\.[0-9]+)?$/';
13+
14+/** 桁数をそろえた 0 以上の数字の列どうしを、筆算で足す(桁数に上限を置かないため int にしない) */
15+function addDigits(string $a, string $b): string
16+{
17+ $n = max(strlen($a), strlen($b));
18+ $a = str_pad($a, $n, '0', STR_PAD_LEFT);
19+ $b = str_pad($b, $n, '0', STR_PAD_LEFT);
20+ $out = '';
21+ $carry = 0;
22+ for ($i = $n - 1; $i >= 0; $i--) {
23+ $s = (int) $a[$i] + (int) $b[$i] + $carry;
24+ $out = ($s % 10) . $out;
25+ $carry = intdiv($s, 10);
26+ }
27+ return ($carry ? '1' : '') . $out;
28+}
29+
30+/** 0.1 単位の整数を "154.4"・"-0.5" の形にする */
31+function tenthsText(int $n): string
32+{
33+ return ($n < 0 ? '-' : '') . intdiv(abs($n), 10) . '.' . abs($n) % 10;
34+}
935
1036 /** @param array{father_cm: string, mother_cm: string, sex: string} $in */
1137 function predictHeight(array $in): array
1238 {
13− if (!is_numeric($in['father_cm']) || !is_numeric($in['mother_cm'])) {
39+ $f = $in['father_cm'];
40+ $m = $in['mother_cm'];
41+ // 0 以下("-" で始まるもの、数字がすべて 0 のもの)もエラー
42+ if (!is_string($f) || !is_string($m) || !preg_match(DECIMAL, $f) || !preg_match(DECIMAL, $m)
43+ || $f[0] === '-' || $m[0] === '-' || trim($f, '0.') === '' || trim($m, '0.') === ''
44+ || !in_array($in['sex'], ['boy', 'girl'], true)) {
1445 return ['error' => 'invalid_input'];
1546 }
16− $father = (float) $in['father_cm'];
17− $mother = (float) $in['mother_cm'];
18− if ($father <= 0 || $mother <= 0) {
19− return ['error' => 'invalid_input'];
20− }
21− $p = $in['sex'] === 'boy' ? ($father + $mother + 13) / 2 : ($father + $mother - 13) / 2;
22− return [
23− 'predict' => sprintf('%.1f', round($p, 1)),
24− 'low' => sprintf('%.1f', $p - 5),
25− 'high' => sprintf('%.1f', $p + 5),
26− ];
47+ [$fi, $ff] = explode('.', $f . '.');
48+ [$mi, $mf] = explode('.', $m . '.');
49+ $len = max(strlen($ff), strlen($mf), 2);
50+ $sum = addDigits($fi . str_pad($ff, $len, '0'), $mi . str_pad($mf, $len, '0')); // (父 + 母) × 10^$len
51+ $cut = strlen($sum) - ($len - 2);
52+ $hundredths = (int) substr($sum, 0, $cut); // (父 + 母) × 100 の切り捨て
53+ $rest = trim(substr($sum, $cut), '0') !== ''; // 切り捨てた桁に 0 でないものがあるか
54+ $y = $hundredths + ($in['sex'] === 'boy' ? 1300 : -1300); // (父 + 母 ± 13) × 100 の切り捨て
55+ // |父 + 母 ± 13| × 100 の切り捨て。負のときは切り捨ての向きが逆になる
56+ $abs = $y >= 0 ? $y : -$y - ($rest ? 1 : 0);
57+ // 予測 × 10 = |…| × 100 ÷ 20。0.5 以上を繰り上げる(切り捨てた端数は 1 未満なので結果を変えない)
58+ $n = intdiv($abs + 10, 20);
59+ $p = $y >= 0 ? $n : -$n;
60+ return ['predict' => tenthsText($p), 'low' => tenthsText($p - 50), 'high' => tenthsText($p + 50)];
2761 }

仕様

入力

項目型説明
father_cm文字列父の身長(cm)。10進の数("170"・"170.5"・"170.25")
mother_cm文字列母の身長(cm)。同上
sex文字列boy(男の子)か girl(女の子)

出力

成功したとき: {"predict": "170.5", "low": "165.5", "high": "175.5"}

項目型定義
predict文字列男の子は (父 + 母 + 13) ÷ 2、女の子は (父 + 母 − 13) ÷ 2 を、小数第1位まで四捨五入した値
low文字列predict − 5
high文字列predict + 5
  • 3つとも、小数第1位まで必ず書く("169.0")
  • 四捨五入は、丸める前の正確な値に対して行う。0.5 ちょうどは 0 から遠い方へ(154.35 → 154.4、174.25 → 174.3)
  • low・high は四捨五入したあとの predict から ±5 する(正確な値 ±5 を丸めても同じ値になる)

判定できないとき: {"error": "invalid_input"}。父または母の身長が 10進の数として読めないとき(空・文字)、または 0 以下のとき。

元ツールとの違い

  • 元ツールは予測身長を roundHalfUp(2進数の誤差を補正して四捨五入)で丸めるが、目安の両端は (予測 ± 5).toFixed(1) で、補正なしに2進数の値を丸めている。予測が 0.05 刻みの境目にあると、目安だけ 0.1cm 小さくなる(上記)
  • 元ツールは入力を parseFloat で読む。本 spec は文字列を10進数として読み、読めなければエラーにする

期待値の作り方

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