たとえば1t + 2kg + 3g + 4mgみたいなことはJavaScriptではできません。
単位変換ができない以前に、単位という概念がありません。
例外は暦くらいで、それ以外の値については値しか扱うことができません。
ということでJavaScriptで単位を扱えるようにしようというproposalが提出されました。
2025年11月現在のstageは1で、実装されるにしてもまだまだ先になりそうですが、面白そうな提案だったので紹介してみます。
ということで以下は該当のproposal、Representing Measuresです。
プロジェクト名はamountなのにタイトルはMeasure、よくわからぬ。
Representing Measures
Goals and needs
現実世界では、数値が数値だけで存在している状況は稀です。
数値は、たいてい単位と共に使用されます。
ボウルの中のリンゴの数、コップ一杯の水の量、電気自動車の消費電力まで、あらゆるものが対象になります。
また、物理量の測定には精度、すなわち有効桁数も存在します。
Intlフォーマッタは数値をフォーマットすることはできますが、その単位は扱わないため、実世界に適用するとバグります。
我々は、単位を表す新しいオブジェクトを導入し、文字列表現を生成できるようにします。
一般的なユースケースとしては、以下のようなものが考えられます。
・計測値の精度を制御する。
・通貨の単位を扱う。ユーザは金額の数値だけではなく、その通貨単位を一緒に扱いたいでしょう。
・計測値を文字列にフォーマットして出力する。
Description
数値、精度、単位を含むプリミティブ型Amountを導入します。
Properties
以下のプロパティを持ちます。
・unit:string|undefined
単位。
・significantDigits:int
有効桁数。正の整数。
・fractionalDigits:int
小数点以下の桁数。非負整数。
Constructor
・new Amount(value[, options])
コンストラクタは数値valueと、以下を含むオプション引数optionsを受け取ります。
・unit:string|undefined
単位。
・significantDigits:int
有効桁数。正の整数。
・fractionalDigits:int
小数点以下の桁数。非負整数。
・roundingMode
Intlが対応している、端数の丸めモード。
オブジェクトは、以下のメソッドを持ちます。
・toString([ options ])
文字列表現を返します。
デフォルトでは単位を角括弧で囲った1.23[kg]のような形式になります。
・toLocaleString(locale[, options])
ロケールを考慮した文字列表現を返します。
たとえば小数点がカンマになるロケールであれば1,23[kg]のようになります。
・with(options)
オプションを追加した新しい単位を作成します。
Examples
まずは単位のない数値の例です。
let a = new Amount("123.456");
a.fractionDigits; // 3
a.significantDigits; // 6
a.with({ fractionDigits: 4 }).toString(); // "123.4560"
有効桁数を増やすと、末尾に0が増えます。
単位付きの例です。
let a = new Amount("42.7", { unit: "kg" });
a.toString(); // "42.7[kg]"
a.toString({ numberOnly: true }); // "42.7"
Formatting with Intl
Amountは、データ・ロケール・表示オプションを分離することで、国際フォーマットのデザインパターンを向上させます。
Amountを使わない場合、引数の目的が混在してしまいます。
let numberOfKilograms = 42.7;
let locale = "zh-CN";
let localizedString = new Intl.NumberFormat(locale, {
minimumSignificantDigits: 4,
style: "unit",
unit: "kilogram",
unitDisplay: "long",
})
.format(numberOfKilograms);
console.log(localizedString); // "42.70千克"
Amountを使うと、目的が分離され、正しく操作しやすくなります。
// データモデル
let amt = new Amount("42.7", { unit: "kilogram", significantDigits: 4 });
// ロケール
let locale = "zh-CN";
// オプション
let options = { unitDisplay: "long" };
// まとめる
let localizedString = amt.toLocaleString(locale, options);
console.log(localizedString); // "42.70千克"
Amountは、Intl.MessageFormatへの組み込みを、当初はユーザランドで、最終的に標準実装での組み込みを見据えています。
Selecting Plural Forms
i18nのよくある問題として、Intl.PluralRulesとIntl.NumberFormatの両方に同じ精度を設定をしないといけないことが挙げられます。
// バグがあります。どうしてかわかりますか?
let locale = "en-US";
let numberOfStars = 1;
let numberString = new Intl.NumberFormat(locale, { minimumFractionDigits: 1 }).format(numberOfStars);
switch (new Intl.PluralRules(locale).select(numberOfStars)) {
case "one":
console.log(`The rating is ${numberString} star`);
break;
default:
console.log(`The rating is ${numberString} stars`);
break;
}
このコードはThe rating is 1.0 starになってしまいます。
シンプルなルールの英語ですら誤りであり、複雑な語形変化を持つ言語では、問題はさらに顕著になります。
Amountを使うと、コードは期待通りになり、流れも追いやすくなります。
let locale = "en-US";
let stars = new Amount(1, { fractionDigits: 1 });
let numberString = stars.toLocaleString(locale);
switch (stars.toLocalePlural(locale)) {
case "one":
console.log(`The rating is ${numberString} star`);
break;
default:
console.log(`The rating is ${numberString} stars`);
break;
}
Rounding
精度を下げると、値は四捨五入されます。
let a = new Amount("123.456");
a.with({ significantDigits: 5 }).toString(); // "123.46"
デフォルトでは、NumberやDecimalでも使用されているIEEE754で規定されたround-ties-to-evenが使用されます。
丸めモードを指定することも可能です。
let b = new Amount("123.456");
a.with({ significantDigits: 5, roundingMode: "truncate" }).toString(); // "123.45"
Units (including currency)
proposalの核となる機能のひとつは、単位と通貨のサポートです。
単位も通貨も必須ではありませんが、指定する場合は単位と通貨のいずれか片方だけを指定可能です。
let a = new Amount("123.456", { unit: "kg" }); // 123.456 kilograms
let b = new Amount("42.55", { unit: "EUR" }); // 42.55 Euros
単位の意味は規定されていないことに注意してください。
単位としてXYZやkeelogramzなども使用可能です。
ただし、Intl.NumberFormatが対応していない単位でtoLocaleStringを呼びだすとエラーになります。
Related but out-of-scope features
スコープ外となった機能。
Amountは、小規模で容易に実現できる機能の核となることを目指しています。
以下の提案は、今後の拡張に含まれる可能性はありますが、現在はスコープ外です。
Mathematical operations
算術演算への対応。
Amount同士の四則演算、スカラー値との乗算・除算など。
gからkgなどへのスケール変換。
Unit conversion
マイルからメートルなどへの単位変換。
Derived units
メートルから平方メートルなどへの単位の派生。
Compound units
単位の組み合わせ。
身長を71inchや5.92feetではなく5 feet 11 inchesと表す。
Polyfill
既にPolyfillが存在します。
本proposalはステージ1であるため、今後互換性のない変更が入る可能性があります。
本番環境での使用には適していません。
感想
mathjs使うわ。
1t + 2kg + 3g + 4mg; // 1.002003004 t