露点・不快指数の計算

気温と湿度から、不快指数・露点・絶対湿度と、体感や結露リスクの目安を出す。露点は対数、絶対湿度は累乗を使う。

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

言語ごとの結果

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

PHPPHP 8.4.26

よくある書き方16 / 17

直した書き方17 / 17

PythonPython 3.14.7

よくある書き方8 / 17

直した書き方17 / 17

GoGo 1.27.2

よくある書き方8 / 17

直した書き方17 / 17

JavaScriptNode 24.15.0

よくある書き方11 / 17

直した書き方17 / 17

JavaJava 21.0.12

よくある書き方14 / 17

直した書き方17 / 17

C#.NET 10.0.402

よくある書き方8 / 17

直した書き方17 / 17

RustRust 1.99.0

よくある書き方8 / 17

直した書き方17 / 17

RubyRuby 4.0.7

よくある書き方15 / 17

直した書き方17 / 17

KotlinKotlin 2.4.21

よくある書き方14 / 17

直した書き方17 / 17

SwiftSwift 6.3.3

よくある書き方8 / 17

直した書き方17 / 17

なぜ割れたのか

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

2進小数の誤差

不快指数 66.35 を、JavaScript は 66.3、Java は 66.4 にした

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

気温20℃・湿度70%(c003)の不快指数は、0.81 × 20 + 0.01 × 70 × (0.99 × 20 − 14.3) + 46.3 = 66.35 ちょうどです。小数第1位に四捨五入して 66.4 が正解です。10言語中4言語が 66.4 と正しく答え、6言語が 66.3 と答えました。

言語書き方答え
JavaScripttoFixed(1)66.3
Pythonf"{x:.1f}"66.3
Gostrconv.FormatFloat(x, 'f', 1, 64)66.3
Rustformat!("{:.1}", x)66.3
C#x.ToString("F1")66.3
SwiftString(format: "%.1f")66.3
JavaString.format("%.1f", x)66.4
Kotlin"%.1f".format()66.4
PHPnumber_format($x, 1)66.4
Rubyx.round(1)66.4

63.05(c004)と 91.35(c007)も同じ形で割れました。6言語は 63.0・91.3、4言語は正解の 63.1・91.4 です。

なぜ割れたのか

2進数の小数で計算した 66.35 の中身は、66.349999999999994315… です。0.05 にわずかに届きません。

  • JavaScript・Python・Go・Rust・C#・Swift は、2進数で実際に入っている値を正確に丸めるので、66.3 になります
  • Java の String.format は、いったん値を最短の十進表記(66.35)に直してから四捨五入するので、66.4 になります。Kotlin の "%.1f".format() は Java の書式化と同じ仕組みなので、同じく 66.4 です
  • PHP の number_format と Ruby の round(1) も、小さな誤差を吸収してから丸めるので、66.4 になります

ただし、救われる側の4言語も万能ではありません。気温30℃・湿度25%(c005)の 74.45 は、2進数の小数で計算すると 74.44999999999999 になり、最短の十進表記でも 74.45 に戻りません。そのため 10言語とも 74.4 と答えました(正解は 74.5)。

どう直したか

気温は小数第1位まで、湿度は整数なので、不快指数を 10万倍すると必ず整数になります。

DI × 100000 = 8100 × (気温×10) + 湿度 × (99 × (気温×10) − 14300) + 4630000

この整数を 10000 で割って四捨五入すれば、2進数の小数を一度も通らずに小数第1位が決まります。体感の区分(70未満なら「快適」など)も、同じ整数を 7000000 と比べて決めました。

0.5 の丸め方

気温20℃・湿度50%の不快指数 65.25 を、Python・Go・Rust・C#・Swift は 65.2 にした

対象のケースc002c006c014 落ちた言語PythonGoC#RustSwift

