Q1console.log(10 / 4); を実行するとどうなりますか?
数値と算術演算 — Number 型と Math
JavaScript の数値を Number 型の基本から学びます。四則演算と剰余・べき乗、Math による丸めの使い分け、toFixed の桁そろえ、小数計算で出る誤差の直し方まで扱います。
Web アプリで扱う処理の多くは、注文の小計・消費税・送料・ポイント還元といった数値の計算です。JavaScript には整数用と小数用で型を分ける仕組みが無く、どちらも同じ Number 型で扱います。
この記事では、余りやべき乗を含む算術演算子、Math による丸め、桁をそろえる toFixed、そして金額計算でつまずきやすい小数の誤差までを扱います。
数値は 1 種類 — Number 型と算術演算
言語によっては、整数を入れる型と小数を入れる型が分かれています。この「値の種類」を型(その値が数値なのか文字なのかといった、値の分類)と呼びます。
JavaScript に整数と小数の区別は無く、1200 も 0.08 も同じ Number 型(数値を表す型)の値です。宣言のときに型を選ばずに済むかわりに、整数どうしの割り算でも結果が小数のまま残ります。
計算を指示する記号を算術演算子(値どうしの計算を表す記号)と呼びます。足し算 +、引き算 -、掛け算 *、割り算 / に加えて、割った余りを求める % と、べき乗を求める ** があります。
% は余り、** はべき乗を返します。console.log(1200 + 300); // 1500
console.log(1200 - 300); // 900
console.log(1200 * 3); // 3600
// 整数どうしでも、割り切れなければ小数が残る
console.log(7 / 2); // 3.5
// % は割った余り、** はべき乗
console.log(7 % 2); // 1
console.log(2 ** 10); // 1024
% は、金額の端数や「何個ずつに分けたときの余り」を求めるときに使います。合計 4380 に対して 4380 % 100 と書けば、100 円単位で見たときの端数 80 が得られます。
** はべき乗を求め、1 キロバイトが何バイトかを表す 2 ** 10 のような値を計算できます。
Math で丸める — 切り捨て・切り上げ・四捨五入
平均 1903.3333333333333 をそのまま請求書に載せることはできません。金額や個数はどこかで整数に丸める必要があり、切り捨て・切り上げ・四捨五入のどれを選ぶかは業務のルールで決まります。
JavaScript では、この丸めの関数が Math にまとまっています。
まず関数(値を渡すと決まった処理をして、結果の値を返す、名前の付いたまとまり)という言い方を使います。渡した値から結果を受け取ることを「呼び出す」「返る」と言います。
Math(数値計算用の関数を集めた、JavaScript 組み込みのまとまり)の関数は、Math という名前のうしろにドットを打ち、その中の関数名を続けて Math.floor(1903.33) のように呼び出します。
Math.floor は小数を切り捨て、Math.ceil は切り上げ、Math.round は小数第 1 位で四捨五入します。どれも新しい数値を返すだけで、渡した変数の中身は変わりません。
const points = 149.8; // 購入金額から計算したポイント
console.log(Math.floor(points)); // 149 切り捨て
console.log(Math.ceil(points)); // 150 切り上げ
console.log(Math.round(points)); // 150 四捨五入
console.log(points); // 149.8 元の値は変わらない
// max は渡した値のうち大きいほうを返す
console.log(Math.max(0, 5000 - 5200)); // 0
丸め以外でよく使うのが Math.max です。渡した値のうち大きいほうを返すので、Math.max(0, -120) は 0 です。
マイナスまで下がってほしくない計算に下限を作れるので、「あと何円で送料無料か」のような表示に使えます。
toFixed で桁をそろえる — 返るのは文字列
平均金額のように、表示する桁数を先に決めておきたい値もあります。1903.3333333333333 をそのまま画面に出すと読みにくいので、小数第 1 位までに整えて 1903.3 と見せます。
この桁そろえには toFixed を使います。
toFixed は、数値のうしろにドットを付けて呼び出すメソッド(値のうしろにドットを付けて呼び出す関数)です。括弧の中に渡す値を引数(関数やメソッドの括弧の中に渡す値)と呼びます。toFixed(1) のように残したい小数の桁数を引数に渡すと、1 つ下の桁で四捨五入して、その桁までを残した結果が返ります。ただし、返るのは数値ではなく文字列です。表示の直前だけに使い、計算を続けるときは元の数値か Math.round を使います。
Math.floor(値) は Math という入れ物の中の関数に値を渡す形、値.toFixed(桁数) は値そのものが持っている関数を呼ぶ形です。ドットの前に何が来ているかで読み分けます。
const averageOrder = 1903.3333333333333; // 注文 1 件あたりの平均金額
console.log(averageOrder.toFixed(1)); // 1903.3 文字列として返る
console.log(averageOrder.toFixed(0)); // 1903 文字列として返る
// 計算を続けたいときは、数値のまま丸める
console.log(Math.round(averageOrder * 10) / 10); // 1903.3 数値のまま
toFixed の結果は表示専用です
toFixed が返す値は文字列なので、その結果をさらに足したり掛けたりすると、数値のときとは違う動きをします。文字列と数値の違い、そして値がどちらなのかを確かめる方法は、型変換の記事で扱います。
小数の誤差 — 0.1 + 0.2 が 0.3 にならない
小数を含む計算を重ねると、答えがわずかにずれることがあります。合計金額が 1 円合わない、といった不具合の原因になるので、なぜずれるのかと、どう避けるのかを先に確認します。
コンピュータは数値を 2 進数で保持します。10 進数の 0.1 は 2 進数では割り切れず、無限に続く小数になるため、ごく近い別の値として保持されます。
0.1 と 0.2 の両方がわずかにずれた状態で足されるので、結果も 0.3 から外れます。
console.log(0.1 + 0.2); // 0.30000000000000004
// 先に整数にしてから計算し、あとで戻せばずれない
console.log((0.1 * 10 + 0.2 * 10) / 10); // 0.3
金額は整数で持つと安全です
小数を足し込む処理が続くほど、ずれは積み重なります。金額を扱うときは円を単位にした整数で保持し、表示の直前だけ割るという持ち方にすると、この誤差を避けられます。
計算が失敗したときの値 — NaN と Infinity
計算が成り立たないとき、JavaScript は実行を止めずに特別な数値を返して先へ進みます。合計が空欄になる、金額の表示がおかしくなるといった不具合の多くは、この値が混ざったまま計算が続いたことが原因です。
数値として表せない結果は NaN(Not a Number の略で、数値にならなかったことを表す値)です。注文が 0 件の日に平均を出そうとした 0 / 0 がその例です。
一方、5710 / 0 のように 0 で割ると Infinity(無限大を表す値)が返ります。どちらもエラーで止まらないため、画面に出るまで気付かないことがあります。
const orderTotal = 5710;
const orderCount = 0; // その日の注文が 0 件だった
console.log(orderTotal / orderCount); // Infinity
console.log(0 / 0); // NaN
// NaN が混ざると、そのあとの計算もすべて NaN になる
console.log(0 / 0 + 1000); // NaN
Math と同じように、Number にも数値まわりの関数がまとまっています。受け取った値が整数かどうかは Number.isInteger で確かめられます。Number.isInteger(3) は true、Number.isInteger(2.5) は false という真偽値(成り立つかどうかの 2 択を表す値)を返すので、個数のように小数を許さない値の確認に使えます。
なお値が等しいかを調べる書き方は次の記事で扱いますが、NaN だけはその方法で自分自身と比べても一致しません。
そのため NaN かどうかは専用の判定を使います。その書き方は、文字列から数値への変換とあわせて型変換の記事で扱います。
console.log(Number.isInteger(3)); // true
console.log(Number.isInteger(2.5)); // false
console.log(Number.isInteger(6 / 2)); // true 割り切れた結果は整数
NaN や Infinity は、エラーで止まらないまま次の計算へ流れていきます。合計や平均の表示がおかしいときは、まず 0 で割っていないかを疑うと、原因にたどり着きやすくなります。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2数値に対して toFixed(2) を呼んだとき、返ってくる値はどれですか?
Q3console.log(0.1 + 0.2); の表示はどれですか?