1. 丸め誤差とは何か?
JavaScript の Number 型は、内部的に 2進数の浮動小数点数(IEEE 754) で表されています。
つまり、10進数の「0.1」「0.2」などを 正確に 2進で表せません。
例:
0.1 + 0.2 === 0.3 // false 😨
0.1 + 0.2 // 0.30000000000000004
JavaScript2. どんなときに起こるの?
代表的なパターン:
| 操作 | 結果 |
|---|---|
0.1 + 0.2 | 0.30000000000000004 |
0.3 - 0.2 | 0.09999999999999998 |
0.1 * 3 | 0.30000000000000004 |
0.07 * 100 | 7.000000000000001 |
1.005.toFixed(2) | "1.00" 😱(四捨五入がズレる) |
3. 対処法まとめ
✅ 方法①:toFixed()で表示を丸める(見た目だけ)
const result = 0.1 + 0.2;
console.log(result.toFixed(2)); // "0.30"
JavaScript特徴
- 人に見せる・UIに表示するには十分。
- ただし戻り値は文字列なので、再計算には使えない。
Number((0.1 + 0.2).toFixed(2)); // => 0.3 (数値として再利用可能)
JavaScript✅ 方法②:整数にスケーリングして計算する(金融計算の定番)
「円」や「ドル」など小数を扱うときは、小数を整数化 してから計算。
// 例:お金の計算(小数点第2位まで扱う)
const price1 = 0.1 * 100; // 10
const price2 = 0.2 * 100; // 20
const total = (price1 + price2) / 100; // 30 / 100 = 0.3
console.log(total); // 0.3 ✅
JavaScriptこれで誤差を防げます。
多くの会計システムではこの方法が使われています。
✅ 方法③:許容誤差を使った比較(ε 比較)
「同じ数値か?」を判定したい場合は、少しの誤差を許容します。
const EPSILON = Number.EPSILON; // 2.220446049250313e-16
function nearlyEqual(a, b) {
return Math.abs(a - b) < EPSILON * Math.max(1, Math.abs(a), Math.abs(b));
}
console.log(nearlyEqual(0.1 + 0.2, 0.3)); // true ✅
JavaScriptまたは、実務では誤差幅を自分で決めることもあります:
Math.abs(a - b) < 1e-10 // 小数第10位まで同じなら「同じ」とみなす
JavaScript✅ 方法④:Math.round()で丸める(中間結果を整える)
途中計算で誤差が累積する場合、都度四捨五入してから次の処理をします。
function roundTo(num, digits) {
const factor = 10 ** digits;
return Math.round(num * factor) / factor;
}
roundTo(0.1 + 0.2, 2); // 0.3 ✅
JavaScript✅ 方法⑤:toPrecision() で桁数制御
表示や精度チェックに便利。
(0.1 + 0.2).toPrecision(2); // "0.30"
(1 / 3).toPrecision(4); // "0.3333"
JavaScript✅ 方法⑥:ライブラリを使う(正確な小数計算)
丸め誤差のない任意精度計算をするには、ライブラリを使うのが最も確実です。
有名どころ:
- decimal.js
- big.js
- math.js
例:decimal.js
const Decimal = require('decimal.js');
const a = new Decimal(0.1);
const b = new Decimal(0.2);
console.log(a.plus(b).toString()); // "0.3" ✅ 正確
JavaScriptこれらは金融や科学計算に最適です。
4. よくある誤差の落とし穴と解決法まとめ表
| ケース | ダメな結果 | 対処法 |
|---|---|---|
0.1 + 0.2 | 0.30000000000000004 | .toFixed() または整数計算 |
0.07 * 100 | 7.000000000000001 | Math.round(num * 100) / 100 |
1.005.toFixed(2) | “1.00” | Math.round(1.005 * 100) / 100 |
price1 + price2 === 0.3 | false | Math.abs(a - b) < 1e-10 |
| 大きな計算の累積誤差 | ずれが蓄積 | 中間ごとに roundTo() で丸める |
5. 実務でのおすすめ方針
| 目的 | おすすめ手法 |
|---|---|
| 画面表示 | .toFixed() |
| 金額計算 | 整数化(セント単位など) |
| 小数点第N位で四捨五入 | Math.round(num * 10**N) / 10**N |
| 数値比較 | Math.abs(a - b) < 1e-10 |
| 高精度が必要 | decimal.js or big.js |
💡 おまけ:お金の四捨五入関数サンプル
// 小数第2位で四捨五入
function roundMoney(value) {
return Math.round(value * 100) / 100;
}
console.log(roundMoney(1.005)); // 1.01 ✅
console.log(roundMoney(2.345)); // 2.35 ✅
JavaScriptまとめ
| ポイント | 説明 |
|---|---|
| JavaScript の Number は浮動小数点数 | → 0.1, 0.2 などが正確に表せない |
表示用には toFixed() | ただし戻り値は文字列 |
| 計算用には整数化 or 四捨五入 | |
| 比較には誤差を許容 | Math.abs(a - b) < 1e-10 |
| 高精度計算が必要ならライブラリ | decimal.js など |
「お金の計算」専用の安全な関数ライブラリ(税率計算や小数第2位の四捨五入込み)
セント単位で安全に計算する実用的なライブラリです。
中には:
- セント単位で計算する基礎関数(
toCents/fromCents/roundToCents) - 加減乗除(
add,subtract,multiply,divide) - 税抜 → 税額 → 税込(
applyTaxExclusive)と、税込 → 税抜(applyTaxInclusive)の関数 Intl.NumberFormatを使ったformatCurrency(表示用)- Node で動かせる簡易サンプル(ファイル末尾)
/**
* safe-money-utils.js
*
* シンプルで安全な「お金」計算ユーティリティ(セント単位で内部計算)
* - すべて内部は整数(最小通貨単位:セント)で計算して丸め誤差を防ぐ
* - 小数第2位での四捨五入(銀行丸めや他の丸めルールが必要なら拡張可能)
*
* 使い方(例はファイル末尾にまとめてあります)
*/
// ---------- ヘルパー ----------
/**
* 数値を安全にNumberに変換(null/undefined/空文字 -> 0)
* @param {any} v
* @returns {number}
*/
function toNumber(v) {
if (v === null || v === undefined || v === "") return 0;
if (typeof v === 'number') return v;
const n = Number(v);
return Number.isFinite(n) ? n : 0;
}
/**
* 与えられた金額(Number)をセント(最小通貨単位:整数)に変換する
* 四捨五入は「最も一般的な」方式(0.5切り上げ)を使用
* @param {number|string} amount
* @returns {number} cents
*/
function toCents(amount) {
const n = toNumber(amount);
// 小数第3位以降の誤差を防ぐため、十分大きい倍率で丸めたあと切替
// ここでは安全のため 1e12 を使って浮動小数点の影響を抑える
const scaled = Math.round(n * 100 * 1e8) / 1e8; // 中間での浮動誤差を低減
return Math.round(scaled * 100);
}
/**
* セントを元のNumber金額に戻す
* @param {number} cents
* @returns {number}
*/
function fromCents(cents) {
return cents / 100;
}
/**
* 小数第2位で四捨五入した数値(Number)を返す
* @param {number} amount
* @returns {number}
*/
function roundToCents(amount) {
return fromCents(toCents(amount));
}
// ---------- 基本的な演算(セントで処理) ----------
/** 足し算 */
function add(...amounts) {
const totalCents = amounts.reduce((acc, v) => acc + toCents(v), 0);
return fromCents(totalCents);
}
/** 引き算 */
function subtract(a, b) {
return fromCents(toCents(a) - toCents(b));
}
/** 掛け算(乗数は浮動小数点可) */
function multiply(amount, multiplier) {
const cents = toCents(amount);
const result = Math.round(cents * toNumber(multiplier));
return fromCents(result);
}
/** 割り算(除数は非ゼロであること) */
function divide(amount, divisor) {
if (Number(divisor) === 0) throw new Error('divide by zero');
const cents = toCents(amount);
const result = Math.round(cents / toNumber(divisor));
return fromCents(result);
}
// ---------- 税率計算ユーティリティ ----------
/**
* 税抜き価格に対して税額と税込金額を計算(税抜 -> 税額 -> 税込)
* 税額はセント単位で四捨五入
* @param {number|string} netAmount 税抜き金額(例: 1000 -> 1000.00)
* @param {number|string} taxRatePercent 税率(%) 例: 10 または 8
* @returns {{net: number, tax: number, gross: number}}
*/
function applyTaxExclusive(netAmount, taxRatePercent) {
const netCents = toCents(netAmount);
const rate = toNumber(taxRatePercent) / 100;
// 税額 = net * rate を計算 -> セントで丸め
const taxCents = Math.round(netCents * rate);
const grossCents = netCents + taxCents;
return {
net: fromCents(netCents),
tax: fromCents(taxCents),
gross: fromCents(grossCents)
};
}
/**
* 税込価格(総額)から税額と税抜金額を逆算(内税 -> 税抜 / 税額)
* @param {number|string} grossAmount
* @param {number|string} taxRatePercent
* @returns {{net: number, tax: number, gross: number}}
*/
function applyTaxInclusive(grossAmount, taxRatePercent) {
const grossCents = toCents(grossAmount);
const rate = toNumber(taxRatePercent) / 100;
// net = gross / (1 + rate)
const netCents = Math.round(grossCents / (1 + rate));
const taxCents = grossCents - netCents;
return {
net: fromCents(netCents),
tax: fromCents(taxCents),
gross: fromCents(grossCents)
};
}
// ---------- 表示ユーティリティ ----------
/**
* 金額を通貨形式の文字列に整形(ロケールを指定可能)
* @param {number|string} amount
* @param {string} locale e.g. 'en-US', 'ja-JP'
* @param {string} currency e.g. 'USD', 'JPY'
*/
function formatCurrency(amount, locale = 'ja-JP', currency = 'JPY') {
const n = toNumber(roundToCents(amount));
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(n);
}
// ---------- エクスポート ----------
const SafeMoney = {
toNumber,
toCents,
fromCents,
roundToCents,
add,
subtract,
multiply,
divide,
applyTaxExclusive,
applyTaxInclusive,
formatCurrency
};
// CommonJS / ESM の両方に対応する簡易エクスポート
if (typeof module !== 'undefined' && module.exports) {
module.exports = SafeMoney;
}
if (typeof window !== 'undefined') {
window.SafeMoney = SafeMoney;
}
export default SafeMoney;
// ---------- サンプル(手元で試す用) ----------
if (typeof require !== 'undefined' && require.main === module) {
// Node で直接実行したときの簡易テスト
console.log('=== SafeMoney サンプル ===');
console.log('add(0.1, 0.2) =>', SafeMoney.add(0.1, 0.2));
console.log('roundToCents(1.005) =>', SafeMoney.roundToCents(1.005));
console.log('applyTaxExclusive(100, 10) =>', SafeMoney.applyTaxExclusive(100, 10));
console.log('applyTaxInclusive(110, 10) =>', SafeMoney.applyTaxInclusive(110, 10));
console.log('formatCurrency(1234.56, "ja-JP", "JPY") =>', SafeMoney.formatCurrency(1234.56, 'ja-JP', 'JPY'));
}
JavaScript

