JavaScriptのMath.asinh():正負の差分を保ちながら表示用の値を圧縮する
Math.asinh()で負数とゼロを含む差分データを表示用に圧縮する方法を解説。引数・戻り値、ブラウザーとNode.jsの実行手順、期待出力、入力検証と非線形変換の制約を学べます。
目的:大きさの違う正負のデータを見やすくする
前月比の増減数やセンサーの基準値との差には、負数、ゼロ、正数が混在します。さらに、一部だけ絶対値が大きいと、そのままグラフに描いたとき、小さな変化が目立たなくなります。
このような表示の前処理に使えるのが、逆双曲線正弦を求めるMath.asinh()です。ゼロ付近では入力に近い値を返し、絶対値が大きくなるほど増え方が緩やかになります。負数は負数のまま、正数は正数のまま変換でき、ゼロも扱えます。
本記事では、この数学的性質を利用し、正負の差分を表示用の座標値へ変換します。これはAPIの用途として保証された専用機能ではなく、関数の性質を使った応用例です。入力データ自体は残し、表示に使う値を別に作ります。
動作環境と対応バージョン
Math.asinh()はJavaScriptの標準組み込みメソッドで、ブラウザーとNode.jsの両方で利用できます。DOMやネットワークには依存しません。MDNでは、2015年7月からブラウザー間で広く利用可能な機能として掲載されています。MDNのMath.asinh()
この記事のサンプルは、現行のChrome、Edge、Firefox、Safari、およびNode.js 22以降を実行対象とします。Node.js 22は記事で採用する実行環境であり、このメソッドの導入バージョンを意味しません。古いブラウザーや組み込みWebViewに組み込む場合は、実際の実行環境で機能の存在を確認してください。
入力検証にはNumber.isFinite()も使います。このメソッドは2015年9月からブラウザー間で広く利用可能です。API単体の対応状況と、サンプル全体が必要とする機能は分けて考えます。MDNのNumber.isFinite()
ブラウザーで実行する方法
通常のWebページを開き、開発者ツールのConsoleを選びます。後述するJavaScriptコード全体を貼り付けて実行してください。コードは関数で囲んでいるため、同じConsoleで再実行できます。画面上の要素を操作する準備は不要です。
Node.jsで実行する方法
Node.jsを導入した環境で、コードをUTF-8のasinh-demo.jsとして保存します。そのファイルがあるディレクトリーで、次のコマンドを実行します。
node --version
node asinh-demo.jsnode --versionで22以上であることを確認します。追加パッケージやpackage.jsonの作成は不要です。ファイル名をnodeに渡して実行する手順は、Node.js公式の実行ガイドに沿っています。
引数と戻り値を理解する
呼び出し方はMath.asinh(x)です。引数のxに数値を渡すと、その逆双曲線正弦を表すnumberが返ります。インスタンスを作る必要はなく、常にMath.asinh()として呼び出します。
代表的な結果は、Math.asinh(0)が0、Math.asinh(1)が約0.881374、Math.asinh(-1)が約-0.881374です。正負が反対で絶対値が同じ入力は、出力も正負が反対になります。
Infinityと-Infinityにはそれぞれ同じ無限大が返り、-0も保持されます。ただし、グラフ用の値として無限大を受け入れるかどうかは別の問題です。今回のコードでは有限の数値だけに制限します。引数と特殊値の基本動作は、MDNの仕様説明と例で確認できます。
基準となる大きさを決める
応用例では、入力を正の基準値scaleで割ってから変換します。計算式はMath.asinh(value / scale)です。
例えばscaleを100にすると、差分100を入力1として扱います。どの程度の変化から圧縮を目立たせたいかを、データの単位に合わせて決められます。API自身がscaleという引数を持つわけではなく、記事内の補助関数が追加する設定です。
自己完結したコードで変換する
次のコードは、増減数の一覧を表示用の値に変換します。正常なデータに加え、文字列と不正な基準値も渡し、失敗時の動作を確認します。
(() => {
"use strict";
if (typeof Math.asinh !== "function") {
console.log("この環境はMath.asinh()に対応していません");
return;
}
function compressDifference(value, scale) {
if (!Number.isFinite(value)) {
throw new TypeError("valueは有限の数値にしてください");
}
if (!Number.isFinite(scale) || scale <= 0) {
throw new RangeError("scaleは正の有限数にしてください");
}
const ratio = value / scale;
if (!Number.isFinite(ratio)) {
throw new RangeError("value / scaleが有限数の範囲を超えました");
}
return Math.asinh(ratio);
}
const scale = 100;
const differences = [-1000, -100, 0, 100, 1000];
for (const value of differences) {
const displayValue = compressDifference(value, scale);
console.log(`${value} -> ${displayValue.toFixed(3)}`);
}
const invalidCases = [
["100", 100],
[100, 0],
];
for (const [value, invalidScale] of invalidCases) {
try {
compressDifference(value, invalidScale);
} catch (error) {
console.log(`${error.name}: ${error.message}`);
}
}
})();compressDifference()は、有限数のvalueと正の有限数のscaleを受け取り、変換結果の数値を返します。入力を文字列から自動変換せず、条件を満たさない値には例外を投げます。Number.isFinite()が文字列を数値へ変換せず判定することは、MDNの入力判定の説明に示されています。
期待出力
ブラウザーでもNode.jsでも、コードが出力する行は次のとおりです。Consoleによっては、最後にコード全体の評価結果としてundefinedも表示されます。
-1000 -> -2.998
-100 -> -0.881
0 -> 0.000
100 -> 0.881
1000 -> 2.998
TypeError: valueは有限の数値にしてください
RangeError: scaleは正の有限数にしてください入力では1000は100の10倍ですが、変換後の絶対値は約3.4倍です。大きな差分を抑えながら、増加と減少の方向を残せています。
toFixed(3)は出力を読みやすくするための処理です。この時点で値は文字列になります。グラフの計算には、丸める前のdisplayValueを使ってください。上記は公式の数値例と計算に基づく期待出力であり、この記事の作成時にコードの実機実行は行っていません。
失敗例と制約
圧縮後の距離を元の数量差として読まない
変換は非線形です。表示上の距離が2倍でも、元データが2倍とは限りません。売上差分などに使うなら、軸に変換済みであることを示し、ツールチップや併記する値には元の数量を使います。
また、この変換は値を一定範囲へ収める処理ではありません。戻り値が必ず-1から1に収まるとは限らないため、描画領域への座標変換は別途必要です。
有限の入力でも途中で範囲を超える場合がある
非常に大きいvalueを非常に小さいscaleで割ると、両方が有限数でも商が無限大になることがあります。そのため、サンプルでは割り算の後にも検査しています。
scaleにゼロを指定しても、JavaScriptの割り算は必ずしも例外を投げません。入力チェックを省くと、不適切な値がそのまま後続の描画処理へ流れるため、補助関数で明示的に拒否します。
丸め誤差と大量処理に注意する
JavaScriptの数値計算には浮動小数点の丸めがあり、数学的な実数の結果を無限の精度で保持することはできません。MDNもMathの関数について、実装によって精度が異なり得ると説明しています。MDNのMath
この処理は同期処理です。Promiseやawaitは不要で、例外は通常のtry...catchで処理できます。ただし、大量のデータを一度に変換するとブラウザーの操作を妨げる可能性があります。表示更新のたびに同じデータを計算し直す設計は避け、まず必要な件数で処理時間を確認します。
活用事例と導入時の改善案
用途としては、地域別の人口増減、在庫の過不足、測定値の基準からのずれなどが考えられます。正負とゼロを残したまま、大きな差と小さな差を一緒に観察したい場面に向いています。
導入時は、同じグラフで比較するデータに共通のscaleを使ってください。項目ごとに変えると、同じ元の値でも表示位置が変わり、比較の意味が崩れます。期間比較でも基準を固定すると、変化を追いやすくなります。
改善案として、元の値、変換値、採用したscaleを分けて管理すると、表示調整とデータ集計を混同しにくくなります。まずサンプルの増減数を実際のデータに置き換え、元データの表示も残した状態で、圧縮の強さが目的に合うか確認してください。
公式参考資料
Googleの優先するニュース提供元にSEの部屋を追加
Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。
候補にSEの部屋が表示されない場合は、まだ追加できません。