順番に読み進めながら学べます

数値と算術演算 — Number 型と Math

JavaScript の数値を Number 型の基本から学びます。四則演算と剰余・べき乗、Math による丸めの使い分け、toFixed の桁そろえ、小数計算で出る誤差の直し方まで扱います。

Web アプリで扱う処理の多くは、注文の小計・消費税・送料・ポイント還元といった数値の計算です。JavaScript には整数用と小数用で型を分ける仕組みが無く、どちらも同じ Number 型で扱います

この記事では、余りやべき乗を含む算術演算子、Math による丸め、桁をそろえる toFixed、そして金額計算でつまずきやすい小数の誤差までを扱います。

数値は 1 種類 — Number 型と算術演算

言語によっては、整数を入れる型と小数を入れる型が分かれています。この「値の種類」を(その値が数値なのか文字なのかといった、値の分類)と呼びます。

JavaScript に整数と小数の区別は無く、12000.08 も同じ Number 型(数値を表す型)の値です。宣言のときに型を選ばずに済むかわりに、整数どうしの割り算でも結果が小数のまま残ります

計算を指示する記号を算術演算子(値どうしの計算を表す記号)と呼びます。足し算 +、引き算 -、掛け算 *、割り算 / に加えて、割った余りを求める % と、べき乗を求める ** があります。

算術演算子と計算の結果
a + b足すa - b引くa * b掛けるa / b割るa % b割った余りa ** bべき乗7 / 2→ 3.57 % 2→ 12 ** 10→ 1024
上 2 行が演算子、下 1 行が実際の結果です。割り算は小数のまま残り% は余り、** はべき乗を返します。
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 のような値を計算できます。

1 日分の注文 3 件から、合計と平均を出します。各注文の金額 orderA・orderB・orderC は宣言済みです。

① 3 件の合計金額を表示してください。

② 1 件あたりの平均金額を表示してください。

③ 合計金額を 100 円単位で見たときの端数を表示してください。

(正しく実行できれば解説が表示されます)

JavaScript / TypeScript エディタ

コードを実行してください

Math で丸める — 切り捨て・切り上げ・四捨五入

平均 1903.3333333333333 をそのまま請求書に載せることはできません。金額や個数はどこかで整数に丸める必要があり、切り捨て・切り上げ・四捨五入のどれを選ぶかは業務のルールで決まります。

JavaScript では、この丸めの関数が Math にまとまっています。

まず関数(値を渡すと決まった処理をして、結果の値を返す、名前の付いたまとまり)という言い方を使います。渡した値から結果を受け取ることを「呼び出す」「返る」と言います。

Math(数値計算用の関数を集めた、JavaScript 組み込みのまとまり)の関数は、Math という名前のうしろにドットを打ち、その中の関数名を続けて Math.floor(1903.33) のように呼び出します。

Math.floor は小数を切り捨て、Math.ceil は切り上げ、Math.round は小数第 1 位で四捨五入します。どれも新しい数値を返すだけで、渡した変数の中身は変わりません。

丸める 3 つの関数の違い
Math.floor(149.8)小数を切り捨てる149Math.ceil(149.8)小数を切り上げる150Math.round(149.8)小数第 1 位で四捨五入する150
同じ 149.8 を渡しても、切り捨ては 149、切り上げと四捨五入は 150 を返します。
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 です。

マイナスまで下がってほしくない計算に下限を作れるので、「あと何円で送料無料か」のような表示に使えます。

カートの小計から、送料無料までの不足額と獲得ポイントを求めます。小計 cartSubtotal と、送料が無料になる金額 freeShippingLine は宣言済みです。

① 送料無料まであと何円かを表示してください。小計が無料の金額を超えていてもマイナスにならないようにしてください。

② 小計の 3.5% にあたる獲得ポイントを、小数を切り捨てて表示してください。

③ 単価 250 円の商品をあと何個足せば送料無料に届くかを、切り上げて表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

toFixed で桁をそろえる — 返るのは文字列

平均金額のように、表示する桁数を先に決めておきたい値もあります。1903.3333333333333 をそのまま画面に出すと読みにくいので、小数第 1 位までに整えて 1903.3 と見せます。

この桁そろえには toFixed を使います。

toFixed は、数値のうしろにドットを付けて呼び出すメソッド(値のうしろにドットを付けて呼び出す関数)です。括弧の中に渡す値を引数(関数やメソッドの括弧の中に渡す値)と呼びます。toFixed(1) のように残したい小数の桁数を引数に渡すと、1 つ下の桁で四捨五入して、その桁までを残した結果が返ります。ただし、返るのは数値ではなく文字列です。表示の直前だけに使い、計算を続けるときは元の数値か Math.round を使います。

Math.floor(値) は Math という入れ物の中の関数に値を渡す形、値.toFixed(桁数) は値そのものが持っている関数を呼ぶ形です。ドットの前に何が来ているかで読み分けます。

toFixed と Math.round が返す値の違い
toFixed(1)桁をそろえて文字列で返す"1903.3"toFixed(0)桁をそろえて文字列で返す"1903"Math.round(x * 10)/ 10丸めた結果を数値で返す1903.3
同じ 1903.3 に見えても、toFixed が返すのは文字列、Math.round が返すのは数値です。
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 が返す値は文字列なので、その結果をさらに足したり掛けたりすると、数値のときとは違う動きをします。文字列と数値の違い、そして値がどちらなのかを確かめる方法は、型変換の記事で扱います。

セール商品の割引率を、画面に出せる形に整えます。定価 listPrice と販売価格 salePrice は宣言済みです。

① 用意された割引率 discountRate を、丸めずにそのまま表示してください。

② 同じ割引率を、小数第 1 位までにそろえて表示してください。

③ 同じ割引率を、小数点以下を残さない形にそろえて表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

小数の誤差 — 0.1 + 0.2 が 0.3 にならない

小数を含む計算を重ねると、答えがわずかにずれることがあります。合計金額が 1 円合わない、といった不具合の原因になるので、なぜずれるのかと、どう避けるのかを先に確認します。

コンピュータは数値を 2 進数で保持します。10 進数の 0.1 は 2 進数では割り切れず、無限に続く小数になるため、ごく近い別の値として保持されます。

0.10.2 の両方がわずかにずれた状態で足されるので、結果も 0.3 から外れます。

小数を 2 進数で保持するときに起きること
0.12 進数では割り切れない0.1 に近い値として保持0.22 進数では割り切れない0.2 に近い値として保持0.1 + 0.2近い値どうしを足す0.3 よりわずかに大きい
保持された時点で 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

金額は整数で持つと安全です

小数を足し込む処理が続くほど、ずれは積み重なります。金額を扱うときは円を単位にした整数で保持し、表示の直前だけ割るという持ち方にすると、この誤差を避けられます。

配送料の計算に使う商品重量を合計します。2 つの重量 weightA と weightB は宣言済みです。

① 2 つの重量をそのまま足した結果を表示してください。

② 同じ合計を、小数第 1 位までに丸めた数値として表示してください。

③ 2 つの重量を先に 10 倍した整数で足してから、10 で割った結果を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

計算が失敗したときの値 — 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)trueNumber.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   割り切れた結果は整数

NaNInfinity は、エラーで止まらないまま次の計算へ流れていきます。合計や平均の表示がおかしいときは、まず 0 で割っていないかを疑うと、原因にたどり着きやすくなります。

QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1console.log(10 / 4); を実行するとどうなりますか?

Q2数値に対して toFixed(2) を呼んだとき、返ってくる値はどれですか?

Q3console.log(0.1 + 0.2); の表示はどれですか?