JavaScriptでプログラムを開発していると、計算結果の小数を処理して整数にしたい場面が頻繁に登場します。
ECMAScriptの仕様には、数値を切り捨てるための方法が複数用意されており、それぞれ挙動やパフォーマンスに細かな違いがあります。
単に「小数部分を消したい」という目的であっても、正の数と負の数で結果が変わるメソッドもあるため、仕様を正しく理解しておくことが重要です。
本記事では、JavaScriptにおける数値の切り捨て処理について、定番のMath.floorから最新のトレンドであるビット演算まで詳しく解説します。
プロジェクトの要件に合わせて最適な手法を選択できるよう、各メソッドの特徴と使い分けのポイントを整理していきましょう。
JavaScriptの切り捨て処理における基本メソッド
JavaScriptにおいて、最も一般的で直感的な切り捨てメソッドはMathオブジェクトに用意されています。
特にMath.floorとMath.truncは頻繁に使用されますが、これらは似ているようで異なる挙動を示します。
まずは、これらの標準的なメソッドの仕組みと基本的な使い方から確認していきましょう。
Math.floorによる切り捨て
Math.floorは、引数として与えられた数値以下の最大の整数を返すメソッドです。
日本語では「床関数」とも呼ばれ、数値を数直線上で左方向(小さい方)にある最も近い整数に丸める処理を行います。
正の数の場合は単純に小数点以下を取り除いた形になりますが、負の数の場合は注意が必要です。
// Math.floorの使用例
const val1 = 4.7;
const val2 = -4.7;
console.log(Math.floor(val1));
console.log(Math.floor(val2));
4
-5
実行結果からわかる通り、-4.7に対してMath.floorを適用すると、-4ではなく-5が返されます。
これは、-5が-4.7以下の最大の整数であるためです。
Math.truncによる単純な切り捨て
ES2015(ES6)で導入されたMath.truncは、純粋に小数点以下を取り除く(切り捨てる)ためのメソッドです。
Math.floorとは異なり、正の数であっても負の数であっても、単に0に近い方の整数に丸めます。
負の数値において「-4.7を-4にしたい」という要件がある場合は、こちらのメソッドが適しています。
// Math.truncの使用例
console.log(Math.trunc(4.7));
console.log(Math.trunc(-4.7));
4
-4
このように、数値の正負に関わらず「小数点以下を無視する」という直感的な動作を実現できます。
ビット演算を利用した高速な切り捨て
JavaScriptでは、ビット演算子を使用することで数値を32ビット整数に変換する副作用を利用し、高速な切り捨てを実現できます。
パフォーマンスが極めて重視されるゲーム開発や大量のデータ処理において、古くから使われてきたテクニックです。
ただし、可読性が低下する側面もあるため、チーム開発では慎重に導入を検討する必要があります。
二重否定(チルダ演算子)による整数化
ビット否定演算子である~(チルダ)を2回重ねることで、数値を整数に変換することができます。
この手法は通称「ダブルチルダ」と呼ばれ、Math.truncとほぼ同じ結果を得ることが可能です。
// ダブルチルダの使用例
const price = 1250.95;
const discounted = ~~price;
console.log(discounted);
1250
~~は、まず数値を32ビット整数に変換し、ビット反転を2回行うことで元の数値の整数部分を取り出します。
記述が非常に短くなるため、コードの圧縮効果もありますが、初見では意図が伝わりにくいというデメリットもあります。
OR演算子による切り捨て
ビットOR演算子である|を使用して「0との論理和」を取ることでも、同様の切り捨てが可能です。
数値 | 0という記述は、多くのJavaScriptエンジンにおいて高速に処理されます。
// OR演算子による切り捨て
console.log(15.8 | 0);
console.log(-15.8 | 0);
15
-15
挙動はMath.truncと同じく、単純な小数点以下の除去となります。
ただし、これらのビット演算には「32ビット整数の範囲(約-21億〜21億)を超える数値には正しく動作しない」という重要な制約があります。
非常に大きな数値を扱う可能性がある場合は、ビット演算ではなくMath.truncを使用するようにしましょう。
各手法の挙動比較まとめ
これまでに紹介した各メソッドの挙動を、正の数、負の数、そして特殊な値において比較してみましょう。
適切なメソッドを選択するために、以下の表を参考にしてください。
| 入力値 | Math.floor | Math.trunc | ~~ (ダブルチルダ) |
|---|---|---|---|
| 3.9 | 3 | 3 | 3 |
| -3.1 | -4 | -3 | -3 |
| NaN | NaN | NaN | 0 |
| Infinity | Infinity | Infinity | 0 |
表から分かるように、ビット演算はNaNやInfinityを強制的に0に変換するという特徴があります。
エラーハンドリングを重視する場合は、Mathオブジェクトのメソッドを利用するのが安全です。
小数点第n位で切り捨てる方法
JavaScriptの標準メソッドは整数への切り捨てしかサポートしていませんが、実務では「小数点第2位まで残して切り捨てたい」といったケースが多くあります。
このような場合には、「桁をずらしてから切り捨て、再び戻す」というロジックを自作します。
汎用的な切り捨て関数の実装
以下の関数は、任意の桁数で切り捨てを行うための実装例です。
/**
* 任意の桁数で切り捨てる関数
* @param {number} value - 対象の数値
* @param {number} precision - 残したい小数点以下の桁数
* @returns {number} 切り捨て後の数値
*/
function floorByStep(value, precision) {
const factor = Math.pow(10, precision);
return Math.floor(value * factor) / factor;
}
console.log(floorByStep(123.4567, 2));
console.log(floorByStep(1.2345, 3));
123.45
1.234
このロジックでは、まず指定された桁数分だけ数値を大きくし(10のn乗を掛ける)、その状態で切り捨てを行い、最後に元の大きさに戻しています。
ただし、浮動小数点数の計算精度問題により、稀に極小の誤差が生じることがあります。
浮動小数点数誤差への対策
JavaScriptの数値計算では、0.1 + 0.2が0.30000000000000004になるような特有の誤差が発生します。
これを回避してより厳密な切り捨てを行いたい場合は、一度文字列として扱うか、非常に小さな値を加算して補正する方法があります。
// 誤差を考慮した精度の高い切り捨ての考え方
function preciseFloor(value, precision) {
return Number(Math.floor(value + 'e' + precision) + 'e-' + precision);
}
console.log(preciseFloor(1.00000000000001, 2));
1
指数表記を利用することで、数値計算による直接的な誤差の影響を最小限に抑えることが可能です。
実戦的なユースケースと使い分け
実際にどのような場面でどのメソッドを使うべきか、具体的なシナリオを見ていきましょう。
適切な選択をすることで、コードの意図が明確になり、バグの混入を防ぐことができます。
ページネーションのページ数計算
全アイテム数と1ページあたりの表示数から総ページ数を計算する場合、通常は切り上げ(Math.ceil)を使います。
しかし、逆に「現在のインデックスが何ページ目か」を求める際には、切り捨てが必要になります。
const index = 25; // 0から始まるインデックス
const perPage = 10;
const currentPage = Math.floor(index / perPage) + 1;
console.log(`アイテム ${index} は ${currentPage} ページ目にあります。`);
アイテム 25 は 3 ページ目にあります。
この場合、負のページ数は存在しないため、Math.floorを用いるのが最も一般的です。
グラフィック処理と座標計算
Canvas APIなどを用いた描画処理では、座標が小数点を含むと描画がぼやける(アンチエイリアスがかかる)ことがあります。
くっきりとした線を描画するために、座標値を整数化する場合に切り捨てが利用されます。
function drawPoint(ctx, x, y) {
// 座標を整数に変換して描画を高速化・鮮明化
const posX = x | 0;
const posY = y | 0;
ctx.fillRect(posX, posY, 1, 1);
}
このような高頻度で呼び出されるループ内では、| 0のようなビット演算子が好んで使われる傾向にあります。
パフォーマンスの観点からの考察
モダンなJavaScriptエンジン(V8など)では、Math.floorやMath.truncも高度に最適化されています。
かつてはビット演算が圧倒的に高速でしたが、現在ではその差はごく僅かです。
そのため、極限まで速度を追求するライブラリ開発でない限りは、可読性と安全性を優先してMath.floorやMath.truncを使用することを推奨します。
特に、大きな数値を扱う可能性があるWebアプリケーションでは、32ビット制限のあるビット演算は思わぬバグの原因となります。
まとめ
JavaScriptでの数値切り捨てには、複数のアプローチが存在します。
基本的には、負の数まで考慮して常に小さい方の整数を得たいなら「Math.floor」を使用します。
単純に小数点以下を消去したいなら「Math.trunc」を選ぶのが、現代のJavaScript開発において最も安全で読みやすい選択です。
一方で、ビット演算子による切り捨ては、パフォーマンス要件が極めて厳しい特殊なケースでのみ利用するようにしましょう。
それぞれのメソッドが持つ「負の数での挙動」と「扱える数値の範囲」の違いを正しく把握することで、意図した通りの数値処理を実現できるようになります。
計算精度の問題や対象となる数値の範囲を常に意識し、最適な切り捨て方法を使い分けていきましょう。
