JavaScriptのMath.log1p():小さな増減率を精度よく対数へ変換する
Math.log1p()で小さな増減率を自然対数へ変換する方法を解説。丸め誤差が起きる理由、引数と戻り値、入力チェック、ブラウザーとNode.jsの実行手順を学べます。
Math.log1p()で解決できること
増減率や測定値の微小な変化を対数へ変換するときは、Math.log1p()が役立ちます。引数をxとすると、計算するのは1 + xの自然対数です。自然対数とは、約2.718の定数を底とする対数で、増減の分析や統計処理で使われます。
たとえば、5%の増加を表す割合は0.05です。この変化を対数で表すにはMath.log1p(0.05)と書きます。値が増えていなければ0、減っていれば負の値になります。
数学的にはMath.log(1 + x)と同じ計算ですが、コンピューター上では途中の丸めが問題になります。非常に小さいxを先に1へ加えると、その差が失われる場合があるためです。Math.log1p()は、このような小さい入力を扱うために用意されています。MDNのMath.log1p()で、計算内容と精度上の理由を確認できます。
小さい差が消える仕組み
JavaScriptの通常の数値は、有限の精度を持つNumberです。1e-16はゼロではありませんが、1 + 1e-16を計算すると、結果は丸められて1になります。その後に対数を取ると、元の微小な変化を取り戻せません。
(() => {
const change = 1e-16;
console.log(1 + change === 1);
console.log(Math.log(1 + change));
console.log(Math.log1p(change).toExponential(3));
})();期待出力は次のとおりです。
true
0
1.000e-16重要なのは、元のchangeをそのまま渡すことです。Math.log1p((1 + change) - 1)では、先の加算で差が失われるため改善できません。
動作環境と対応バージョン
Math.log1p()はECMAScript 2015で追加された標準の数学メソッドです。ブラウザー専用の機能ではなく、ブラウザーとNode.jsの両方で利用できます。外部ライブラリーや通信、DOMへのアクセスは必要ありません。
2026年10月12日時点で確認したMDNでは、2015年7月から主要ブラウザー間で広く利用できる機能とされています。本記事の対象は現在のChrome、Edge、Firefox、Safariと、Node.js 22系・24系です。ブラウザーごとの最古の対応バージョンやNode.jsでの初搭載バージョンは、ここでは断定しません。
古い組み込みブラウザーなども対象にする場合は、次の式で機能の有無を確認できます。
console.log(typeof Math.log1p === "function");対応環境での期待出力はtrueです。未対応ならfalseになり、そのまま呼び出すことはできません。後述の完成コードには、未対応環境を検出して停止する処理も入れます。
Node.jsはV8を利用し、安定したJavaScript機能を通常は追加フラグなしで提供します。この仕組みはNode.jsのECMAScript対応解説で説明されています。本記事の対象バージョンでは、実験用フラグを付ける必要はありません。
引数と戻り値を理解する
呼び出し方はMath.log1p(x)です。Mathはコンストラクターではないため、インスタンスを作らず直接呼び出します。
- 引数
xは、1へ加える数値です。増減率なら、5%を0.05として渡します。
- 戻り値は
Numberで、1 + xの自然対数です。
- 有限の
xが-1より大きければ、戻り値も有限になります。
xが-1なら-Infinity、-1より小さければNaNになります。
0は0、-0は-0、InfinityはInfinityになります。
同期処理なので、戻り値はその場で受け取れます。Promiseを返さず、awaitやコールバックも不要です。
APIの仕様と業務上の制約を分ける
API自体は-1を受け付けますが、その結果は有限の数ではありません。集計や画面表示で有限値だけを扱いたいなら、アプリケーション側ではx > -1を条件にするのが自然です。
また、MathのメソッドはBigIntを扱いません。数値の文字列などが暗黙変換されるケースに依存すると入力ミスを見逃すため、本記事の完成コードでは有限のNumberだけを受け付けます。数学メソッドの精度が実装に依存することも、MDNのMathで説明されています。
入力チェック付きのコードを実行する
次のコード全体をコピーしてください。割合を対数へ変換するtoLogChangeと、正常値・不正値の動作を確認する処理を一緒にしています。依存パッケージはありません。
(() => {
if (typeof Math.log1p !== "function") {
throw new Error("この環境はMath.log1p()に対応していません");
}
function toLogChange(rate) {
if (typeof rate !== "number" || !Number.isFinite(rate)) {
throw new TypeError("増減率は有限のNumberで指定してください");
}
if (rate <= -1) {
throw new RangeError("増減率は-1より大きくしてください");
}
return Math.log1p(rate);
}
const samples = [
{ label: "変化なし", rate: 0 },
{ label: "5%増加", rate: 0.05 },
{ label: "5%減少", rate: -0.05 },
{ label: "微小な増加", rate: 1e-16 }
];
for (const sample of samples) {
const result = toLogChange(sample.rate);
const display = sample.rate === 1e-16
? result.toExponential(3)
: result.toFixed(6);
console.log(`${sample.label}: ${display}`);
}
const invalidRates = [-1, -1.1, NaN, Infinity, "0.05"];
for (const rate of invalidRates) {
try {
toLogChange(rate);
} catch (error) {
console.log(`${String(rate)}: ${error.name}`);
}
}
})();ブラウザーでの実行方法
対象ブラウザーで開発者ツールを開き、Consoleを選択して、コード全体を貼り付けて実行します。HTMLファイルの作成やWebサーバーの起動は不要です。表示される場所はページ本文ではなくConsoleです。
コードは即時実行関数で囲んでいるため、変数はその関数の中だけに存在します。同じコードをもう一度実行しても、トップレベルの同名変数の宣言が衝突しません。
Node.jsでの実行方法
Node.js 22系または24系を用意し、コード全体をUTF-8のlog-change.cjsへ保存します。ターミナルで保存先のフォルダーへ移動して、次のコマンドを実行してください。
node --version
node log-change.cjs最初のコマンドで使用バージョンを確認し、次のコマンドでコードを実行します。ファイルを渡して起動する方法はNode.jsのスクリプト実行ガイドに沿っています。
完成コードの期待出力
ブラウザーとNode.jsのどちらでも、ログとして次の内容が順に表示される想定です。ブラウザーによってはログの横にソース位置なども表示されます。
変化なし: 0.000000
5%増加: 0.048790
5%減少: -0.051293
微小な増加: 1.000e-16
-1: RangeError
-1.1: RangeError
NaN: TypeError
Infinity: TypeError
0.05: TypeErrorこの出力は仕様と計算内容から導いた期待値であり、実機実行の記録ではありません。小数の末尾は実装差があり得るため、例では表示桁数を絞っています。
失敗例と制約
不正な範囲でも例外になるとは限らない
APIを直接呼ぶ場合、数値が計算範囲を外れていても、例外ではなく特殊な値が返ります。
console.log(Math.log1p(-1));
console.log(Math.log1p(-2));期待出力は次のとおりです。
-Infinity
NaNこの呼び出しをtry...catchで囲むだけでは、不正な範囲を検出できません。完成コードが事前に検査して例外を投げるのは、処理を止める条件を明確にするためです。APIが返す特殊値と、独自関数が投げる例外を区別してください。
パーセントと割合を取り違えない
5%増加に5を渡すと、計算対象は1.05ではなく6になります。これは構文エラーでも範囲エラーでもないため、入力チェックだけでは見つかりません。画面で受け付ける単位を決め、パーセントなら100で割ってから渡します。
たとえば、入力値が数値のpercentなら、割合はpercent / 100です。ただし入力欄の文字列を数値へ変換する処理は別途必要です。空欄や単位付き文字列をどう扱うかを先に決めると、誤った変化量を計算しにくくなります。
入力に含まれない精度は復元できない
元データを丸めた段階で変化がゼロになっている場合、Math.log1p()でも復元できません。改善できるのは、保持している小さい数値を対数計算へ渡す際の精度です。
また、結果を===で固定の小数値と比較する設計は避け、用途に合った許容誤差で判断してください。表示用に桁数を整える処理と、内部の計算精度は別の問題です。
活用事例と実装を改善するポイント
時系列の増減率を対数変化へ変換すると、掛け算で連続する変化を足し算で表せます。たとえば、ある期間で5%増え、次の期間で5%減った場合、倍率は1.05 * 0.95なので元の値には戻りません。対数変化を加えると、このわずかな減少を負の値として扱えます。
完成コードの5%増加と5%減少の結果は、符号を反転しただけの同じ大きさにはなっていません。増加と減少の基準が各時点の値で変わることが、結果にも表れます。
別の用途として、アクセス件数など非負のデータをMath.log1p(count)で変換すると、ゼロ件を0として保持しつつ、大きな値の差を圧縮できます。可視化用の変換を考える際には、変換後の値を元の件数と混同しないよう、軸や説明に変換内容を明記します。
実装を改善するなら、変数名をvalueではなくrateやcountのように用途へ合わせ、入力の単位を関数の説明に残してください。同じAPIでも、増減率なら-1より大きい値、件数なら非負の値というように、受け付ける範囲は目的によって変わります。
公式参考資料
仕様、互換性、実行方法は次の公式資料を参照しました。
Googleの優先するニュース提供元にSEの部屋を追加
Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。
候補にSEの部屋が表示されない場合は、まだ追加できません。