ウェブエンジニア問題集

数値の扱いと計算 — toFixed・四捨五入・0.1+0.2問題・Mathの実践ガイド

金額の計算、フォーム入力値のバリデーション、ランダムな出題順——数値の扱いはあらゆる場面で登場します。一方で「0.1 + 0.20.3 にならない」「parseInt の結果がおかしい」など、JavaScript特有のハマりどころも多い分野です。

この章では、JavaScriptの数値(number 型)の前提から始めて、文字列⇄数値の変換浮動小数点の誤差数値の整形(toFixed など)Number / Math の主要メソッドを、引数つきの表で逆引きできるように整理します。

JavaScriptの数値は基本「1種類」だけ — number 型

多くの言語には int(整数)や float(小数)といった区別がありますが、JavaScriptの数値は原則すべて number 型の1種類です。整数も小数も同じ型で扱われます。

const count = 42; // 整数
const price = 19.99; // 小数
console.log(typeof count, typeof price); // "number" "number"

内部的には64ビットの浮動小数点数(IEEE 754)で表現されており、これが後述する「0.1 + 0.2 問題」の原因にもなります。

数値データを扱うイメージ

特別な数値 — NaN と Infinity

計算結果が「数値にならない」「無限大」になることがあります。これらも number 型の値です。

console.log(1 / 0); // Infinity(無限大)
console.log(-1 / 0); // -Infinity
console.log(Number('りんご')); // NaN(Not a Number:数値変換に失敗)
console.log(typeof NaN); // "number"(NaNの型はnumber)
学習者学習者

NaN の型が number なの、ちょっと混乱する…。「数値じゃない」のに「数値型」?

NaN は「数値の演算をした結果、有効な数値にならなかった」ことを表す特別な number 値、と捉えると腑に落ちます。NaN の判定には後述の Number.isNaN() を使います(=== NaN では判定できません。理由はハマりどころで解説します)。

文字列と数値の相互変換

フォームの入力値(<input>value)は常に文字列です。計算するには数値へ変換する必要があります。逆に、表示・連結のために数値を文字列へ戻すこともあります。

文字列 → 数値

