SQL Server の ROUND で金額計算がズレる3つの罠 — 丸め方向・負の桁・暗黙の切り捨て

みなさんこんにちは!ヒロポンです!

金額の集計で ROUND を使ったら、合計が1円ズレた。検算しても合わない。sql server round で四捨五入したつもりが、出てくる数字が想定とどうも食い違う。あれ、計算合わへん??ってやつ。業務SEなら一度は通る道なんですよね。

金額は、1円ズレただけで信頼が飛ぶ。「このシステム、計算合わないんだけど」。これを言われたら、もう技術がどうこうの話じゃなくなる。

で、ROUND。一見ただの丸め関数。けど、引数の渡し方と型と丸めの仕様で、結果がしれっと変わる。今回はそのなかでも金額計算で踏みやすいやつを3つに絞ります。実測コードで、出てきた値そのまま見せていく。最後に ROUND / CEILING / FLOOR / CAST の使い分け早見表も置いときます。

📌 前提: バージョンによる挙動差はほぼありません。この記事の数値はすべて Docker 上の SQL Server 互換エンジンで実測した値です。

目次

忙しい人向けに最初にまとめ

  • 罠1: 第3引数で切り捨てになるROUND(x, 2, 1) のように第3引数に 0 以外を渡すと四捨五入でなく切り捨て。負の桁(-2)は百の位で丸まる。
  • 罠2: 四捨五入であって銀行丸めではない / float は誤差0.512.53。会計の銀行丸めとは違う。float への ROUND は浮動小数点誤差が残る。
  • 罠3: INT除算は ROUND より先に切り捨て7 / 23。小数で割りたいなら一方を decimal に。金額は decimal で持つ。

1. 第2引数の桁と、第3引数の「切り捨てフラグ」

まず引数の話。ROUND(数値, 桁) で桁を指定して四捨五入、ここまでは多くの人が知ってる。問題はその先、第3引数です。

ROUND には隠れた第3引数があって、ここに 0 以外を渡すと、四捨五入をやめて切り捨てに切り替わる。

SELECT ROUND(123.456, 2);     -- 123.460(小数2桁で四捨五入)
SELECT ROUND(123.456, 2, 1);  -- 123.450(第3引数≠0 で切り捨て・四捨五入しない)

ん?そんなフラグ立てた覚えないけど。そう思っても、拾ってきたコードに , 1 がこっそり混ざってた、なんてのはあるある。四捨五入のつもりで切り捨てが走って、合計がじわじわ削れていく。こんな感じで、気づいた時には差額が育ってる。

もう一つ見落としやすいのが、桁にマイナスを渡せること。

SELECT ROUND(12345, -2);   -- 12300(百の位で丸め・45<50 で切り捨て側)
SELECT ROUND(12350, -2);   -- 12400(50 でけた上げ)

負の桁は整数側の丸めです。-2 なら百の位。千円単位で丸めたい時には便利。ただ、思ってたより大きい単位でガサッと丸まって事故る、というのも起きる。

一行教訓: ROUND の引数は3つある。第3引数の切り捨てフラグと、負の桁を意識する。

2. ROUND は四捨五入。銀行丸めじゃない(float はもっと危ない)

ここが金額計算でいちばん怖いところ。SQL Server の ROUND四捨五入0.5 をゼロから遠い側に倒します。

-- 0.5/1.5/2.5 を四捨五入。リテラル 0.5 は桁が足りず overflow するので CAST で桁を確保
SELECT ROUND(CAST(0.5 AS decimal(2,1)), 0)   -- 1.0
     , ROUND(CAST(1.5 AS decimal(2,1)), 0)   -- 2.0
     , ROUND(CAST(2.5 AS decimal(2,1)), 0);  -- 3.0

結果は 123。きっちり四捨五入。(ちなみに裸の ROUND(0.5, 0)桁あふれエラーになります。リテラル 0.5 の型が numeric(1,1) で整数部の桁を持たず、1 に丸めた瞬間に枠からはみ出すため。地味に刺さるので、CAST で桁を確保してます。)

で、会計の世界には銀行丸め(round half to even / 偶数丸め)ってのがあって、0.5 を偶数側に倒す(0.5→02.5→2)。ROUND はこれとは別物です。仕様が銀行丸めなら、ROUND をそのまま当てると合わない。だから着手前に、どっちの丸めか握っておく必要がある。

そしてもっと厄介なのが float への ROUND。float は浮動小数点で、一部の小数を正確に持てない。丸める前から値が微妙にズレてて、ROUND の結果まで引きずられる。

-- float は 2.675 を正確に表現できないことがある
SELECT ROUND(CAST(2.675 AS float), 2);          -- 2.67(誤差で 2.675 が 2.6749… 扱い)
-- decimal なら正確
SELECT ROUND(CAST(2.675 AS decimal(10,3)), 2);  -- 2.680(正しく切り上げ)

同じ 2.675 を2桁で丸めてるのに、float と decimal で答えが割れる。ん?同じ数ちゃうん??ってなる瞬間。金額を float で持つと、この「再現しないズレ」に延々と付き合うことになります。

一行教訓: ROUND は四捨五入。会計仕様が銀行丸めなら別処理。そして金額に float は使わない。

3. INT同士の除算は、ROUNDより先に切り捨てられる

