2.

JavaScript の算術演算子|+ の文字列連結・小数の誤差・NaN

編集
この記事の要点
  • + だけは片方が文字列だと連結になる- * / は数値に変換される
  • 整数どうしの /小数を返す。切り捨ては Math.trunc()
  • %負数のとき符号が左側に従う-1 % 32 ではなく -1
  • 0.1 + 0.2 !== 0.3金額は最小単位の整数で扱う
  • 安全に扱える整数は ±2^53-1 まで。超えるなら BigInt

演算子の一覧

演算子意味
+加算 / 文字列連結1 + 2 → 3、"1" + 2"12"
-減算"5" - 2 → 3
*乗算3 * 4 → 12
/除算(常に小数)10 / 4 → 2.5
%剰余10 % 3 → 1
**べき乗2 ** 10 → 1024
++ --1 増やす / 減らすi++
単項 + -数値に変換 / 符号反転+"42" → 42

+ だけが特別

console.log(1 + 2);        // 3
console.log("1" + 2);      // "12"    ← 文字列連結
console.log(1 + "2");      // "12"
console.log("1" - 2);      // -1      ← - は数値に変換する
console.log("3" * "4");    // 12
console.log("10" / "2");   // 5

console.log(1 + 2 + "3");  // "33"    左から順に。(1+2) が 3、そこに "3" を連結
console.log("1" + 2 + 3);  // "123"

console.log([] + []);      // ""      配列は文字列に変換される
console.log([] + {});      // "[object Object]"
console.log(1 + true);     // 2       true は 1
console.log(1 + null);     // 1       null は 0
console.log(1 + undefined);// NaN

フォームから受け取った値は文字列なので、そのまま + すると連結されます。

const a = document.querySelector("#a").value;   // "10"
const b = document.querySelector("#b").value;   // "5"

console.log(a + b);                  // "105"   ← 連結されてしまう
console.log(Number(a) + Number(b));  // 15      明示的に変換する
console.log(+a + +b);                // 15      単項 + でも変換できる
console.log(parseInt(a, 10) + parseInt(b, 10)); // 15
変換方法"12abc""""1.5"
Number()NaN01.5
parseInt(s, 10)12NaN1
parseFloat(s)12NaN1.5

parseInt()途中まで読めれば値を返します。厳密に検証したいなら Number()Number.isNaN() を組み合わせてください。parseInt() の第 2 引数(基数)は必ず書きます。

割り算と剰余

console.log(10 / 4);           // 2.5    整数どうしでも小数になる
console.log(Math.trunc(10/4)); // 2      0 方向に切り捨て
console.log(Math.floor(-2.5)); // -3     負の無限大方向
console.log(Math.trunc(-2.5)); // -2     0 方向
console.log(Math.ceil(2.1));   // 3
console.log(Math.round(2.5));  // 3
console.log(Math.round(-2.5)); // -2     ← 「.5 は大きいほうへ」なので -2

console.log(10 % 3);           // 1
console.log(-10 % 3);          // -1     ← 符号は左側に従う
console.log(10 % -3);          // 1

// 常に正の剰余が欲しいとき
const mod = (n, m) => ((n % m) + m) % m;
console.log(mod(-10, 3));      // 2

console.log(1 / 0);            // Infinity   (エラーにならない)
console.log(-1 / 0);           // -Infinity
console.log(0 / 0);            // NaN

0 で割っても例外は出ません。Infinity がそのまま計算に混ざって表示が壊れるので、割る前に確認してください。

浮動小数点の誤差

console.log(0.1 + 0.2);              // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);      // false
console.log(0.3 - 0.1);              // 0.19999999999999998
console.log(1.005 * 100);            // 100.49999999999999

// 比較は誤差を許して行う
const eq = (a, b) => Math.abs(a - b) < Number.EPSILON * 10;
console.log(eq(0.1 + 0.2, 0.3));     // true

// 表示のときだけ丸める
console.log((0.1 + 0.2).toFixed(2)); // "0.30"(文字列になる)

// 金額は「円」ではなく最小単位の整数で持つ
const priceInSen = 1050;             // 10.50 円
const total = priceInSen * 3;        // 3150
console.log(total / 100);            // 31.5

金額・数量の計算を float のまま積み上げてはいけません。整数(銭・グラム単位)で保持し、表示するときだけ割ります。

大きな整数

console.log(Number.MAX_SAFE_INTEGER);        // 9007199254740991
console.log(9007199254740991 + 2);           // 9007199254740992(1 ずれる)
console.log(Number.isSafeInteger(2 ** 53));  // false

// BigInt を使う
const big = 9007199254740991n + 2n;          // 9007199254740993n
console.log(big);

// 数値と混ぜられない
// console.log(1n + 1);                      // TypeError
console.log(1n + BigInt(1));                 // 2n
console.log(Number(1n) + 1);                 // 2

API から来る ID が 16 桁以上ある場合、JSON.parse() の時点で値がずれます。ID は文字列で受け取るのが安全です。

NaN の扱い

console.log(Number("abc"));        // NaN
console.log(NaN === NaN);          // false   ← 自分自身とも等しくない
console.log([NaN].includes(NaN));  // true    includes だけは判定できる

console.log(Number.isNaN(NaN));    // true    (推奨)
console.log(Number.isNaN("abc"));  // false   変換しない
console.log(isNaN("abc"));         // true    変換してしまう古い関数

// 計算に NaN が 1 つ混ざると結果もすべて NaN になる
const values = [1, 2, Number("x")];
console.log(values.reduce((a, b) => a + b, 0));   // NaN

// 入口で弾く
const nums = values.filter((v) => Number.isFinite(v));
console.log(nums.reduce((a, b) => a + b, 0));     // 3

優先順位

console.log(2 + 3 * 4);       // 14    * が先
console.log((2 + 3) * 4);     // 20
console.log(2 ** 3 ** 2);     // 512   ** は右から(2 ** 9)
// console.log(-2 ** 2);      // SyntaxError(あいまいなので禁止されている)
console.log((-2) ** 2);       // 4

Math のよく使う関数

Math.abs(-5);                    // 5
Math.max(1, 5, 3);               // 5
Math.max(...[1, 5, 3]);          // 5   配列は展開して渡す
Math.min();                      // Infinity(引数なしの既定値に注意)
Math.random();                   // 0 以上 1 未満
Math.floor(Math.random() * 6) + 1;  // 1〜6 のサイコロ
Math.hypot(3, 4);                // 5
Math.sign(-3);                   // -1

// 暗号用途の乱数は Math.random を使わない
const buf = new Uint32Array(1);
crypto.getRandomValues(buf);
console.log(buf[0]);

Math.random()予測可能です。トークンやパスワードの生成には crypto.getRandomValues()crypto.randomUUID() を使ってください。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 代入演算子
  2. 算術演算子
  3. 比較演算子

最近更新/作成されたページ