だし(出汁)の割合計算

水の量と、だしの種類ごとの割合(昆布1%・かつお節2%など)から、材料のグラム数を小数第1位まで出す。

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

言語ごとの結果

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

PHPPHP 8.4.26

よくある書き方11 / 14

直した書き方14 / 14

PythonPython 3.14.7

よくある書き方8 / 14

直した書き方14 / 14

GoGo 1.27.2

よくある書き方11 / 14

直した書き方14 / 14

JavaScriptNode 24.15.0

よくある書き方11 / 14

直した書き方14 / 14

JavaJava 21.0.12

よくある書き方11 / 14

直した書き方14 / 14

C#.NET 10.0.402

よくある書き方8 / 14

直した書き方14 / 14

RustRust 1.99.0

よくある書き方11 / 14

直した書き方14 / 14

RubyRuby 4.0.7

よくある書き方11 / 14

直した書き方14 / 14

KotlinKotlin 2.4.21

よくある書き方11 / 14

直した書き方14 / 14

SwiftSwift 6.3.3

よくある書き方11 / 14

直した書き方14 / 14

なぜ割れたのか

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

2進小数の誤差

水45ml のかつおだしで、10言語とも 1.35g を 1.3g にした

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

水45ml でかつおだし(かつお節 3%)を取るとき(c003)、かつお節は 45 × 3% = 1.35g です。小数第1位に四捨五入して 1.4g が正解ですが、10言語すべてが 1.3g と答えました。

水335ml(c004)の 10.05g、水155ml の煮干しだし(c005)の 4.65g も、10言語とも同じように 0.1g 少なくなりました。

なぜ少なくなったのか

よくある書き方では、10言語とも元のツールと同じく、水の量に「割合 ÷ 100」を掛けて2進数の小数(double・f64・Float など)で計算しました。3 ÷ 100 = 0.03 は2進数の小数で正確に表せないので、掛け算の結果が 0.05 にわずかに届きません。

計算正確な値2進数の小数での結果
45 × (3 ÷ 100)1.351.3499999999999999

0.05 に届かない端数は、どの言語の四捨五入でも切り捨てられます。

仕様の元になったシルギアの公開ツールも同じ式で計算していて、水 1〜5,000ml × 割合(1%・2%・3%)の15,000通りのうち66通りで 0.1g 少なく出ることを確かめました(2026-10-15)。

どう直したか

10言語とも、0.1g を単位にした整数で計算しました。水の量も割合も整数なので、0.1g 単位の四捨五入は整数の割り算だけで書けます。

tenths = (water_ml * pct * 10 + 50) // 100   # 0.1g 単位の整数

45ml × 3% なら (45 × 3 × 10 + 50) ÷ 100 = 14.0 → 14、つまり 1.4g です。

0.5 の丸め方

1.25g を、Python と C# は 1.2g にした(Python は 0.15g も 0.1g に)

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

水125ml の一番だし(c007)の昆布は 125 × 1% = 1.25g、小数第1位に四捨五入して 1.3g です。10言語中8言語は 1.3g を返しましたが、Python と C# は 1.2g を返しました。水4,995ml の煮干しだし(c013)の 149.85g も、正解の 149.9g に対して、Python と C# は 149.8g でした。

水15ml の一番だし(c008)の昆布 0.15g は 0.2g が正解です。ここで外れたのは Python だけで、0.1g を返しました。残りの9言語は 0.2g です。

書き方の違いが、結果の違いになった

小数第1位への丸めの書き方は、言語によって2通りに分かれました。

  • 10倍して整数に丸め、10で割る: Go・JavaScript・Java・Rust・Kotlin・Swift
  • 小数第1位を指定して丸める関数: PHP の round($n, 1)・Ruby の round(1)・Python の round(n, 1)・C# の Math.Round(x, 1)
値2進数で実際に入っている値10倍して丸める(6言語)・PHP・RubyPython round(n, 1)C# Math.Round(x, 1)
0.15(c008)0.1499999999999999944…0.20.10.2
1.25(c007)1.25(誤差なし)1.31.21.2
149.85(c013)149.8499999999999943…149.9149.8149.8

