JavaScript | 数値の有効桁数を指定して文字列に変換(toPrecision() メソッド)

javascrpit JavaScript
スポンサーリンク

toPrecision() の出力を「表の中で桁数を揃えて整列表示」する UI は、
データ分析や科学技術系ツールでとてもよく使われる実装パターンです。

ここでは初心者でも理解しやすいように、HTML / CSS / JS 分割版で示します。
(有効桁数をスライダーで変えて、テーブル内の数値がリアルタイムで更新される動き付き)

完成イメージ

  • 数値データ(例えば測定値など)をテーブルに表示
  • 「有効桁数」をスライダーで変更すると、全てのセルの表示が即座に更新
  • toPrecision() の挙動(丸めや指数表記など)を体感できる

See the Pen toPrecision Demo by MONO365 -Color your days- (@monoqlo365) on CodePen.

ポイント解説

ポイント内容
.toPrecision(n)有効桁数を指定して文字列化。例:12345.6789.toPrecision(3)"1.23e+4"
桁揃えすべての数を同じ桁数で表示することで、比較や整列がしやすくなる
スライダーHTML <input type="range">n を動的に変更可能
表示形式の変化有効桁を小さくすると指数表記(e+4など)が増えるのを観察できる

応用アイデア(上級)

  • 「指数表記になったら通常表示に直す」関数(前回の safeToPrecision)を組み合わせて、指数を展開表示にする。
  • Chart.js などでグラフと連動し、桁数を変えた時の変化をビジュアル表示。
  • データを fetch() で外部JSONから取得して自動更新(リアルタイムモニタ風)。

では、「指数表記を避けて常に通常の小数表記にする版」を紹介します。
toPrecision() の結果が "1.23e+4" のような指数表記になった場合、それを自動で通常の "12300" のような表記に直して表示します。

機能概要

  • toPrecision() を使って有効桁数を揃える
  • 結果が指数表記 (e+ / e-) になったら、自動で普通の小数に変換
  • スライダーで有効桁数をリアルタイム調整
  • 表内の数値が即座に更新

See the Pen toPrecision Demo #2 by MONO365 -Color your days- (@monoqlo365) on CodePen.

仕組みの説明

処理内容
toPrecision()まずは通常通り有効桁数で文字列化
toPlainString()e(指数表記)を検出し、正規表現で分解
exponent > 0小数点を右へ動かして 0 を追加
exponent < 0小数点を左へ動かして 0. + 0 埋め
結果常に "123.45""0.00012" のような普通の小数表記

実行してみよう

スライダーを動かすと:

  • 有効桁数が少ないと "12345.6789""1.235e+4""12350" に変化
  • 小さい値(0.0000009876)も "9.876e-7""0.0000009876" に展開

指数を避けつつ、桁数の感覚を直感的に掴めます。

応用アイデア

  • 固定幅で表示したい場合:padEnd() / padStart()で桁数を揃える
  • Chart.js連携:桁数変更でグラフ上のラベル値も変化させる
  • 入力ボックス付き:ユーザーが直接桁数を数値入力できるようにする

Chart.js と連動して 有効桁数(toPrecision)をスライダーで変えるとグラフの目盛り・ツールチップ・テーブル表示が即時更新される 完成版。