気温20℃・湿度50%(c002)の不快指数は 65.25 ちょうどです。小数第1位に四捨五入して 65.3 が正解です。10言語中5言語(JavaScript・Java・Kotlin・PHP・Ruby)は 65.3 を返しましたが、残りの5言語(Python・Go・Rust・C#・Swift)は 65.2 を返しました。

気温7.5℃・湿度60%(c006)の 48.25 も、同じ5言語が 48.2 にしました。気温を "20" と小数なしで入れた c014 も、c002 と同じ答えです。

なぜ割れたのか

65.25 と 48.25 は、2進数の小数でも誤差なく表せます(0.25 = 1/4)。違いは、0.5 ちょうどの丸め方だけです。

言語書き方65.25 →
Pythonf"{x:.1f}"65.2(偶数の方へ)
Gostrconv.FormatFloat(x, 'f', 1, 64)65.2(偶数の方へ)
Rustformat!("{:.1}", x)65.2(偶数の方へ)
C#x.ToString("F1")65.2(偶数の方へ)
SwiftString(format: "%.1f")65.2(偶数の方へ)
JavaScripttoFixed(1)65.3
JavaString.format("%.1f", x)65.3
Kotlin"%.1f".format()65.3
PHPnumber_format($x, 1)65.3
Rubyx.round(1)65.3

文字列にするための書式化の関数の多くは、0.5 ちょうどを偶数の方へ丸めます。四捨五入の関数(Rust の f64::round など)と、同じ言語の中でも丸め方が違うことがあります。

20℃・50% は、エアコンの設定や天気予報でよく見る値です。こうした「きりのよい入力」ほど、0.5 ちょうどが現れやすくなります。

どう直したか

前の解説と同じく、不快指数を 10万倍した整数で計算し、0.5 ちょうどは大きい方(0 から遠い方)へ丸めると決めて、自分で書きました。

負の数

露点 −0.04℃ を「−0.0℃」と表示した

対象のケースc008c009 落ちた言語PythonGoJavaScriptJavaC#RustRubyKotlinSwift

気温0.1℃・湿度99%(c009)の露点は −0.038…℃ です。小数第1位にすると 0.0℃ ですが、10言語中、PHP 以外の9言語は 「−0.0」 と表示しました。

気温1.0℃・湿度93%(c008)の露点 −0.003…℃ では、PHP と Ruby が「0.0」、残りの8言語が「−0.0」でした。

言語−0.003(c008)−0.038(c009)
PHP0.00.0
Ruby0.0−0.0
Python・Go・JavaScript・Java・C#・Rust・Kotlin・Swift−0.0−0.0

なぜ「−0.0」になるのか

負の数を小数第1位に丸めると、絶対値は 0.0 になっても、符号のマイナスが残ります。多くの言語の書式化は、この「マイナスの 0」をそのまま -0.0 と書きます。Kotlin("%.1f".format())と Swift(String(format: "%.1f"))も、そのまま「−0.0」と書きました。

  • PHP の number_format は、PHP 8.0 から「−0」を書かないように変わったので、どちらも「0.0」でした
  • Ruby の round(1) は、−0.003 を 0.0 に、−0.038 を −0.0 にしました。値の大きさによって結果の符号が変わります

気温0〜2℃・湿度85〜99%あたりは、冬の朝の窓まわりでよく現れる条件です。この範囲(気温 −20〜40℃ を0.1℃刻み × 湿度 1〜100%)で露点が −0.05〜0℃ に入る入力は、57通りありました。

どう直したか

小数第1位にした結果が -0.0 なら 0.0 にしました。不快指数は整数で計算しているので、0 に丸まるときは最初から 0.0 と書いています。

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

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

ケース入力正解よくある書き方の答え(10言語)
c001基本・25℃ 60%
temp
25.0
humid
60
di
72.8
feel
暑くない
dew
16.7
abs_humidity
13.8
risk
中

✓10言語すべて正解

c002不快指数がちょうど 65.25(2進数でも誤差なし) 65.25 → 65.3。2進数でも 65.25 ちょうどなので、偶数丸めの言語は 65.2 にする なぜ? 解説を読む
temp
20.0
humid
50
di
65.3
feel
快適
dew
9.3
abs_humidity
8.7
risk
低い
  • Python・Go・C#・Rust・Swiftdi"65.2"
  • PHP・JavaScript・Java・Ruby・Kotlin✓ 正解
c003不快指数がちょうど 66.35 66.35 → 66.4。2進数の小数では 66.349… になり 66.3 に落ちる なぜ? 解説を読む
temp
20.0
humid
70
di
66.4
feel
快適
dew
14.4
abs_humidity
12.1
risk
中
  • Python・Go・JavaScript・C#・Rust・Swiftdi"66.3"
  • PHP・Java・Ruby・Kotlin✓ 正解
c004不快指数がちょうど 63.05 63.05 → 63.1 なぜ? 解説を読む
temp
20.0
humid
10
di
63.1
feel
何も感じない
dew
-12.5
abs_humidity
1.7
risk
低い
  • Python・Go・JavaScript・C#・Rust・Swiftdi"63.0"
  • PHP・Java・Ruby・Kotlin✓ 正解
c005不快指数がちょうど 74.45 74.45 → 74.5。2進数では 74.44999999999999 なぜ? 解説を読む
temp
30.0
humid
25
di
74.5
feel
暑くない
dew
7.8
abs_humidity
7.6
risk
低い
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftdi"74.4"
c006不快指数がちょうど 48.25(2進数でも誤差なし) 48.25 → 48.3。偶数丸めなら 48.2 なぜ? 解説を読む
temp
7.5
humid
60
di
48.3
feel
寒い
dew
0.2
abs_humidity
4.8
risk
低い
  • Python・Go・C#・Rust・Swiftdi"48.2"
  • PHP・JavaScript・Java・Ruby・Kotlin✓ 正解
c007不快指数がちょうど 91.35 91.35 → 91.4 なぜ? 解説を読む
temp
40.0
humid
50
di
91.4
feel
暑くてたまらない
dew
27.6
abs_humidity
25.6
risk
高い
  • Python・Go・JavaScript・C#・Rust・Swiftdi"91.3"
  • PHP・Java・Ruby・Kotlin✓ 正解
c008露点がわずかに負(−0.003℃) −0.0 ではなく 0.0 なぜ? 解説を読む
temp
1.0
humid
93
di
34.7
feel
寒い
dew
0.0
abs_humidity
4.8
risk
低い
  • Python・Go・JavaScript・Java・C#・Rust・Kotlin・Swiftdew"-0.0"
  • PHP・Ruby✓ 正解
c009露点がわずかに負(−0.04℃) −0.0 ではなく 0.0 なぜ? 解説を読む
temp
0.1
humid
99
di
32.3
feel
寒い
dew
0.0
abs_humidity
4.8
risk
低い
  • Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftdew"-0.0"
  • PHP✓ 正解
c010湿度100%・18℃(露点がちょうど結露リスクの境目) 露点は数式の上でちょうど 18 → 「高い」
temp
18.0
humid
100
di
64.4
feel
何も感じない
dew
18.0
abs_humidity
15.4
risk
高い

✓10言語すべて正解

c011湿度100%・12℃ 露点はちょうど 12 → 「中」
temp
12.0
humid
100
di
53.6
feel
寒い
dew
12.0
abs_humidity
10.7
risk
中

✓10言語すべて正解

c012寒くて乾燥 露点は負
temp
-10.0
humid
30
di
30.9
feel
寒い
dew
-24.3
abs_humidity
0.7
risk
低い

✓10言語すべて正解

c013蒸し暑い
temp
35.0
humid
80
di
90.9
feel
暑くてたまらない
dew
31.0
abs_humidity
31.7
risk
高い

✓10言語すべて正解

c014小数を省いた気温 "20" も 20.0 と同じ なぜ? 解説を読む
temp
20
humid
50
di
65.3
feel
快適
dew
9.3
abs_humidity
8.7
risk
低い
  • Python・Go・C#・Rust・Swiftdi"65.2"
  • PHP・JavaScript・Java・Ruby・Kotlin✓ 正解
c015湿度 0% はエラー ln(0) は計算できない
temp
25.0
humid
0
error
invalid_input

✓10言語すべて正解

c016気温が範囲外
temp
40.1
humid
50
error
invalid_input

✓10言語すべて正解

c017気温が小数第2位まである
temp
25.05
humid
50
error
invalid_input

✓10言語すべて正解

コード

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

Dew.php+22 −12よくある書き方(411f8f3)→ 直した書き方

@@ 変更前 3行目〜 / 変更後 3行目〜 @@
33
44 /**
55 * 露点・不快指数の計算。
6− * v1: (float) と log / pow で計算し、number_format($x, 1) で小数第1位にする素直な書き方。
6+ * v2: 不快指数は 10万倍した整数で正確に計算して四捨五入する(float だと 74.45 が 74.44999999999999 になり 74.4 に落ちる)。
7+ * 露点・絶対湿度は log / pow のまま(小数第1位では正確な値と一致することを60,100通りで確かめた)。湿度100%の露点は気温そのもの。
78 */
8−function feelOf(float $di): string
9+/** $n は不快指数 × 100000(整数) */
10+function feelOf(int $n): string
911 {
1012 return match (true) {
11− $di < 55 => '寒い',
12− $di < 60 => '肌寒い',
13− $di < 65 => '何も感じない',
14− $di < 70 => '快適',
15− $di < 75 => '暑くない',
16− $di < 80 => 'やや暑い',
17− $di < 85 => '暑くて汗が出る',
13+ $n < 5500000 => '寒い',
14+ $n < 6000000 => '肌寒い',
15+ $n < 6500000 => '何も感じない',
16+ $n < 7000000 => '快適',
17+ $n < 7500000 => '暑くない',
18+ $n < 8000000 => 'やや暑い',
19+ $n < 8500000 => '暑くて汗が出る',
1820 default => '暑くてたまらない',
1921 };
22+}
23+
24+/** 10万倍した整数 $n を小数第1位に四捨五入した文字列(0.5 は 0 から遠い方へ) */
25+function tenthsText(int $n): string
26+{
27+ $r = intdiv(abs($n) + 5000, 10000);
28+ return $r === 0 ? '0.0' : ($n < 0 ? '-' : '') . intdiv($r, 10) . '.' . $r % 10;
2029 }
2130
2231 /** @param array{temp: string, humid: int} $in */
@@ 変更前 28行目〜 / 変更後 37行目〜 @@
2837 return ['error' => 'invalid_input'];
2938 }
3039 $t = (float) $temp;
40+ $t10 = (int) round($t * 10); // 小数第1位までなので、10倍すれば整数
3141 if ($t < -20 || $t > 40 || $humid < 1 || $humid > 100) {
3242 return ['error' => 'invalid_input'];
3343 }
3444 $es = 6.1078 * pow(10, (7.5 * $t) / (237.3 + $t));
3545 $gamma = log($humid / 100) + (17.27 * $t) / (237.7 + $t);
36− $dew = (237.7 * $gamma) / (17.27 - $gamma);
37− $di = 0.81 * $t + 0.01 * $humid * (0.99 * $t - 14.3) + 46.3;
46+ $dew = $humid === 100 ? $t : (237.7 * $gamma) / (17.27 - $gamma);
47+ $di = 8100 * $t10 + $humid * (99 * $t10 - 14300) + 4630000; // 不快指数 × 100000(整数)
3848 $ah = (217 * $es * ($humid / 100)) / ($t + 273.15);
39− return ['di' => number_format($di, 1), 'feel' => feelOf($di), 'dew' => number_format($dew, 1),
49+ return ['di' => tenthsText($di), 'feel' => feelOf($di), 'dew' => number_format($dew, 1),
4050 'abs_humidity' => number_format($ah, 1), 'risk' => $dew >= 18 ? '高い' : ($dew >= 12 ? '中' : '低い')];
4151 }

