JavaScript Tips | 文字列ユーティリティ:業務用 - 小数丸め

JavaScript JavaScript
スポンサーリンク

何を作るのか:「小数丸め」ユーティリティ

ここで作りたいのは、「小数点以下を指定桁数で丸める」処理を、毎回バラバラに書かず、分かりやすい関数にまとめたものです。
「四捨五入」「切り上げ」「切り捨て」を、業務で安心して使える形にしておきます。

例えば、こんなイメージです。

roundDecimal(1.2345, 2);        // 1.23(小数2桁に四捨五入)
roundDecimal(1.2355, 2);        // 1.24
roundDecimal(1234.567, 0);      // 1235(整数に四捨五入)

roundDecimalCeil(1.2345, 2);    // 1.24(切り上げ)
roundDecimalFloor(1.2345, 2);   // 1.23(切り捨て)
JavaScript

「毎回 Math.round(num * 100) / 100 みたいな式を書く」のではなく、
「丸めたいときはこの関数を呼ぶ」と決めてしまうのがゴールです。


小数丸めの基本アイデアを整理する

「何桁目で丸めるか」をどう指定するか

小数丸めは、基本的に次の 2 つを決めるだけです。

どの桁で丸めるか(小数第 2 位、小数第 3 位、整数など)。
どう丸めるか(四捨五入、切り上げ、切り捨て)。

「どの桁か」は、10 の何乗を掛けるかで表現できます。

小数第 2 位で丸めたい → 100 を掛けてから丸めて、あとで 100 で割る。
小数第 3 位で丸めたい → 1000 を掛けてから丸めて、あとで 1000 で割る。

この「掛けて丸めて戻す」というパターンを、関数に閉じ込めます。
Math.round / Math.ceil / Math.floor を組み合わせるのが基本です。【参考:Math.round / Math.ceil / Math.floor の説明】


一番基本の「四捨五入」ユーティリティ

roundDecimal の実装

まずは、「小数を指定桁数で四捨五入する」関数を作ります。

function roundDecimal(value, fractionDigits = 0) {
  if (value == null || value === "") {
    return null;
  }

  const num = Number(value);
  if (!Number.isFinite(num)) {
    return null;
  }

  const d = Number(fractionDigits);
  if (!Number.isInteger(d) || d < 0) {
    throw new Error("fractionDigits は 0 以上の整数で指定してください");
  }

  const factor = Math.pow(10, d);

  const rounded = Math.round(num * factor) / factor;

  return rounded;
}
JavaScript

重要ポイントをかみ砕いて説明する

最初に、値のチェックと数値変換をしています。

if (value == null || value === "") {
  return null;
}

const num = Number(value);
if (!Number.isFinite(num)) {
  return null;
}
JavaScript

ここでは、「値がない」「数値としておかしい」ものは null を返す、という方針にしています。
業務コードでは、ここで null が返ってきたら「丸められない値だった」と判断して、別の処理に回す、という設計にできます。

次に、「何桁で丸めるか」を表す fractionDigits をチェックしています。

const d = Number(fractionDigits);
if (!Number.isInteger(d) || d < 0) {
  throw new Error("fractionDigits は 0 以上の整数で指定してください");
}
JavaScript

小数第 2 位なら 2、整数なら 0、という形で指定します。
マイナスや小数が来たらエラーにしてしまうことで、「変な指定」を早めに気づけるようにしています。

丸めの本体はここです。

const factor = Math.pow(10, d);

const rounded = Math.round(num * factor) / factor;
JavaScript

例えば、小数第 2 位で丸めたいとき(d = 2)は、factor = 100 になります。

1.2345 × 100 = 123.45
Math.round(123.45) = 123
123 / 100 = 1.23

という流れで、「小数第 2 位で四捨五入」が実現できます。
この「掛けて丸めて戻す」パターンは、JavaScript で小数丸めを書くときの定番です。【参考:Math.round を使った小数丸めの解説】


実際の動きを例で確認する

roundDecimal(1.2345, 2);   // 1.23
roundDecimal(1.2355, 2);   // 1.24
roundDecimal(1234.567, 0); // 1235
roundDecimal("9.1", 2);    // 9.1
roundDecimal("10", 2);     // 10
JavaScript

「必要なときだけ丸める」「すでに短い小数はそのまま」という動きになっているのが分かると思います。
"9.1" のような文字列も Number(...) で数値に変換しているので、そのまま扱えます。


切り上げ・切り捨て版のユーティリティ

roundDecimalCeil(切り上げ)