3つめは型と除算。これは ROUND の手前で起きるので、ROUND を後から足しても直らない。

SQL Server では、INT 同士の割り算は小数が切り捨てられます。ROUND を被せたところで、もう切り捨てた後。手遅れです。

SELECT 7 / 2;             -- 3(INT同士は整数除算・小数切り捨て)
SELECT ROUND(7 / 2, 0);  -- 3(すでに切り捨て済みなので ROUND しても同じ)

ROUND(7 / 2, 0) でも 3 のまま。ん?四捨五入なら 4 ちゃうの、と思った人。そう、もう 3 になった後を丸めてるだけなんですよね。

直し方は、片方を小数(decimal)にする。どっちかが decimal なら、除算の結果も小数で返ってくる。

SELECT 7 / 2.0;            -- 3.500000(一方が小数なら小数で計算)
SELECT ROUND(7 / 2.0, 0); -- 4.000000(3.5 を四捨五入)

そして金額。消費税や割引の計算は、最初から decimal で持つのが鉄則です。

-- 金額は decimal で。消費税8%を計算
SELECT CAST(1000 AS decimal(12,2)) * 0.08;                         -- 80.0000
-- 表示桁を金額の2桁に揃えるなら、結果も明示的に丸める
SELECT CAST(CAST(1000 AS decimal(12,2)) * 0.08 AS decimal(12,2));  -- 80.00

int で持てば除算で切り捨て、float で持てば誤差。金額は decimal で固定。これだけで、いい感じに事故が減ります。

一行教訓: INT除算は ROUND より先に切り捨て。小数で割るには一方を decimal に。金額は decimal で持つ。

ROUND / CEILING / FLOOR / CAST の使い分け早見表

丸め系の関数を、丸め方向・誤差・金額適性で並べときます。「切り上げたい」「切り捨てたい」で迷った時に、いい感じに引けます。

SQL Server の ROUND / CEILING / FLOOR / CAST(decimal) を、丸め方向・桁指定の可否・float誤差のリスク・金額計算への適性・主な用途で比較した早見表

最適 / 良い / 場合による / × 不可 / ! 要注意。

俺の現場で金額がズレた時の話

フリーで入った案件で、請求金額の集計バッチを書いたことがあります。テスト環境では合ってたのに、本番で月締めの合計が数円ズレる。検算しても原因が見えない。

追っていったら、消費税の計算を float でやってて、1行ごとの微妙な誤差が、何千行と積もって表に出てた。decimal に変えて、丸めを仕様書どおりの四捨五入に揃えたら、ぴたっと合いました。

金額計算は、1円のズレでも経理に詰められる世界です。あの時ほど「最初から decimal で持っとけば」と悔やんだことはない。

それ以来、金額を扱う時の手順を固定してます。まず型を decimal で確保。次に丸めの仕様を、客か仕様書で確認してから書く。たったこれだけで、検算で固まる時間がほぼ消えました。

まとめ — 金額は decimal、ROUND は仕様を確認してから

ROUND で金額がズレる。原因は、だいたいこの3つに集約されます。

  • 第3引数の切り捨てフラグ・負の桁 — 知らずに切り捨て・大きい単位で丸め
  • 四捨五入であって銀行丸めではない / float誤差 — 会計仕様と食い違う・floatで再現性のないズレ
  • INT除算の暗黙切り捨て — ROUNDより先に切り捨てられる

金額計算の事故は、技術的には小さい。でも業務側の信頼は一発で崩れる。金額は decimal で持つ。ROUND は丸め仕様を確認してから使う。この2つを徹底するだけで、「計算が合わない」はぐっと減らせます!!

よくある質問

Q1. ROUND で四捨五入したのに切り捨てになります。なぜですか?

第3引数に 0 以外の値が渡っている可能性が高いです。ROUND(x, 2, 1) のように第3引数が 0 以外だと、四捨五入ではなく切り捨てになります。四捨五入したいなら第3引数は省略するか 0 を渡します。

Q2. 会計システムで ROUND を使って大丈夫ですか?

丸めの仕様を確認してからにしてください。SQL Server の ROUND は四捨五入で、会計でよく使う銀行丸め(偶数丸め)とは違います。仕様が銀行丸めなら、ROUND だけでは合わず、別途ロジックが必要です。仕様を先に客や会計担当と握るのが安全です。

Q3. 金額を float で持ってはいけないのはなぜですか?

float は浮動小数点で、一部の小数を正確に表現できないためです。計算や丸めの過程で誤差が積もり、合計が1円ズレるなどの再現性のない不具合を生みます。金額は decimal(numeric)型で持ち、計算も decimal のまま行います。

Q4. 7 / 2 が 3 になります。3.5 にするには?

INT 同士の除算は小数が切り捨てられるためです。一方を小数にすれば小数で計算されます。7 / 2.0 または CAST(7 AS decimal(10,2)) / 2 のように書くと 3.5 が得られます。

次に読むべき記事

金額の丸めで詰まってる同業がいたら、この記事ぶん投げてやってください。どんどんシェア待ってるぜ!!

以上!

執筆者

バイブス父さん — 業務 SE 7 年 (SIer 正社員 2 / フリーランス 5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SIer の正社員からフリーランスに転じ、複数のエージェント経由で案件を回してきた経験ベースで「業務 SE 視点」の技術 + キャリア記事を書いています。

🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次