仕様

着手前に測ったこと(2026-10-15)

  • 露点(log)と絶対湿度(pow(10, x))を、8言語の標準の数学関数で気温 −20.0〜40.0℃(0.1℃刻み)× 湿度 1〜100% の60,100通り計算した。2進数の値の最後のビットが言語で違う入力が、露点で3,657通り・絶対湿度で19,293通りあった(Go は数学関数を自前で実装していて多数派と違う回数が最も多く、JavaScript・Java も違うことがある)
  • しかし小数第1位で表示すると、60,100通り(0.01℃刻みにした600,100通りでも)すべてで8言語の表示がそろい、50桁の十進で計算した正確な値を丸めた結果とも一致した
  • 不快指数(多項式)は割り切れる小数なので、0.05 ちょうどになる入力が22通りあり、2進数の小数で 0.05 に届かずに外れるもの(17通り)と、2進数でも誤差なく 0.05 ちょうどになり丸め方で割れるもの(65.25 など)があった
  • 露点が −0.05〜0℃ になる入力が57通りあり、小数第1位にすると「−0.0」になる

入力

項目型説明
temp文字列(小数)気温(℃)。−20.0〜40.0、小数第1位まで("20"・"7.5"・"-10.0")
humid整数相対湿度(%)。1〜100

範囲外・小数第2位以下がある・読めないときは {"error": "invalid_input"}。(元ツールの範囲は気温 −60〜80℃・湿度は小数も可。本 spec は、上で測った範囲に絞った)

