合計は reduce に初期値 0 を渡すのが基本です。小数がずれる理由、空配列で例外になる条件、オブジェクトの配列の合計、大きすぎる数の扱いまで、実行して確かめます。
答え
reduce() に初期値 0を渡します。
example.js
const nums = [1, 2, 3, 4];
console.log(nums.reduce((sum, n) => sum + n, 0));
出力書き換えて実行できます
10
javascript
console.log([].reduce((sum, n) => sum + n));
TypeError: Reduce of empty array with no initial value
初期値の 0 を省かないでください。 空配列で例外になります(後述)。
上のコードは書き換えて実行できます。0 を 100 にしてみてください。
空配列で落ちないこと
初期値が無く要素も無いと、返すものが決まらず例外になります。
example.js
const prices = [100, 250, 80];
const expensive = prices.filter(p => p >= 1000);
console.log(expensive);
console.log(expensive.reduce((sum, n) => sum + n, 0));
出力書き換えて実行できます
[]
0
javascript
const prices = [0.1, 0.2, 0.3];
console.log(prices.reduce((sum, n) => sum + n, 0));
console.log(prices.reduce((sum, n) => sum + n, 0) === 0.6);
0.6000000000000001
false
空配列は普通に起こります。 filter() の結果が空、取得件数が0件、未入力。
example.js
const cents = [10, 20, 30];
const total = cents.reduce((sum, n) => sum + n, 0);
console.log(total);
console.log(total / 100);
出力書き換えて実行できます
60
0.6
example.js
const prices = [0.1, 0.2, 0.3];
const total = prices.reduce((sum, n) => sum + Math.round(n * 100), 0) / 100;
console.log(total);
console.log(total === 0.6);
出力書き換えて実行できます
0.6
true
初期値があれば 0 が返ります。合計は0件なら0が自然です。
小数はずれる
ここがいちばん多い事故です。
example.js
const items = [
{ name: 'りんご', price: 120, qty: 2 },
{ name: 'みかん', price: 80, qty: 3 },
];
console.log(items.reduce((sum, i) => sum + i.price, 0));
console.log(items.reduce((sum, i) => sum + i.price * i.qty, 0));
出力書き換えて実行できます
200
480
example.js
const items = [
{ name: 'りんご', price: 120, qty: 2 },
{ name: 'みかん', price: 80, qty: 3 },
];
const total = items
.map(i => i.price * i.qty)
.reduce((sum, n) => sum + n, 0);
console.log(total);
出力書き換えて実行できます
480
2進数で 0.1 を正確に表せないためで、reduce() のせいではありません。
理由は
なぜ 0.1 + 0.2 は 0.30000000000000004 になるのか にあります。
直しかた: 整数で足す
金額なら、最小単位の整数で持ちます。円なら円、ドルならセント。
javascript
const inputs = ['10', '20', '30'];
console.log(inputs.reduce((sum, n) => sum + n, 0));
0102030
example.js
const inputs = ['10', '20', '30'];
console.log(inputs.reduce((sum, n) => sum + Number(n), 0));
出力書き換えて実行できます
60
すでに小数で持っているなら、足す前に整数へ寄せます。
javascript
const inputs = ['10', 'あ', '30'];
console.log(inputs.reduce((sum, n) => sum + Number(n), 0));
NaN
example.js
const inputs = ['10', 'あ', '30', ''];
const nums = inputs.map(Number).filter(Number.isFinite);
console.log(nums);
console.log(nums.reduce((sum, n) => sum + n, 0));
出力書き換えて実行できます
[ 10, 30, 0 ]
40
桁数(ここでは 100)は扱う通貨に合わせます。円なら 1、ドルなら 100 です。
オブジェクトの配列を合計する
足したい値を取り出してから足します。
example.js
const inputs = ['10', 'あ', '30', ''];
const nums = inputs
.filter(s => s.trim() !== '')
.map(Number)
.filter(Number.isFinite);
console.log(nums);
console.log(nums.reduce((sum, n) => sum + n, 0));
出力書き換えて実行できます
[ 10, 30 ]
40
javascript
const big = [9007199254740991, 1, 1];
console.log(big.reduce((sum, n) => sum + n, 0));
9007199254740992
map() を挟むと読みやすくなります。
example.js
const big = [9007199254740991n, 1n, 1n];
console.log(big.reduce((sum, n) => sum + n, 0n));
出力書き換えて実行できます
9007199254740993n
javascript
console.log([1n, 2n].reduce((sum, n) => sum + n, 0));
TypeError: Cannot mix BigInt and other types, use explicit conversions
配列を1回余分に作りますが、この規模なら差は出ません。読みやすさを優先してください。
値が数値とは限らないとき
フォームから来た値は文字列です。そのまま足すと繋がります。
example.js
const nums = [1, 2, 3, 4];
let byLoop = 0;
for (const n of nums) byLoop += n;
console.log(nums.reduce((sum, n) => sum + n, 0));
console.log(byLoop);
出力書き換えて実行できます
10
10
0 + '10' が '010' になり、以降ずっと文字列です。
数値に変換できないものが混ざると NaN になります。
NaN は一度混ざると最後まで残ります。どこで壊れたか分からなくなります。
先に弾いてください。
'' が 0 として残っていることに注意してください。
'あ' は NaN になって落ちましたが、'' は最後尾の 0 として生き残っています。
Number('') は 0 です。未入力を弾きたいなら、変換の前に落とします。
変換の詳しい話は
文字列を数値に変換する にあります。
大きすぎる合計
合計が9007199254740991を超えると、正確でなくなります。
1 を2回足したのに、1しか増えていません。
BigInt で足します。
初期値も 0n にする必要があります。0 と 0n は足せません。
他のやり方
| 書き方 |
空配列 |
向いている場面 |
reduce(…, 0) |
0 が返る |
ほぼ常にこれ |
for...of で足す |
0 が返る |
途中で抜けたい・条件が複雑なとき |
reduce()(初期値なし) |
例外 |
使わない |
どちらでも構いません。 reduce() のほうが「合計を作っている」と読めます。
reduce() そのものの説明は
Array.prototype.reduce() にまとめてあります。
キーごとに分けて合計したいなら
配列をキーごとにまとめる を見てください。