Python の round(n, 1) は、2進数で実際に入っている値を正確に丸めます。0.15 の中身は 0.15 よりわずかに小さいので、0.1 になります。誤差のない 1.25 は 0.5 ちょうどなので、偶数丸めで 1.2 になります。

C# の Math.Round(x, 1) は、既定が偶数丸めです。1.25 は 1.2 に、149.85 は 149.8 になりました。0.15(c008)は 0.2 と正しく返しました。C# は、水5ml の顆粒だし(c006)の 0.05g も 0g にしています(正解は 0.1g)。

10倍して丸める6言語は、10倍したときに誤差が消えて 1.5 ちょうどになり、たまたま正解しました。PHP の round($n, 1) と Ruby の round(1) も、3件とも正解しました。ただし前の解説のとおり、10倍しても誤差が残る値(1.3499…)では、10言語がそろって外れます。

どちらの書き方も、「十進で 0.05 ちょうど」を確実には扱えません。

どう直したか

前の解説と同じく、10言語とも 0.1g を単位にした整数で計算しました。小数を一度も通らないので、丸め方の違いも誤差も入りません。

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

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

ケース入力正解よくある書き方の答え(10言語)
c001基本・一番だし 1000ml
water_ml
1000
type
ichiban
materials
[{"name": "昆布", "grams": "10"}, {"name": "かつお節", "grams": "20"}]

✓10言語すべて正解

c002煮干しだし 600ml
water_ml
600
type
niboshi
materials
[{"name": "煮干し", "grams": "18"}]

✓10言語すべて正解

c0030.05 の境目・かつおだし 45ml 1.35g → 1.4g。45 × 0.03 は2進数の小数で 1.3499999999999999 になる なぜ? 解説を読む
water_ml
45
type
katsuo
materials
[{"name": "かつお節", "grams": "1.4"}]
  • PHP・Python・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftmaterials[{"name": "かつお節", "grams": "1.3"}]
  • Gomaterials[{"grams": "1.3", "name": "かつお節"}]
c0040.05 の境目・かつおだし 335ml 10.05g → 10.1g なぜ? 解説を読む
water_ml
335
type
katsuo
materials
[{"name": "かつお節", "grams": "10.1"}]
  • PHP・Python・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftmaterials[{"name": "かつお節", "grams": "10"}]
  • Gomaterials[{"grams": "10", "name": "かつお節"}]
c0050.05 の境目・煮干しだし 155ml 4.65g → 4.7g なぜ? 解説を読む
water_ml
155
type
niboshi
materials
[{"name": "煮干し", "grams": "4.7"}]
  • PHP・Python・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftmaterials[{"name": "煮干し", "grams": "4.6"}]
  • Gomaterials[{"grams": "4.6", "name": "煮干し"}]
c0060.05 の境目・顆粒だし 5ml 0.05g → 0.1g なぜ? 解説を読む
water_ml
5
type
karyu
materials
[{"name": "顆粒だし", "grams": "0.1"}]
  • C#materials[{"name": "顆粒だし", "grams": "0"}]
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swift✓ 正解
c0070.05 の境目・一番だし 125ml 昆布 1.25g → 1.3g・かつお節 2.5g なぜ? 解説を読む
water_ml
125
type
ichiban
materials
[{"name": "昆布", "grams": "1.3"}, {"name": "かつお節", "grams": "2.5"}]
  • Python・C#materials[{"name": "昆布", "grams": "1.2"}, {"name": "かつお節", "grams": "2.5"}]
  • PHP・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swift✓ 正解
c0080.05 の境目・一番だし 15ml 昆布 0.15g → 0.2g・かつお節 0.3g なぜ? 解説を読む
water_ml
15
type
ichiban
materials
[{"name": "昆布", "grams": "0.2"}, {"name": "かつお節", "grams": "0.3"}]
  • Pythonmaterials[{"name": "昆布", "grams": "0.1"}, {"name": "かつお節", "grams": "0.3"}]
  • PHP・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swift✓ 正解
