3億2700万ドルの単位変換エラー
1999年9月23日、NASAの火星気候探査機(Mars Climate Orbiter)が火星軌道に入るためにエンジンを噴射し、二度と通信が取れなくなりました。探査機は低すぎる高度で進入しました——計画の226kmではなく57km——大気で燃え尽きたか宇宙に弾き飛ばされました。原因:ロッキード・マーティンの地上ソフトウェアがスラスター力データをポンド力・秒で出力していましたが、NASAのナビゲーションソフトウェアはニュートン・秒を期待していました。9ヶ月の飛行中、誰もこの不一致に気づきませんでした。
変換係数はシンプルです:1ポンド力 = 4.44822ニュートン。ソフトウェアはすべての軌道修正マヌーバで4.45倍ずれていました。各小さなエラーが数百万キロメートルにわたって蓄積しました。調査委員会はこれを「地上ソフトウェアファイルのコーディングでメートル法単位を使用しなかった失敗」と呼びました。本当の失敗はシステム的でした——インターフェース仕様が単位を定義せず、バリデーションが値が期待範囲内かチェックせず、統合テストが不一致を検出しませんでした。
これは一回限りではありません。単位変換エラーはギムリー・グライダー事件(1983年、エア・カナダ767が地上クルーがキログラムではなくポンドで燃料を計算したため燃料切れ)、ヴァーサ号の沈没(1628年、左舷と右舷で2つの異なる測定システムで建造)、東京ディズニーランドのスペースマウンテン脱線(2003年、インチの車軸仕様がミリメートルとして解釈された)を引き起こしました。パターンは常に同じです:2つのシステム、2つの単位規約、それらの間の明示的な契約がない。
単位変換エラーが起き続ける理由
根本的な問題:単位のない数値は無意味ですが、ほとんどのプログラミング言語はそれを裸の数値として扱います。distance = 384400と書いたとき、それはキロメートル(地球から月)ですか、マイル(月を235,000km通り過ぎます)ですか?変数名は"distance_km"かもしれませんが、型システムはそれを強制しません。メートルを期待する関数はフィートの値を喜んで受け入れ、エラーなしにゴミを出力します。
メートル法とヤードポンド法の分裂がこれを悪化させます。米国、ミャンマー、リベリアだけが公式にメートル法を採用していません。しかしメートル法の国でも、レガシーシステムはヤードポンド法を使います——航空は世界中で高度にフィート、距離に海里を使います。医学も両方使います(血圧はmmHg、体温は米国で°Fですが他では°C)。つまり国際的なシステムは両方を扱う必要があり、変換の境界がバグの温床です。
ソフトウェア特有の罠:CSSはpx、em、rem、vh、vwを使います——すべて長さの異なる単位です。APIはプロバイダーによってケルビン、摂氏、華氏で温度を返します。タイムスタンプは秒、ミリ秒、マイクロ秒で来ます。角度は度、ラジアン、グラジアンがあります。システム、ライブラリ、API間の境界を越えるたびに、単位の不一致のリスクがあります。
人的要因:単位変換エラーは退屈です。巧妙なバグではなく、起こるには単純すぎると感じる平凡なミスです。だからこそコードレビューをすり抜けます。レビュアーは"thrust = calculateThrust(data)"を見て「dataの単位は何?」と聞こうとは思いません。火星探査機チームには数百人のエンジニアと9ヶ月の飛行運用がありました。誰もその質問をしませんでした。
ソフトウェアでよくある単位変換ミス
温度:C = (F - 32) × 5/9の公式はシンプルですが、一部の言語での整数除算が結果を切り捨てます。Cでは:(100 - 32) * 5 / 9 = 37(正しい)、しかし(50 - 32) * 5 / 9 = 10(10.0であるべきですが、十分近い)。本当の罠:ケルビンから摂氏は単にK - 273.15ですが、ケルビンから華氏は2ステップ必要です。そしてランキン(一部の米国工学で使用)は華氏 + 459.67です。scientific-calculatorツールは4つのスケールすべてを扱います。
距離:1マイル = 1.60934 km、1フィート = 0.3048メートル正確(1959年以降の定義)、1インチ = 25.4 mm正確。「正確」が重要です——これらは定義された変換であり、測定された近似ではありません。しかし海里(1.852 km)は法定マイル(1.609 km)と異なり、英国の「マイル」は標準化まで歴史的に米国マイルと異なっていました。どのマイルか常に指定しましょう。
重量 vs 質量:日常語では「重量」と「質量」は互換可能です。物理学では、質量(kg)は固有で重量(ニュートン)は重力に依存します。地球上で1kgの質量は9.81Nの重さです。火星では3.72Nです。この区別は航空宇宙、物理シミュレーション、異なる重力環境のオブジェクトをモデル化するコードで重要です。火星探査機のエラーは質量ではなく力(ニュートン vs ポンド力)に関するものでした。
データストレージ:1 KB = 1,000バイト(SI、ハードドライブメーカーが使用)または1,024バイト(バイナリ、OSが使用)。これが「1 TB」のドライブがWindowsで931 GBと表示される理由です。IEC規格はKiB(1,024バイト)、MiB(1,048,576バイト)等を曖昧さ解消のために導入しましたが、採用は一貫していません。コードでファイルサイズを報告するとき、どちらの規約を使っているか明示しましょう。
実際に機能する防止策
戦略1:単位をエンコードする型システムを使う。TypeScriptではブランド型が使えます:type Meters = number & { __brand: "meters" }。Metersを期待する関数は、明示的なキャストなしにプレーンなnumberを受け付けません。ts-units、unitful(Haskell)、Boost.Units(C++)などのライブラリはコンパイル時に単位の正しさを強制します。火星探査機のエラーは適切な単位型があればコンパイルエラーになっていたでしょう。
戦略2:システム境界で常に正規単位に変換する。一つの単位系(SI メートル法が標準的な選択)を選び、すべての入力を受信時にそのシステムに即座に変換します。内部計算は正規単位のみを使用します。出力境界でのみ表示単位に戻します。この「早期正規化、遅延非正規化」パターンが内部の単位混乱を排除します。
戦略3:変数名とAPI契約に単位を含める。"distance"ではなく"distance_meters"。"temperature"ではなく"temp_celsius"。APIドキュメントでは単位を明示的に指定:"altitude: number(海抜メートル)"。データベーススキーマではコメントを追加するか"weight_kg"のようなカラム名を使います。ローテクですがコードレビューでエラーを検出します。
戦略4:境界で範囲を検証する。天気の読み取りで5,000°Cの温度は明らかに間違いです——おそらく変換されていない5,000ケルビン、または小数点がずれた50.00°Cです。車の旅で384,400の距離は、おそらくマイルを期待していたのにキロメートル(またはその逆)です。範囲チェックは物理的に不可能な値を生む単位エラーを検出します。unit-converterツールは両方の値を並べて表示するので、大きさの妥当性を確認できます。
// Branded types prevent unit confusion at compile time
type Meters = number & { readonly __brand: unique symbol };
type Feet = number & { readonly __brand: unique symbol };
function metersToFeet(m: Meters): Feet {
return (m * 3.28084) as Feet;
}
function calculateAltitude(alt: Meters): string {
return `${alt}m above sea level`;
}
const altitude = 10000 as Meters;
calculateAltitude(altitude); // ✅ OK
// calculateAltitude(10000 as Feet); // ❌ Type error!
// Simpler approach: objects with explicit unit field
interface Measurement {
value: number;
unit: 'meters' | 'feet' | 'km' | 'miles';
}
function toMeters(m: Measurement): number {
switch (m.unit) {
case 'meters': return m.value;
case 'feet': return m.value * 0.3048;
case 'km': return m.value * 1000;
case 'miles': return m.value * 1609.34;
}
}科学計算における単位変換
科学コードには追加の課題があります:組立単位。速度はメートル/秒、加速度はメートル/秒²、力はkg·m/s²(ニュートン)、エネルギーはkg·m²/s²(ジュール)。速度に時間を掛けると結果はメートルであるべきですが、プログラミング言語はそれを知りません。次元解析(単位が正しく相殺されることの確認)は物理学者が紙の上で行いますが、ソフトウェアにエンコードされることは稀です。
Pintライブラリ(Python)や類似ツールがこれを扱います:distance = 5 * ureg.meter; time = 2 * ureg.second; speed = distance / timeで完全な単位追跡付きの2.5 meter/secondが得られます。メートルと秒を足そうとするとDimensionalityErrorが発生します。これは無意味な結果を黙って生むバグを検出します。
浮動小数点精度が単位変換と微妙に相互作用します。正確な変換1インチ = 25.4 mmは浮動小数点で表現可能です。しかし1フィート = 0.3048メートルは循環する2進表現を持ち、微小な丸め誤差を導入します。数百万回の変換(CADソフトウェアや物理シミュレーションで一般的)にわたって、これらのエラーが蓄積します。精度が重要な場合は変換係数に正確な有理数演算を使いましょう。
実例:GPS座標。小数点以下6桁の緯度/経度は約0.11メートルの精度を与えます。しかし三角関数計算のためにラジアンに変換して戻すと、浮動小数点の丸めで位置がセンチメートル単位でずれる可能性があります。ナビゲーションにはこれで十分です。測量(ミリメートルが重要)では、変換精度に注意が必要です。一度変換して結果を保持し、何度も行き来しないようにしましょう。
単位変換ツールでは不十分な場合
一部の「変換」は単純な掛け算ではありません。温度(摂氏から華氏はオフセットを含み、比率だけではない)。デシベル(対数スケール——パワーを2倍にすると3 dB加算、dBが2倍にはならない)。pH(同じく対数——pH 5はpH 6の10倍酸性)。リヒタースケール(各整数は31.6倍のエネルギー)。これらは変換係数ではなく、基礎となるスケールの理解が必要です。
通貨変換は特殊なケースです:変換係数が毎秒変わります。固定レートを使う「単位変換器」は使う時点で間違っています。実際の通貨変換にはライブ為替レートが必要で、得られるレートは金額(スプレッド)、方向(買いレート vs 売りレート)、プロバイダー(銀行 vs FXブローカー vs クレジットカード)に依存します。unit-converterは固定変換係数の物理単位を扱います。通貨にはライブレートAPIが必要です。
料理の計量は驚くほど複雑です。「カップ」は米国で236.6 mL、オーストラリアで250 mL、日本で200 mLです。「大さじ」は14.8 mL(米国)、15 mL(メートル法)、20 mL(オーストラリア)。異なる国のレシピは異なる「標準」計量を使います。そして体積から重量への変換は食材に依存します:小麦粉1カップは125gですが、砂糖1カップは200gです。シンプルな変換表ではこれを扱えません。
これらのエッジケースからの教訓:単位変換は固定比率の明確に定義された物理単位に対してのみ「シンプル」です。それ以外のすべて——通貨、料理、対数スケール、文脈依存の単位——にはドメイン知識が必要で、単なる掛け算係数では不十分です。最良の単位変換ツール(私たちのものを含む)は、何が変換でき何ができないかを明示し、単純な係数では不十分なケースをフラグします。