コンクリート・生コンの必要量

面積と厚さからコンクリートの体積とリットルを出し、1袋の出来上がり量から必要な袋数を切り上げで出す。

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

言語ごとの結果

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

PHPPHP 8.4.26

よくある書き方9 / 16

直した書き方16 / 16

PythonPython 3.14.7

よくある書き方8 / 16

直した書き方16 / 16

GoGo 1.27.2

よくある書き方9 / 16

直した書き方16 / 16

JavaScriptNode 24.15.0

よくある書き方9 / 16

直した書き方16 / 16

JavaJava 21.0.12

よくある書き方9 / 16

直した書き方16 / 16

C#.NET 10.0.402

よくある書き方8 / 16

直した書き方16 / 16

RustRust 1.99.0

よくある書き方9 / 16

直した書き方16 / 16

RubyRuby 4.0.7

よくある書き方9 / 16

直した書き方16 / 16

KotlinKotlin 2.4.21

よくある書き方9 / 16

直した書き方16 / 16

SwiftSwift 6.3.3

よくある書き方9 / 16

直した書き方16 / 16

なぜ割れたのか

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

2進小数の誤差

3袋ちょうどなのに、10言語とも「4袋」と答えた

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

面積 3m²・厚さ 10cm のコンクリートは 0.3m³ です。1袋で 0.1m³ できるインスタントセメントなら、0.3 ÷ 0.1 = 3袋 ちょうどです(c002)。10言語すべてが 4袋 と答えました。

0.15m³ ÷ 0.05m³(c003)、0.07m³ ÷ 0.01m³(c004)、0.3m³ ÷ 0.025m³(c006)も、10言語とも同じように1袋多くなりました。

なぜ1袋増えたのか

袋数は「足りないと困る」ので切り上げます。ところが、割り算の結果が2進数の小数でわずかに大きくなると、切り上げで次の整数に上がります。

計算正確な値2進数の小数での結果切り上げ
3 × (10 ÷ 100)0.30.30000000000000004
0.3 ÷ 0.133.00000000000000044

