SQLiteで端数処理(小数)

端数処理(丸め)と言えば四捨五入、切り捨て、切り上げですが、SQLiteには四捨五入(round関数)はあっても切り捨て・切り上げの関数がありません。

ユーザー定義関数で実装するというのもひとつの手ですが、今回はSQLiteの標準関数の組み合わせでどうにかしてみようと思います。
また、用途により扱いを変える必要のある負の数や.5の処理についても考えてみます。

なお、今回扱うのは原則として、小数点以下を取っ払って整数に丸める場合のみです。
任意の精度(丸め幅)で丸める場合、最初に丸め幅で割ってから計算し、最後に丸め幅をかけましょう。

  1. 準備
  2. 四捨五入
    1. 通常の四捨五入
    2. 端数が0.5のとき常に正の無限大へ丸める
    3. 端数が0.5のとき常に負の無限大へ丸める
    4. 端数が0.5のとき偶数へ丸める(最近接偶数への丸め)
  3. 切り捨て
    1. 0への丸め(絶対値の切り捨て)
    2. 負の無限大への丸め(床関数)
  4. 切り上げ
    1. 無限大への丸め(絶対値の切り上げ)
    2. 正の無限大への丸め(天井関数)

準備

とりあえず、サンプルの動作確認のため、簡単なテーブルを作っておきます。

CREATE TABLE test (value REAL NOT NULL);
INSERT INTO test VALUES( 2.0);
INSERT INTO test VALUES( 1.8);
INSERT INTO test VALUES( 1.5);
INSERT INTO test VALUES( 1.2);
INSERT INTO test VALUES( 1.0);
INSERT INTO test VALUES( 0.8);
INSERT INTO test VALUES( 0.5);
INSERT INTO test VALUES( 0.2);
INSERT INTO test VALUES( 0.0);
INSERT INTO test VALUES(-0.2);
INSERT INTO test VALUES(-0.5);
INSERT INTO test VALUES(-0.8);
INSERT INTO test VALUES(-1.0);
INSERT INTO test VALUES(-1.2);
INSERT INTO test VALUES(-1.5);
INSERT INTO test VALUES(-1.8);
INSERT INTO test VALUES(-2.0);
value
2.0
1.8
1.5
1.2
1.0
0.8
0.5
0.2
0.0
-0.2
-0.5
-0.8
-1.0
-1.2
-1.5
-1.8
-2.0

四捨五入

ただの四捨五入なら先述の通り標準関数にあるのですが、これにもいくらかのバリエーションがあるのです。

通常の四捨五入

おさらいも兼ねて、round関数による四捨五入をまず見てみましょう。

SELECT value, round(value) [round] FROM test;
valueround
2.0 2.0
1.8 2.0
1.5 2.0
1.2 1.0
1.0 1.0
0.8 1.0
0.5 1.0
0.2 0.0
0.0 0.0
-0.2 0.0
-0.5 -1.0
-0.8 -1.0
-1.0 -1.0
-1.2 -1.0
-1.5 -2.0
-1.8 -2.0
-2.0 -2.0

-0.5や-1.5の扱いを見る限り、SQLiteでは絶対値に対して四捨五入を行っているようです。

普段は直感的にもわかりやすいこの方法で充分かと思われます。
しかし、それでは困るというケースもあるかと思います。

端数が0.5のとき常に正の無限大へ丸める

数直線で言うと、負の数でも例外なく「ちょうど真ん中は右側に寄せる」という処理です。
実装としては、逆に負の数で端数が0.5のときに例外的に1を足すようにしています。

SELECT value, round(value) + CASE WHEN value >= 0.0 OR value - round(value) != 0.5 THEN 0.0 ELSE 1.0 END [round] FROM test;
valueround
2.0 2.0
1.8 2.0
1.5 2.0
1.2 1.0
1.0 1.0
0.8 1.0
0.5 1.0
0.2 0.0
0.0 0.0
-0.2 0.0
-0.5 0.0
-0.8 -1.0
-1.0 -1.0
-1.2 -1.0
-1.5 -1.0
-1.8 -2.0
-2.0 -2.0

なんだか強引な気もしますが、これよりスマートな方法は思いつかなかったです。

端数が0.5のとき常に負の無限大へ丸める

数直線で言うと、「ちょうど真ん中は左側に寄せる」という処理です。
四捨五入と呼べるか微妙だし、おそらくこれ単体では使い道のないものですが、で使います。

SELECT value, round(value) - CASE WHEN value <= 0.0 OR round(value) - value != 0.5 THEN 0.0 ELSE 1.0 END [round] FROM test;
valueround
2.0 2.0
1.8 2.0
1.5 1.0
1.2 1.0
1.0 1.0
0.8 1.0
0.5 0.0
0.2 0.0
0.0 0.0
-0.2 0.0
-0.5 -1.0
-0.8 -1.0
-1.0 -1.0
-1.2 -1.0
-1.5 -2.0
-1.8 -2.0
-2.0 -2.0

