マイナス値の丸め(切り上げ / 切り捨て / 四捨五入)について

処理する場所(言語)によって値が変わってくるという話。

1.切り上げ

1-1.JavaScript

$ node
Welcome to Node.js v18.15.0.
Type ".help" for more information.
> console.log(Math.ceil(-0.5));
-0

1-2.PHP

$ php -a
Interactive shell

php > echo ceil(-0.5);
-0

1-3.PostgreSQL

$ psql
psql (13.14)
"help"でヘルプを表示します。

postgres=> SELECT ceil(-0.5);
 ceil
ーーー
    0
(1)

1-4.Excel

Excelの切り上げ

2.切り捨て

2-1.JavaScript

$ node
Welcome to Node.js v18.15.0.
Type ".help" for more information.
> console.log(Math.floor(-0.5));
-1

2-2.PHP

$ php -a
Interactive shell

php > echo floor(-0.5);
-1

2-3.PostgreSQL

$ psql
psql (13.14)
"help"でヘルプを表示します。

postgres=> SELECT floor(-0.5);
 floor
ーーーー
    -1
(1)

2-4.Excel

Excelの切り捨て

3.round(四捨五入)

3-1.JavaScript

$ node
Welcome to Node.js v18.15.0.
Type ".help" for more information.
> console.log(Math.round(-0.4));
-0

> console.log(Math.round(-0.5));
-0

> console.log(Math.round(-0.6));
-1

3-2.PHP

$ php -a
Interactive shell

php > echo round(-0.4);
-0

php > echo round(-0.5);
-1

php > echo round(-0.6);
-1

3-3.PostgreSQL

$ psql
psql (13.14)
"help"でヘルプを表示します。

postgres=> SELECT round(-0.4);
 round
ーーーー
     0
(1)

postgres=> SELECT round(-0.5);
 round
ーーーー
    -1
(1)

postgres=> SELECT round(-0.6);
 round
ーーーー
    -1
(1)

3-4.Excel

Excelの四捨五入

4.まとめ

環境 切り上げ(-0.5) 切り捨て(-0.5) round(-0.4) round(-0.5) round(-0.6)
JavaScript -0 -1 -0 -0 -1
PHP -0 -1 -0 -1 -1
PostgreSQL 0 -1 0 -1 -1
Excel (ROUND*) -1 0 0 -1 -1
Excel (*.MATH) 0 -1 -- -- --

4−1.JavaScriptのマイナス値のroundは四捨五入でなく五捨六入

$ node
Welcome to Node.js v18.15.0.
Type ".help" for more information.
> console.log(Math.round(-1.5));
-1
> console.log(Math.round(-1.6));
-2
> console.log(Math.round(-2.5));
-2
> console.log(Math.round(-2.6));
-3

マイナス値は五捨六入の方が増える方の数が「1,2,3,4,5」の5個、減る方の数が「6,7,8,9」の4個で、プラス値の増える方「5,6,7,8,9」の5個、減る方「1,2,3,4」の4個と対応が取れるんですよね。ちなみに「気温は0度未満は五捨六入」です。

それからroundする(というか計算する)場所ですが「データベース」と「バックエンドのPHP」と「フロントエンドのJavaScript」を混在させてしまうとバグの温床になりそうです。要注意ですね。

4-2.「-0」はどうする?

自分としては「0.1〜0.4を丸めた0」と「−0.5〜-0.9を丸めた0」の区別がつくから「−0」表記はアリなんですが、「−0ってなんだよ0は0だろバーカ(もっと丁寧な表現だけどホントに言われたことがある)」と言われることもあるので確認した方がいいかもしれません。確認、大事。

4-3.切り上げ/切り捨て

「Excel (ROUND*)は絶対値で切り上げ/切り捨て」「それ以外は数量的に正しい増減で切り上げ/切り捨て」になってますが「Excel (ROUND*)形式」でないと困る時があります。


//Excel (ROUND*):絶対値で切り上げ/切り捨て
販売: 50.4円 ×  7個 = 352.8円 切り捨て  352円
返品: 50.4円 × -7個 = 352.8円 切り捨て -352円

//その他:数量的に正しい増減で切り上げ/切り捨て
販売: 50.4円 ×  7個 = 352.8円 切り捨て  352円
返品: 50.4円 × -7個 = 352.8円 切り捨て -353円 ... -1円の差額が発生!!

解決方法としては「絶対値で切り上げ/切り捨てする関数を作る」「販売入力画面ではマイナス値の入力を許可せず返品入力画面を別に用意する」があります。

5.おまけ

5-1.PostgreSQLのround()

今回、記事を書くにあたって色々と調べたらPostgreSQLがデータ型によってround()の結果が変わるということを知りました。(PostgreSQLのround()の話

JavaがMathとBigDecimalで違うって知ってたんですが。PostgreSQLお前もか。

浮動小数点データ型だと銀行丸めになるんですね。基本的に数値型は整数型とNUMERICしか使わないんであまり関係ないんですが気をつけようと思います。

$ psql
psql (13.14)
"help"でヘルプを表示します。

postgres=> SELECT round(-0.5::NUMERIC);
 round
ーーーー
    -1
(1)

postgres=> SELECT round(-0.5::DOUBLE PRECISION);
 round
ーーーー
    -0
(1)

postgres=> SELECT round(-1.5::DOUBLE PRECISION);
 round
ーーーー
    -2
(1)

postgres=> SELECT round(-2.5::DOUBLE PRECISION);
 round
ーーーー
    -2
(1)

postgres=> SELECT round(-3.5::DOUBLE PRECISION);
 round
ーーーー
    -4
(1)

5-2.PHPのround()

PHPはオプションを指定して挙動を変えることができます。

$ php -a
Interactive shell
php >
php > //PHP_ROUND_HALF_EVENは「JSと同じ」じゃないんだなぁ「0.5 → 0」になるんだよ
php >
php > echo round(0.6, 0, PHP_ROUND_HALF_EVEN);
1

php > echo round(0.5, 0, PHP_ROUND_HALF_DOWN);
0

php > echo round(-0.5, 0, PHP_ROUND_HALF_DOWN);
0

php > echo round(-0.6, 0, PHP_ROUND_HALF_DOWN);
-1

php >
php > //PHP_ROUND_HALF_EVENで銀行丸めもできる
php >
php > echo round(2.5, 0, PHP_ROUND_HALF_EVEN);
2

php > echo round(1.5, 0, PHP_ROUND_HALF_EVEN);
2

php > echo round(0.5, 0, PHP_ROUND_HALF_EVEN);
0

php > echo round(-0.5, 0, PHP_ROUND_HALF_EVEN);
0

php > echo round(-1.5, 0, PHP_ROUND_HALF_EVEN);
-2

php > echo round(-2.5, 0, PHP_ROUND_HALF_EVEN);
-2