0.1 も 0.3 も2進数では正確に表せません。よくある書き方では、10言語とも体積を2進数の小数で計算しました(Java・C#・Kotlin・Swift は double/Double、Rust は f64、Ruby は Float)。C# は、金額ではない量なので double を選んでいます。どの言語も同じ規格の小数を使うので、そろって同じ答えになりました。

仕様の元になったシルギアの公開ツールも同じ式で計算していて、面積(1〜30m²)・厚さ(3〜20cm)・1袋の量(10L〜0.5m³)のよくある組み合わせ1,080通りのうち、62通りで1袋多く出ることを確かめました(2026-10-15)。

切り捨てや四捨五入より、切り上げの方がこの誤差を拾いやすい点に注意が必要です。「ちょうど割り切れる」値は人が入力しやすい値なので、実際によく現れます。

どう直したか

面積・厚さ・1袋の量を、十進の数のまま計算しました。0.3 ÷ 0.1 は正確に 3 になり、切り上げても3袋です。

使ったもの言語
十進の小数PHP(BcMath\Number)・Java と Kotlin(BigDecimal)・C#(decimal)
分数Python(fractions.Fraction)・Go(math/big.Rat)・Ruby(Rational)
整数で組んだ分数JavaScript(BigInt)・Rust(i128)・Swift(Int128)

2進小数の誤差

0.1125m³ が 0.112 に、112.5L が 112L になった

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

面積 1.5m²・厚さ 7.5cm(c009)の体積は 0.1125m³ です。小数第3位までに四捨五入すると 0.113m³、リットルでは 112.5L を四捨五入して 113L です。10言語すべてが 0.112m³・112L と答えました。

0.7m² × 1.5cm(c011)の 0.0105m³ = 10.5L、4.5m² × 7.5cm(c012)の 0.3375m³ = 337.5L も、10言語とも同じように切り捨てられました。

なぜ切り捨てられたのか

よくある書き方では、10言語とも体積を2進数の小数(double・f64・Float など)で計算しました。

計算正確な値2進数の小数での結果
1.5 × (7.5 ÷ 100)0.11250.11249999999999999
0.7 × (1.5 ÷ 100)0.01050.010499999999999999
4.5 × (7.5 ÷ 100)0.33750.33749999999999997

0.5 ちょうどのはずの端数が、わずかに 0.5 に届きません。どの言語の四捨五入でも、0.5 未満は切り捨てます。

体積とリットルは同じ値から出しているので、表示の2か所がそろってずれました。

どう直したか

袋数の解説と同じく、10言語とも十進の数(または分数)のまま計算してから四捨五入しました。1.5 × 7.5 ÷ 100 は正確に 0.1125 になり、0.113・113L になります。

0.5 の丸め方

12.5L を、Python と C# だけ 12L にした

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

面積 0.25m²・厚さ 5cm(c010)の体積は 0.0125m³ = 12.5L です。四捨五入して 13L が正解です。10言語中8言語は 13L を返しましたが、Python と C# の2言語は 12L を返しました。C# は、体積の表示も 0.013m³ ではなく 0.012m³ にしました。

なぜ割れたのか

0.0125 × 1000 = 12.5 は、2進数の小数でも正確に表せます(誤差はありません)。違いは、0.5 ちょうどの丸め方です。

丸め方言語と書き方12.5 →
0.5 ちょうどを偶数の方へPython の round()・C# の Math.Round12
0.5 ちょうどを大きい方へPHP の round()・Go の math.Round()・JavaScript の Math.round()・Java の Math.round・Rust の f64::round・Ruby の round・Kotlin の roundToLong()・Swift の rounded()13

Python 3 の round() と C# の Math.Round は、既定で 0.5 ちょうどを偶数の方へ丸めます(12.5 → 12、13.5 → 14)。

C# は体積も Math.Round(x, 3) で小数第3位に丸めたので、0.0125 が 0.012 になりました。Python の体積(round(x, 3))は 0.013 で、こちらは正解しています。

前の解説(0.1125m³ が 0.112 に)では、2進数の誤差のために10言語がそろって外れました。こちらは誤差のない値で、丸め方の違いだけで外れたケースです。

どう直したか

Python では round() を使わず、分数に 1/2 を足して切り捨てる形で四捨五入しました。

def half_up(x):
    return math.floor(x + Fraction(1, 2))

C# では decimal で計算し、Math.Round(x, MidpointRounding.AwayFromZero) と、0.5 ちょうどを大きい方へ丸めると明示しました。

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

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

ケース入力正解よくある書き方の答え(10言語)
c001基本・10m² × 10cm
area
10
thickness_cm
10
loss_pct
0
bag_m3
0
volume
1
liters
1000
loss_volume
1
loss_liters
1000
bags
null

✓10言語すべて正解

c002袋数・割り切れる(0.1m³ の袋) 0.3 ÷ 0.1 = 3袋ちょうど。2進数の小数では 3.0000000000000004 になり、切り上げると4袋 なぜ? 解説を読む
area
3
thickness_cm
10
loss_pct
0
bag_m3
0.1
volume
0.3
liters
300
loss_volume
0.3
loss_liters
300
bags
3
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftbags4
c003袋数・割り切れる(0.05m³ の袋) 0.15 ÷ 0.05 = 3袋 なぜ? 解説を読む
area
3
thickness_cm
5
loss_pct
0
bag_m3
0.05
volume
0.15
liters
150
loss_volume
0.15
loss_liters
150
bags
3
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftbags4
c004袋数・割り切れる(10L の袋) 0.07 ÷ 0.01 = 7袋 なぜ? 解説を読む
area
1
thickness_cm
7
loss_pct
0
bag_m3
0.01
volume
0.07
liters
70
loss_volume
0.07
loss_liters
70
bags
7
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftbags8
c005袋数・割り切れない 0.14 ÷ 0.05 = 2.8 → 3袋
area
2
thickness_cm
7
loss_pct
0
bag_m3
0.05
volume
0.14
liters
140
loss_volume
0.14
loss_liters
140
bags
3

✓10言語すべて正解

c006袋数・25L の袋 0.3 ÷ 0.025 = 12袋ちょうど なぜ? 解説を読む
area
3
thickness_cm
10
loss_pct
0
bag_m3
0.025
volume
0.3
liters
300
loss_volume
0.3
loss_liters
300
bags
12
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftbags13
c007幅 × 奥行で入力 8.64m² × 0.12m = 1.0368m³ → 1.037。リットルは 1036.8 → 1037
width
2.4
depth
3.6
thickness_cm
12
loss_pct
0
bag_m3
0
volume
1.037
liters
1037
loss_volume
1.037
loss_liters
1037
bags
null

✓10言語すべて正解

c008ロス率 5%
area
10
thickness_cm
10
loss_pct
5
bag_m3
0
volume
1
liters
1000
loss_volume
1.05
loss_liters
1050
bags
null

✓10言語すべて正解

c009体積の四捨五入・0.5 ちょうど 0.1125m³ → 0.113、112.5L → 113。2進数の小数では 0.11249999999999999 になる なぜ? 解説を読む
area
1.5
thickness_cm
7.5
loss_pct
0
bag_m3
0
volume
0.113
liters
113
loss_volume
0.113
loss_liters
113
bags
null
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftvolume"0.112"liters112loss_volume"0.112"loss_liters112
c010リットルの四捨五入・0.5 ちょうど 0.0125m³ = 12.5L → 13L。2進数でも正確に表せる値。偶数丸めなら 12 なぜ? 解説を読む
area
0.25
thickness_cm
5
loss_pct
0
bag_m3
0
volume
0.013
liters
13
loss_volume
0.013
loss_liters
13
bags
null
  • Pythonliters12loss_liters12
  • C#volume"0.012"liters12loss_volume"0.012"loss_liters12
  • PHP・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swift✓ 正解
c011小さな体積 0.0105m³ → 0.011、10.5L → 11 なぜ? 解説を読む
area
0.7
thickness_cm
1.5
loss_pct
0
bag_m3
0
volume
0.011
liters
11
loss_volume
0.011
loss_liters
11
bags
null
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftvolume"0.01"liters10loss_volume"0.01"loss_liters10
c012ロス率に小数 0.3375m³、ロス込み 0.3628125m³。袋数はロスを含まない体積から 13.5 → 14袋 なぜ? 解説を読む
area
4.5
thickness_cm
7.5
loss_pct
7.5
bag_m3
0.025
volume
0.338
liters
338
loss_volume
0.363
loss_liters
363
bags
14
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftvolume"0.337"liters337
c013末尾の 0 を省く 0.15(0.150 ではない)
area
1.5
thickness_cm
10
loss_pct
0
bag_m3
0
volume
0.15
liters
150
loss_volume
0.15
loss_liters
150
bags
null

✓10言語すべて正解

c014広い面積 150m³・6000袋
area
1000
thickness_cm
15
loss_pct
10
bag_m3
0.025
volume
150
liters
150000
loss_volume
165
loss_liters
165000
bags
6000

✓10言語すべて正解

c015厚さ 0 はエラー
area
10
thickness_cm
0
loss_pct
0
bag_m3
0
error
invalid_input

✓10言語すべて正解

c016ロス率が負はエラー 元ツールは 0 として計算する
area
10
thickness_cm
10
loss_pct
-5
bag_m3
0
error
invalid_input

✓10言語すべて正解

コード

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

Concrete.php+15 −10よくある書き方(646c770)→ 直した書き方

@@ 変更前 1行目〜 / 変更後 1行目〜 @@
11 <?php
22 declare(strict_types=1);
33
4+use BcMath\Number;
5+
46 /**
57 * コンクリート・生コンの必要量。
6− * v1: (float) で数にして計算する素直な書き方(体積は round($x, 3)、袋数は ceil)。
8+ * v2: 入力の小数を BcMath\Number(十進)のまま計算し、四捨五入と切り上げを十進で行う。
9+ * float だと 0.3 ÷ 0.1 が 3.0000000000000004 になって切り上げで4袋になり、0.1125 が 0.11249999999999999 になって四捨五入で外れる。
10+ * 割り算(体積 ÷ 1袋の量)は割り切れないことがあるので、切り上げに影響しない桁数(30桁)まで出す。
711 */
8−function fmtVol(float $n): string
12+function fmtVol(Number $n): string
913 {
10− return rtrim(rtrim(sprintf('%.3f', round($n, 3)), '0'), '.');
14+ return rtrim(rtrim($n->round(3, RoundingMode::HalfAwayFromZero)->value, '0'), '.');
1115 }
1216
1317 /** @param array<string, string> $in */
1418 function calcConcrete(array $in): array
1519 {
16− $a = isset($in['area']) ? (float) $in['area'] : (float) $in['width'] * (float) $in['depth'];
17− $t = (float) $in['thickness_cm'];
18− $loss = (float) $in['loss_pct'];
19− $bag = (float) $in['bag_m3'];
20+ $a = isset($in['area']) ? new Number($in['area']) : (new Number($in['width'])) * new Number($in['depth']);
21+ $t = new Number($in['thickness_cm']);
22+ $loss = new Number($in['loss_pct']);
23+ $bag = new Number($in['bag_m3']);
2024 if ($a <= 0 || $t <= 0 || $loss < 0 || $bag < 0) {
2125 return ['error' => 'invalid_input'];
2226 }
23− $v = $a * ($t / 100);
27+ $v = $a * $t / 100;
2428 $lv = $v * (1 + $loss / 100);
25− return ['volume' => fmtVol($v), 'liters' => (int) round($v * 1000), 'loss_volume' => fmtVol($lv),
26− 'loss_liters' => (int) round($lv * 1000), 'bags' => $bag > 0 ? (int) ceil($v / $bag) : null];
29+ $toInt = fn (Number $x): int => (int) $x->round(0, RoundingMode::HalfAwayFromZero)->value;
30+ return ['volume' => fmtVol($v), 'liters' => $toInt($v * 1000), 'loss_volume' => fmtVol($lv),
31+ 'loss_liters' => $toInt($lv * 1000), 'bags' => $bag > 0 ? (int) $v->div($bag, 30)->ceil()->value : null];
2732 }

仕様

入力

小数はすべて文字列で渡す("2.4")。

項目型説明
area文字列(小数)または無し面積(m²)。あれば width・depth より優先する
width文字列(小数)または無し幅(m)。area が無いとき、面積 = 幅 × 奥行
depth文字列(小数)または無し奥行(m)
thickness_cm文字列(小数)厚さ(cm)
loss_pct文字列(小数)ロス率(%)。0 以上
bag_m3文字列(小数)1袋の出来上がり量(m³)。"0" なら袋数を出さない

出力

成功したとき:

項目型定義
volume文字列体積(m³)= 面積 × 厚さ ÷ 100。小数第3位まで四捨五入し、末尾の 0 と小数点を省く("0.15"・"1")
liters整数体積 × 1000 を整数に四捨五入
loss_volume文字列ロス込みの体積 = 体積 × (1 + ロス率 ÷ 100)。volume と同じ形
loss_liters整数ロス込みの体積 × 1000 を整数に四捨五入
bags整数または null袋数 = 体積 ÷ 1袋の出来上がり量 を切り上げ(ちょうど割り切れればその数)。bag_m3 が "0" なら null
  • 四捨五入・切り上げは、丸める前の正確な値に対して行う(途中で小数第何位かに丸めない)
  • 袋数は、元ツールと同じくロスを含まない体積から出す
  • 値はすべて 0 より大きいので、四捨五入は「0.5 ちょうどは大きい方へ」でよい

判定できないとき: {"error": "invalid_input"}。面積(または幅・奥行)・厚さが 0 以下、ロス率が負、bag_m3 が負のとき。

元ツールとの違い

  • 元ツールはロス率が負なら 0 として計算する。本 spec はエラーにした
  • 元ツールは入力の小数を2進数の小数(parseFloat)で計算するので、袋数・体積・リットルが境目でずれる(上記)。本 spec は小数を十進のまま正確に計算した結果を正解にする

期待値の作り方

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