方法引数戻り値・特徴
Number(value)value:変換する値数値。全体が数値でないと NaNNumber('12px')NaN
parseInt(string, radix)string:対象文字列 / radix:基数(通常 10整数。先頭から読める所まで変換(parseInt('12px', 10)12
parseFloat(string)string:対象文字列小数を含む数値。先頭から読める所まで(parseFloat('1.5em')1.5
+value(単項プラス)Number() とほぼ同じ。最短で書けるが可読性は落ちる
Number('100'); // 100
Number('100.5'); // 100.5
Number('100px'); // NaN(全体が数値でない)
 
parseInt('100px', 10); // 100(先頭の数値だけ読む)
parseFloat('100.5px'); // 100.5
 
+'42'; // 42(単項プラス)

数値 → 文字列

方法引数戻り値・特徴
String(value)value:変換する値文字列。null/undefined も安全に変換できる
num.toString(radix?)radix(省略可):基数文字列。(255).toString(16)"ff"(16進数)など基数変換に便利
`${num}`(テンプレートリテラル)最も読みやすい。実務での第一候補
String(42); // "42"
(255).toString(16); // "ff"(16進数の文字列に)
`合計: ${1000}円`; // "合計: 1000円"

浮動小数点の罠 — 0.1 + 0.2 が 0.3 にならない

JavaScript(に限らず多くの言語)で最も有名なハマりどころです。

console.log(0.1 + 0.2); // 0.30000000000000004(!)
console.log(0.1 + 0.2 === 0.3); // false(!)
学習者学習者

えっ、0.1 + 0.20.3 じゃない…?バグ?それともJavaScriptが壊れてる?

バグではありません。コンピュータは数値を2進数で表現しますが、0.10.2 は2進数では割り切れない(無限小数になる)ため、ごくわずかな誤差が生じます。10進数で 1/3 = 0.333... が割り切れないのと同じ理屈です。

先生先生

「コンピュータの小数は、ほんの少しだけズレることがある」とだけ覚えておけばOK。大事なのは“どう対処するか”だよ。

対処法1: 表示は toFixed で丸める

画面に出すだけなら、後述の toFixed で桁を丸めれば実用上は問題ありません。

(0.1 + 0.2).toFixed(2); // "0.30"(文字列)
Number((0.1 + 0.2).toFixed(2)); // 0.3(数値に戻す場合)

対処法2: 比較は「誤差の許容範囲」で行う

小数同士が「ほぼ等しい」かを判定したいときは、差が十分小さいかで比較します。

const almostEqual = Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;
console.log(almostEqual); // true

対処法3: お金の計算は「最小単位の整数」で

金額計算では誤差が致命的になります。円なら「銭」、ドルなら「セント」など最小単位の整数で計算し、表示時にだけ割るのが定石です。厳密さが要るなら decimal.js などのライブラリを使います。

// ❌ 小数のまま足すと誤差が出る
const total = 0.1 + 0.2; // 0.30000000000000004
 
// ⭕ 整数(最小単位)で計算してから戻す
const totalCents = 10 + 20; // 30
const totalYen = totalCents / 100; // 0.3

数値のフォーマット — toFixed / toLocaleString

小数の桁数を揃える — toFixed

構文: num.toFixed(digits?)

引数説明
digits(省略可)小数点以下の桁数(0〜100)。省略すると 0

戻り値: 指定桁に丸めた文字列(数値ではない点に注意)

(3.14159).toFixed(2); // "3.14"
(3).toFixed(2); // "3.00"(桁を揃えられる)
(2.5).toFixed(0); // "3"(四捨五入される。※環境差に注意)

3桁区切り・通貨表示 — toLocaleString

構文: num.toLocaleString(locales?, options?)

引数説明
locales(省略可)言語・地域('ja-JP' など)。省略すると実行環境の設定
options(省略可)表示オプション({ style: 'currency', currency: 'JPY' } など)

戻り値: 地域の慣習に従って整形した文字列

(1234567).toLocaleString(); // "1,234,567"(3桁区切り)
 
(1234567).toLocaleString('ja-JP', {
  style: 'currency',
  currency: 'JPY',
}); // "¥1,234,567"

Number の静的メソッド・プロパティ

Number には、数値の判定や定数として使う静的メンバーが用意されています。

メンバー引数戻り値・用途
Number.isInteger(value)value:調べる値boolean(整数か)
Number.isNaN(value)value:調べる値booleanNaN か。安全な NaN 判定
Number.isFinite(value)value:調べる値boolean(有限の数値か。Infinity を弾ける)
Number.parseInt(str, radix)str / radixグローバルの parseInt と同じ(モジュール的に明示できる)
Number.MAX_SAFE_INTEGER安全に扱える整数の最大値(約 9007兆)
Number.EPSILON浮動小数点比較に使う「ごく小さな値」
Number.isInteger(42); // true
Number.isInteger(42.5); // false
Number.isNaN(Number('x')); // true
Number.isFinite(1 / 0); // false(Infinityは有限でない)

Math オブジェクト — よく使う計算

Math は計算用の関数・定数をまとめた組み込みオブジェクトです。new Math() のようにインスタンス化はせず、Math.round(...) のように直接呼び出します

数値を計算・加工するイメージ
メソッド引数戻り値・用途
Math.round(x)x:数値四捨五入した整数
Math.floor(x)x:数値切り捨て(小さい方の整数へ)
Math.ceil(x)x:数値切り上げ(大きい方の整数へ)
Math.trunc(x)x:数値小数部を捨てる(符号はそのまま)
Math.abs(x)x:数値絶対値
Math.max(...nums)複数の数値最大値
Math.min(...nums)複数の数値最小値
Math.pow(base, exp)base:底 / exp:指数べき乗(base ** exp でも同じ)
Math.sqrt(x)x:数値平方根
Math.random()0 以上 1 未満の乱数
Math.round(2.5); // 3
Math.floor(2.9); // 2
Math.ceil(2.1); // 3
Math.abs(-5); // 5
Math.max(3, 1, 4, 1, 5); // 5
Math.pow(2, 10); // 1024(2 ** 10 と同じ)

配列の最大値・最小値はスプレッドで渡す

Math.max / Math.min は配列を直接受け取れません。スプレッド構文(...)で展開して渡します。

const scores = [82, 95, 70, 88];
 
Math.max(scores); // NaN(配列をそのまま渡すとダメ)
Math.max(...scores); // 95(スプレッドで展開)
先生先生

Math.max(...arr) のスプレッド渡しは頻出パターン。分割代入とスプレッド構文で扱った ... がここでも活きてくるよ。

よく使うパターン: 範囲指定のランダムな整数

Math.random()(0以上1未満)と Math.floor を組み合わせると、好きな範囲の整数が作れます。クイズの出題順シャッフルや抽選などで頻出です。

// min 以上 max 以下のランダムな整数を返す
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
 
randomInt(1, 6); // サイコロ(1〜6のいずれか)

早見表

やりたいこと使うもの
文字列を整数にparseInt(str, 10)
文字列を数値に(小数含む)Number(str)
小数の桁を揃えて表示num.toFixed(2)(戻り値は文字列)
3桁区切り・通貨表示num.toLocaleString('ja-JP', ...)
四捨五入 / 切り捨て / 切り上げMath.round / Math.floor / Math.ceil
絶対値Math.abs(x)
配列の最大・最小Math.max(...arr) / Math.min(...arr)
範囲指定の乱数Math.floor(Math.random() * 幅) + min
整数かどうか判定Number.isInteger(x)
NaN かどうか判定Number.isNaN(x)

よくあるハマりどころ

数値処理のハマりどころを確認するイメージ

1. NaN === NaN が false になる

NaN は「自分自身とも等しくない」唯一の値です。比較演算子では判定できません。

NaN === NaN; // false(!)
Number.isNaN(NaN); // true(こちらを使う)

2. toFixed の戻り値は文字列

toFixed は文字列を返すため、そのまま計算すると文字列連結になってしまいます。

const a = (1.5).toFixed(1); // "1.5"(文字列)
a + 1; // "1.51"(数値の足し算ではなく連結!)
Number(a) + 1; // 2.5(数値に戻してから計算)

3. Math.floorMath.trunc はマイナスで結果が違う

floor は「小さい方」へ、trunc は「0の方向」へ丸めます。負の数で差が出ます。

Math.floor(-1.5); // -2(より小さい整数へ)
Math.trunc(-1.5); // -1(小数部を捨てるだけ)

4. parseInt は途中までしか読まない

Number() は全体が数値でないと NaN ですが、parseInt は読める所まで読みます。用途で使い分けます。

Number('12.3.4'); // NaN
parseInt('12.3.4', 10); // 12(最初の整数部分だけ)

ちゃんと使うためのポイント

  • JavaScriptの数値は基本 number 型の1種類。整数も小数も同じ型
  • 文字列→数値は Number()(厳密)/ parseInt(str, 10)(先頭だけ)を使い分ける。parseInt は基数 10 を必ず渡す
  • 0.1 + 0.2 は誤差が出る。表示は toFixed、比較は Number.EPSILON、金額は最小単位の整数で対処
  • 桁区切り・通貨は toLocaleString() に任せる
  • NaN 判定は Number.isNaN()、整数判定は Number.isInteger()
  • Math はインスタンス化せず直接呼ぶ。配列の最大・最小は Math.max(...arr)

次の章では、日付と時刻の操作 を扱います。Date も数値(ミリ秒)と密接に関わり、getTime() で差分を計算するなど、この章の数値の知識がそのまま活きてきます。