c009整数で書く 40g(40.0g ではない)
water_ml
2000
type
konbu
materials
[{"name": "昆布", "grams": "40"}]

✓10言語すべて正解

c010小数第1位まで 昆布 3.5g・かつお節 7g
water_ml
350
type
ichiban
materials
[{"name": "昆布", "grams": "3.5"}, {"name": "かつお節", "grams": "7"}]

✓10言語すべて正解

c0110.1g に届かない 0.04g → 0g
water_ml
4
type
karyu
materials
[{"name": "顆粒だし", "grams": "0"}]

✓10言語すべて正解

c012昆布だし 25ml 0.5g
water_ml
25
type
konbu
materials
[{"name": "昆布", "grams": "0.5"}]

✓10言語すべて正解

c013多めの水 149.85g → 149.9g なぜ? 解説を読む
water_ml
4995
type
niboshi
materials
[{"name": "煮干し", "grams": "149.9"}]
  • Python・C#materials[{"name": "煮干し", "grams": "149.8"}]
  • PHP・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swift✓ 正解
c014水 0ml はエラー
water_ml
0
type
ichiban
error
invalid_input

✓10言語すべて正解

コード

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

Dashi.php+4 −2よくある書き方(e995f69)→ 直した書き方

@@ 変更前 3行目〜 / 変更後 3行目〜 @@
33
44 /**
55 * だし(出汁)の割合計算。
6− * v1: 水の量 × (割合 ÷ 100) を float で計算し、round($n, 1) で小数第1位に丸める素直な書き方。
6+ * v2: 0.1g を単位にした整数で計算する。水の量 × 割合 ÷ 100 を 0.1g 単位で四捨五入 = intdiv(水 × 割合 × 10 + 50, 100)。
7+ * float だと 45 × 0.03 が 1.3499999999999999 になり、round で 1.3 に落ちる。
78 */
89 const TYPES = [
910 'ichiban' => [['昆布', 1], ['かつお節', 2]],
@@ 変更前 22行目〜 / 変更後 23行目〜 @@
2223 }
2324 $materials = [];
2425 foreach (TYPES[$in['type']] as [$name, $pct]) {
25− $materials[] = ['name' => $name, 'grams' => (string) round($water * ($pct / 100), 1)];
26+ $tenths = intdiv($water * $pct * 10 + 50, 100);
27+ $materials[] = ['name' => $name, 'grams' => $tenths % 10 === 0 ? (string) intdiv($tenths, 10) : intdiv($tenths, 10) . '.' . $tenths % 10];
2628 }
2729 return ['materials' => $materials];
2830 }

仕様

入力

項目型説明
water_ml整数水の量(ml)
type文字列だしの種類(下の表)
type名前材料と割合(水の重さに対する %)
ichiban一番だし昆布 1%・かつお節 2%
katsuoかつおだしかつお節 3%
konbu昆布だし昆布 2%
niboshi煮干しだし煮干し 3%
karyu顆粒だし顆粒だし 1%

水 1ml = 1g として計算する(元ツールと同じ)。

出力

成功したとき: {"materials": [{"name": "昆布", "grams": "10"}, {"name": "かつお節", "grams": "20"}]}

項目型定義
materials配列上の表の順に、材料ごとの name(材料の名前)と grams
grams文字列水の量 × 割合 ÷ 100 を小数第1位まで四捨五入した値。小数第1位が 0 なら整数で書く("20"・"1.4"・"0")
  • 四捨五入は、丸める前の正確な値に対して行う。値は 0 以上なので「0.5 ちょうどは大きい方へ」(0.05 → 0.1、1.25 → 1.3)

判定できないとき: {"error": "invalid_input"}。水の量が 0 以下のとき。

元ツールとの違い

元ツールは水の量 × (割合 ÷ 100) を2進数の小数で計算し、Math.round(n × 10) / 10 で丸めるので、0.05 刻みの境目で 0.1g 少なくなることがある(上記)。本 spec は正確な値を丸めた結果を正解にする。

期待値の作り方

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