端数が0.5のとき偶数へ丸める(最近接偶数への丸め)

特定の分野で行われる丸めですね。
「bankers' rounding」とか「JIS丸め」なんて呼ばれているようです。

SELECT value, round(value) + CASE WHEN abs(value - round(value)) != 0.5 OR abs(round(value) % 2) = 0 THEN 0.0 ELSE CASE WHEN value > 0 THEN -1.0 ELSE 1.0 END END [round] FROM test;
valueround
2.0 2.0
1.8 2.0
1.5 2.0
1.2 1.0
1.0 1.0
0.8 1.0
0.5 0.0
0.2 0.0
0.0 0.0
-0.2 0.0
-0.5 0.0
-0.8 -1.0
-1.0 -1.0
-1.2 -1.0
-1.5 -2.0
-1.8 -2.0
-2.0 -2.0

切り捨て

切り捨てには、小数部分をごっそり取り除いて0に近い整数に丸める「0への丸め」と、その数以下で最大の整数に丸める「負の無限大への丸め」があります。

0への丸め(絶対値の切り捨て)

他のデータベースエンジンではtrunk関数として提供されていることがありますね。
これは、あきれ返るほど簡単なのです。

SELECT value, CAST(value AS INT) [trunc] FROM test;
valuetrunc
2.0 2
1.8 1
1.5 1
1.2 1
1.0 1
0.8 0
0.5 0
0.2 0
0.0 0
-0.2 0
-0.5 0
-0.8 0
-1.0 -1
-1.2 -1
-1.5 -1
-1.8 -1
-2.0 -2

式を見ても結果を見てもわかるとおり、この方法ではREAL型からINT型へ型が変換されます。
REAL型が必要なら改めてキャストしましょう。

負の無限大への丸め(床関数)

そもそも四捨五入は、「0.5を足して床関数を適用する」という方法で実装されることが多いです。
逆に言うと、床関数は「0.5を引いて四捨五入」すればよいのです。
先述した「端数が0.5のとき常に正の無限大へ丸める四捨五入」を元に書いてみます。

SELECT value, round(value - 0.5) + CASE WHEN value >= 0.5 OR value - round(value - 0.5) != 1.0 THEN 0.0 ELSE 1.0 END [floor] FROM test;
valuefloor
2.0 2.0
1.8 1.0
1.5 1.0
1.2 1.0
1.0 1.0
0.8 0.0
0.5 0.0
0.2 0.0
0.0 0.0
-0.2 -1.0
-0.5 -1.0
-0.8 -1.0
-1.0 -1.0
-1.2 -2.0
-1.5 -2.0
-1.8 -2.0
-2.0 -2.0

切り上げ

切り上げには、絶対値の部分を切り上げする「無限大への丸め」と、その数以上で最小の整数に丸める「正の無限大への丸め」があります。

無限大への丸め(絶対値の切り上げ)

SELECT value, CAST(value AS INT) + CASE WHEN value > CAST(value AS INT) THEN 1 WHEN value < CAST(value AS INT) THEN -1 ELSE 0 END [round] FROM test;
valueround
2.0 2
1.8 2
1.5 2
1.2 2
1.0 1
0.8 1
0.5 1
0.2 1
0.0 0
-0.2 -1
-0.5 -1
-0.8 -1
-1.0 -1
-1.2 -2
-1.5 -2
-1.8 -2
-2.0 -2

0への丸めと同様、REAL型からINT型へ型が変換されます。
REAL型が必要なら改めてキャストしましょう。

正の無限大への丸め(天井関数)

床関数の逆なので、逆の方法を使うことになります。
先述した「端数が0.5のとき常に負の無限大へ丸める四捨五入」を元に書いてみます。

SELECT value, round(value + 0.5) - CASE WHEN value <= -0.5 OR round(value + 0.5) - value != 1.0 THEN 0.0 ELSE 1.0 END [ceil] FROM test;
valueceil
2.0 2.0
1.8 2.0
1.5 2.0
1.2 2.0
1.0 1.0
0.8 1.0
0.5 1.0
0.2 1.0
0.0 0.0
-0.2 0.0
-0.5 0.0
-0.8 0.0
-1.0-1.0
-1.2-1.0
-1.5-1.0
-1.8-1.0
-2.0-2.0

うーん、自分で書いていて実に気持ち悪い。
floor関数一つあれば全部もっとわかりやすく実装できたのに。

もっとスマートで汎用性の高い方法があれば掲示板メールでお知らせくださればうれしいです。