JavaScript

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として保存します。そのファイルがあるディレクトリーで、次のコマンドを実行します。

Shell
node --version
node asinh-demo.js

node --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という引数を持つわけではなく、記事内の補助関数が追加する設定です。

自己完結したコードで変換する

次のコードは、増減数の一覧を表示用の値に変換します。正常なデータに加え、文字列と不正な基準値も渡し、失敗時の動作を確認します。

JavaScript
(() => {
  "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も表示されます。

CODE
-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を分けて管理すると、表示調整とデータ集計を混同しにくくなります。まずサンプルの増減数を実際のデータに置き換え、元データの表示も残した状態で、圧縮の強さが目的に合うか確認してください。

公式参考資料

PREFERRED SOURCES

Googleの優先するニュース提供元にSEの部屋を追加

Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。

Googleの設定画面で確認する 新しいタブで開きます

候補にSEの部屋が表示されない場合は、まだ追加できません。

y.
WRITTEN BY

y_ymo10

SEの部屋で、HTML・CSSからJavaScript・React・Next.jsまで、Web制作の開発ノートを公開しています。

ほかの開発ノートを読む →