function roundDecimalCeil(value, fractionDigits = 0) {
  if (value == null || value === "") {
    return null;
  }

  const num = Number(value);
  if (!Number.isFinite(num)) {
    return null;
  }

  const d = Number(fractionDigits);
  if (!Number.isInteger(d) || d < 0) {
    throw new Error("fractionDigits は 0 以上の整数で指定してください");
  }

  const factor = Math.pow(10, d);

  const rounded = Math.ceil(num * factor) / factor;

  return rounded;
}
JavaScript

roundDecimalFloor(切り捨て)

function roundDecimalFloor(value, fractionDigits = 0) {
  if (value == null || value === "") {
    return null;
  }

  const num = Number(value);
  if (!Number.isFinite(num)) {
    return null;
  }

  const d = Number(fractionDigits);
  if (!Number.isInteger(d) || d < 0) {
    throw new Error("fractionDigits は 0 以上の整数で指定してください");
  }

  const factor = Math.pow(10, d);

  const rounded = Math.floor(num * factor) / factor;

  return rounded;
}
JavaScript

どう使い分けるか

四捨五入は「平均的な丸め」に向いていますが、業務では「必ず多めに」「必ず少なめに」という要件もよくあります。

税込金額を「1 円未満切り上げ」にしたい。
ポイント計算を「小数点以下切り捨て」にしたい。

そういうときに、roundDecimalCeilroundDecimalFloor を使い分けます。
中身は Math.roundMath.ceil / Math.floor に変えただけで、考え方は同じです。【参考:Math.ceil / Math.floor の説明】


toFixed を使うパターンとの違い

JavaScript には Number.prototype.toFixed というメソッドがあり、これも「小数を指定桁数で丸めて文字列にする」機能を持っています。【参考:Number.prototype.toFixed の仕様】

(1.2345).toFixed(2);  // "1.23"
(1.2355).toFixed(2);  // "1.24"
(10).toFixed(2);      // "10.00"
JavaScript

toFixed の特徴は次の通りです。

戻り値が「数値」ではなく「文字列」である。
指定した桁数に足りない場合は 0 で埋める(10 → “10.00”)。
内部的には丸め誤差の影響を受けることがある。【参考:toFixed の丸めと注意点】

「表示用の文字列が欲しい」だけなら toFixed はとても便利ですが、
「丸めたあとも数値として計算に使いたい」場合は、今回のように Math.round 系で数値として扱う方が分かりやすいです。


実務で意識してほしい設計のポイント

一つ目は、「丸めるタイミングを決める」ことです。
計算の途中で何度も丸めると、誤差が積み上がっていきます。
基本は「内部計算はできるだけそのまま」「表示や保存の直前で丸める」という方針にしておくと、安全です。【参考:浮動小数点と丸め誤差の注意】

二つ目は、「丸め方を一箇所に閉じ込める」ことです。
画面ごとに Math.round(num * 100) / 100 のような式を書き始めると、
どこかで「2 桁」と「3 桁」が混ざったり、「四捨五入」と「切り捨て」が混ざったりします。
roundDecimal / roundDecimalCeil / roundDecimalFloor のような関数をモジュールにまとめ、「小数丸めは必ずここを通す」と決めてしまうと、ルールがブレません。

三つ目は、「表示用のフォーマットと組み合わせる」ことです。
例えば、金額表示では「まず roundDecimal で小数 2 桁に丸める → その結果を formatCurrency で文字列にする」という二段構えにしておくと、
「丸め」と「フォーマット」が頭の中で分かれて、コードも読みやすくなります。【参考:toFixed を使った金融系フォーマットの例】


少し手を動かして感覚をつかむ

コンソールで、次のようなコードを実際に打ってみてください。

roundDecimal(1.2345, 2);
roundDecimal(1.2355, 2);
roundDecimal(1234.567, 0);

roundDecimalCeil(1.2345, 2);
roundDecimalFloor(1.2345, 2);

(1.2345).toFixed(2);
(10).toFixed(2);
JavaScript

「どの値がどう丸められるか」「四捨五入・切り上げ・切り捨ての違い」が、自分の目で見えてくるはずです。

そのうえで、自分のプロジェクトに

export function roundDecimal(...) { ... }
export function roundDecimalCeil(...) { ... }
export function roundDecimalFloor(...) { ... }
JavaScript

のような関数を置き、「小数を丸めたくなったら必ずここを通す」というルールを作ってみてください。
それだけで、あなたの小数処理は、場当たり的な式の寄せ集めから、「意図と一貫性を備えた業務レベルの小数丸めユーティリティ」に変わっていきます。

タイトルとURLをコピーしました