出力

項目型定義
di文字列不快指数 DI = 0.81 × T + 0.01 × RH × (0.99 × T − 14.3) + 46.3 を小数第1位まで四捨五入
feel文字列DI(丸める前)による体感。55未満「寒い」・60未満「肌寒い」・65未満「何も感じない」・70未満「快適」・75未満「暑くない」・80未満「やや暑い」・85未満「暑くて汗が出る」・それ以上「暑くてたまらない」
dew文字列露点温度(Magnus式)γ = ln(RH ÷ 100) + 17.27 × T ÷ (237.7 + T)、Td = 237.7 × γ ÷ (17.27 − γ) を小数第1位まで四捨五入
abs_humidity文字列絶対湿度 AH = 217 × es × (RH ÷ 100) ÷ (T + 273.15)、es = 6.1078 × 10^(7.5 × T ÷ (237.3 + T)) を小数第1位まで四捨五入
risk文字列露点(丸める前)による結露リスク。18以上「高い」・12以上「中」・それ未満「低い」
  • 四捨五入は、丸める前の正確な値に対して行う。0.05 ちょうどは 0 から遠い方へ(65.25 → 65.3)
  • 小数第1位まで必ず書く("18.0")。0 に丸まる値は "0.0"("-0.0" にしない)
  • 湿度 100% のとき、露点は数式の上でちょうど気温に等しい(T = 18.0 なら Td = 18 で「高い」)

期待値の作り方

expect は oracle.py で、DI は分数、露点と絶対湿度は 50桁の十進(decimal の ln・exp)で計算して生成した(2026-10-15)。実装ではない。runner は使わない。