ポイント

  • スライダーを動かすとテーブルの toPrecision 表示が更新され、Chart.js の Y 軸目盛り(ticks)とツールチップも同じフォーマットで更新されます。
  • toPrecision が指数表記 (e+) を返したときは読みやすく通常の小数表記に展開する safeToPrecision を実装して、見た目で比較しやすくしています(一般的なケース向け・簡易実装です)。
  • Chart.js は CDN を使って読み込みます(CodePen では JS の External Scripts に https://cdn.jsdelivr.net/npm/chart.js を追加しても動作します)。

See the Pen toPrecision Demo #3 by MONO365 -Color your days- (@monoqlo365) on CodePen.

拡張案

  • ツールチップで toFixed / toPrecision を選べる切替ボタンを追加(比較用)。
  • safeToPrecision を改良して「丸めの方式(四捨五入 / 切り捨て / 切り上げ)」を切替可能にする。
  • 複数系列(線)を追加して、系列ごとに異なる精度を指定できるようにする。
  • Chart.js のアニメーションを有効化して、精度変更のたびに値がスムーズに遷移する演出を追加。
<!--
CodePen 用:HTML / CSS / JS 分割で使えるデモ
ファイル: HTML (このファイルの <style> と <script> を分離して CodePen の CSS / JS ペインへ貼り付けてください)

機能:
- 数値入力
- スライダーで有効桁数を変更 (1〜21)
- toPrecision と toFixed の即時比較表示
- 丸めの差分(元値, 表示値, 10進での丸め過程表示)を可視化
- 整数リテラルに対する注意表示
- コピー・ボタンで結果をクリップボードへ
-->

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>toPrecision() デモ — CodePen 用</title>
<style>
  :root{font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;}
  body{max-width:980px;margin:28px auto;padding:20px;line-height:1.5;color:#111}
  header{display:flex;align-items:center;gap:16px;margin-bottom:18px}
  h1{font-size:20px;margin:0}
  .card{border:1px solid #e3e3e3;border-radius:10px;padding:14px;background:#fff;box-shadow:0 6px 18px rgba(0,0,0,0.04)}
  .controls{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:14px}
  label{display:flex;flex-direction:column;font-size:13px}
  input[type="number"], input[type="range"], select{padding:8px;border-radius:6px;border:1px solid #ccc}
  .output-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}
  pre{background:#f7f7f7;padding:10px;border-radius:8px;overflow:auto}
  .small{font-size:12px;color:#666}
  .row{display:flex;align-items:center;gap:8px}
  button{padding:8px 10px;border-radius:8px;border:1px solid #bbb;background:#f4f4f4;cursor:pointer}
  .explain{margin-top:12px;font-size:14px}
  .badge{background:#eee;padding:4px 8px;border-radius:999px;font-size:12px}
  @media (max-width:720px){.output-grid{grid-template-columns:1fr}}
</style>
</head>
<body>
  <header>
    <div class="badge">Demo</div>
    <h1>toPrecision() の挙動を可視化するインタラクティブデモ</h1>
  </header>

  <section class="card">
    <div class="controls">
      <label>
        数値を入力(数値リテラル、または小数)
        <input id="numInput" type="text" value="123.456" inputmode="decimal" />
        <span class="small">例: 123.456 / 9.995 / 0.00012345</span>
      </label>

      <label>
        有効桁数 (toPrecision の引数)
        <input id="precisionRange" type="range" min="1" max="21" value="4" />
        <input id="precisionNum" type="number" min="1" max="21" value="4" style="width:72px;margin-top:6px;" />
      </label>

      <label>
        toFixed の小数桁数
        <input id="fixedNum" type="number" min="0" max="20" value="4" style="width:100px" />
      </label>

      <div style="display:flex;flex-direction:column;justify-content:space-between">
        <div class="small">プリセット</div>
        <div class="row" style="margin-top:6px">
          <button data-val="123.456">123.456</button>
          <button data-val="9.995">9.995</button>
          <button data-val="0.00012345">0.00012345</button>
          <button data-val="48">48</button>
        </div>
      </div>

    </div>

    <div class="output-grid">
      <div>
        <h3>結果(文字列)</h3>
        <div class="row">
          <strong>toPrecision:</strong>
          <pre id="toPrecisionOut">---</pre>
        </div>
        <div class="row" style="margin-top:6px">
          <strong>toFixed:</strong>
          <pre id="toFixedOut">---</pre>
        </div>
        <div class="row" style="margin-top:6px">
          <button id="copyPrecision">toPrecision をコピー</button>
          <button id="copyFixed">toFixed をコピー</button>
        </div>
      </div>

      <div>
        <h3>丸めの可視化</h3>
        <div class="small">内部的な丸めイメージを 10 進で示します(実際の内部表現は IEEE 754 の二進浮動小数点です)。</div>
        <pre id="roundingExplain">---</pre>

        <h4 style="margin-top:10px">整数リテラルの注意</h4>
        <div class="small">JavaScript では <code>48.toPrecision(3)</code> は構文エラーになります。整数に対して呼ぶ場合は <code>(48).toPrecision(3)</code> または <code>48 .toPrecision(3)</code> のように書いてください。</div>
      </div>
    </div>

    <div class="explain">
      <h4>解説</h4>
      <ol>
        <li><strong>toPrecision(n)</strong> は "有効桁数 n" で数値を表現し、必要に応じて四捨五入とゼロ補完をします。</li>
        <li>出力は文字列で、場合によって指数表記(ex. <code>1.2e+2</code>)を採用します。</li>
        <li><strong>toFixed(m)</strong> は小数点以下 m 桁を指定するメソッドで、常に通常表記(指数ではない)になります。</li>
      </ol>
    </div>
  </section>

<script>
(function(){
  const numInput = document.getElementById('numInput');
  const precisionRange = document.getElementById('precisionRange');
  const precisionNum = document.getElementById('precisionNum');
  const fixedNum = document.getElementById('fixedNum');
  const toPrecisionOut = document.getElementById('toPrecisionOut');
  const toFixedOut = document.getElementById('toFixedOut');
  const roundingExplain = document.getElementById('roundingExplain');
  const copyPrecision = document.getElementById('copyPrecision');
  const copyFixed = document.getElementById('copyFixed');

  // プリセットボタン
  document.querySelectorAll('button[data-val]').forEach(btn=>{
    btn.addEventListener('click', ()=>{
      numInput.value = btn.dataset.val;
      update();
    });
  });

  // 双方向リンク: range <-> number
  precisionRange.addEventListener('input', ()=>{ precisionNum.value = precisionRange.value; update(); });
  precisionNum.addEventListener('input', ()=>{ let v = Number(precisionNum.value) || 1; if(v<1)v=1; if(v>21)v=21; precisionNum.value = v; precisionRange.value = v; update();});
  numInput.addEventListener('input', update);
  fixedNum.addEventListener('input', update);

  copyPrecision.addEventListener('click', ()=>{
    navigator.clipboard.writeText(toPrecisionOut.textContent).then(()=>{
      copyPrecision.textContent = 'コピーしました'; setTimeout(()=>copyPrecision.textContent='toPrecision をコピー',1200);
    });
  });
  copyFixed.addEventListener('click', ()=>{
    navigator.clipboard.writeText(toFixedOut.textContent).then(()=>{
      copyFixed.textContent = 'コピーしました'; setTimeout(()=>copyFixed.textContent='toFixed をコピー',1200);
    });
  });

  function safeNumberParseRaw(text){
    // ユーザ入力をなるべくそのまま数値にする(10進を期待)
    // 空文字や無効値は NaN を返す
    // 先頭末尾の空白除去
    const t = String(text).trim();
    if(t === '') return NaN;
    // 特殊値処理
    if(t.toLowerCase() === 'infinity' || t === '∞') return Infinity;
    if(t.toLowerCase() === '-infinity' || t === '-∞') return -Infinity;
    const n = Number(t);
    return Number.isFinite(n) || Number.isNaN(n) ? n : NaN;
  }

  function update(){
    const raw = numInput.value;
    const precision = Math.max(1, Math.min(21, parseInt(precisionNum.value||'1',10)) );
    const fixed = Math.max(0, Math.min(20, parseInt(fixedNum.value||'0',10)) );

    const n = safeNumberParseRaw(raw);
    if(Number.isNaN(n)){
      toPrecisionOut.textContent = '入力が数値ではありません';
      toFixedOut.textContent = '入力が数値ではありません';
      roundingExplain.textContent = '数値を入力してください(例: 123.456, 9.995, 0.00012345)。';
      return;
    }

    // toPrecision と toFixed の出力
    try{
      const p = n.toPrecision(precision);
      const f = (typeof n.toFixed === 'function') ? n.toFixed(fixed) : '—';
      toPrecisionOut.textContent = p;
      toFixedOut.textContent = f;

      // 丸め説明(ユーザに分かりやすく10進ベースで示す)
      // 実際の丸めは二進浮動小数点で行われるため厳密には異なるが、概念説明のため簡易表示
      const explanation = buildRoundingExplanation(n, precision);
      roundingExplain.textContent = explanation;

    }catch(err){
      toPrecisionOut.textContent = 'エラー: ' + err.message;
      toFixedOut.textContent = 'エラー: ' + err.message;
      roundingExplain.textContent = 'エラー発生';
    }
  }

  function buildRoundingExplanation(value, precision){
    // 有効桁数 precision の手順を簡易的に説明(10進)
    if(!Number.isFinite(value)) return String(value);
    // 絶対値と指数
    const abs = Math.abs(value);
    if(abs === 0) return `値は 0 です。有効桁数 ${precision} では "${(0).toPrecision(precision)}" が返ります。`;

    // 指数e を求める (10^e で1<=m<10 の形にする)
    const e = Math.floor(Math.log10(abs));
    const m = value / Math.pow(10,e); // 1 <= |m| < 10
    // m を precision 桁に丸める
    const scale = Math.pow(10, precision-1);
    // m * scale を四捨五入
    const roundedM = Math.round(m * scale) / scale;

    // もし桁上がりで roundedM が 10 になったら指数を1つ増やす
    let finalE = e;
    let finalM = roundedM;
    if(Math.abs(roundedM) >= 10){ finalM = roundedM / 10; finalE = e + 1; }

    const before = `${value}`;
    const step1 = `正規化: ${value} = ${m} × 10^${e}`;
    const step2 = `有効桁 ${precision} のため、m を ${precision} 桁に四捨五入: ${roundedM}(内部計算は二進浮動小数点なので近似)`;
    const step3 = `桁上がり調整: ${finalM} × 10^${finalE}`;
    const result = `${(finalM).toPrecision(precision)}e+${finalE}`;
    const human = `表示: ${value}.toPrecision(${precision}) → ${value.toPrecision(precision)}`;

    return [
      `元の値: ${before}`,
      step1,
      step2,
      step3,
      human
    ].join('\n');
  }

  // 初期更新
  update();
})();
</script>

</body>
</html>
HTML
タイトルとURLをコピーしました