Namsang LABS
Deep Dive · #settlement #rounding #python #kotlin

コードで読む会計:丸めはポリシーだ

· Sangkyoon Nam

朝、出社してすぐSlackの通知が鳴った。経理担当のKさんからだった。

「A社の今月の精算額が請求書と1円ズレてるんですけど、何か変わりました?」

変えていない。何も変えていない。なのに1円合わない。

午後いっぱいかけて調べた。原因はあっけなかった。整数除算で小数点以下が静かに切り捨てられていた。修正は1行で済んだ。だがデータをもう少し見てみると、本当の問題はその先にあった。

#round(0.5) が常に1を返すとは限らない

学校で習った四捨五入 — 5以上なら切り上げ、4以下なら切り捨て — では0.5は1になるはずだ。Python 2まではその通りだった。

# Python 2
round(0.5)  # → 1.0
round(1.5)  # → 2.0
round(2.5)  # → 3.0

ところがPython 3で実行すると結果が変わる。

# Python 3
round(0.5)  # → 0
round(1.5)  # → 2
round(2.5)  # → 2

0.5が0に、2.5も2になる。Python 3で一体何が起きたのか。

#Banker’s Rounding(銀行丸め)

この挙動には名前がある。Banker’s Rounding、別名Round Half to Evenだ。0.5のようにちょうど中間値にあたる場合、無条件に切り上げるのではなく、最も近い偶数側に丸める方式である。

  • 0.5 → 0(最も近い偶数は0)
  • 1.5 → 2(最も近い偶数は2)
  • 2.5 → 2(最も近い偶数は2)
  • 3.5 → 4(最も近い偶数は4)

四捨五入は1件ずつ見れば何の問題もない。0.5を1に切り上げるだけだ。しかし数百万件の取引に繰り返し適用するとなると話が変わる。0.5を常に1に切り上げていると、切り上げの偏りが少しずつ蓄積される。件数が増えるほど、金額が大きくなるほど、この偏りは無視できない誤差になる。

この問題をルールとして整理したのがBanker’s Roundingだ。0.5のとき切り上げる回数と切り下げる回数を長期的に均等に分散させ、偏りを相殺する。1985年、IEEE 754浮動小数点標準の制定時にこの方式がデフォルトの丸めモードとして採用された。以降、ほとんどのハードウェアとプログラミング言語がこの標準に従うようになった。

一つ面白い話がある。「Banker’s Rounding」という名前の由来は実は不明だ。銀行業界の公式標準だったという証拠はなく、むしろ当時の銀行には統一された丸め基準そのものが存在しなかったという記録がある。名前はBanker’sだが、出典は金融業界ではない可能性がある。

日本語では「五捨六入」とも呼ばれる。四捨五入が「4は捨てて5は入れる」という意味であるように、五捨六入は「5は捨てて6は入れる」という意味だ。厳密には5のとき無条件に切り捨てるのではなく偶数側に処理するのだが、漢字の名前自体はその方向性を直感的に表している。

Python 3がこの方式に切り替えた理由も同じ文脈にある。Python 2の丸めは「0から遠ざかる方向へ」、つまり四捨五入だった。Python 3は「最も近い偶数へ」に変更した。統計的により公平で、IEEE 754標準にも準拠しているからだ。Pythonは科学計算やデータ分析で多用されるため、大量演算での偏りの蓄積を減らす方向が選ばれた。

JavaとKotlinもMath.round()は四捨五入である。差が出るのは、精密な小数点計算が必要なときに使うBigDecimalだ。BigDecimalは丸め方式を指定しないと、実行そのものを拒否する。

BigDecimal("2.5").setScale(0)
// → ArithmeticException: Rounding necessary
// (0.5を切り上げるか切り捨てるか判断できないため)

これはバグではなく設計だ。BigDecimalは丸め方式を開発者自身が明示することを強制している。そのためRoundingModeを必ずセットで使わなければならない。

import java.math.BigDecimal
import java.math.RoundingMode

BigDecimal("2.5").setScale(0, RoundingMode.HALF_UP)   // 3 — 四捨五入
BigDecimal("2.5").setScale(0, RoundingMode.HALF_EVEN) // 2 — Banker's Rounding
BigDecimal("2.5").setScale(0, RoundingMode.DOWN)      // 2 — 無条件切り捨て

同じ2.5でも、どのRoundingModeを選ぶかで結果が変わる。Pythonがデフォルトを決め打ちしているのに対し、JavaとKotlinは選択を強制する。

先に述べたように、言語ごとに結果が異なる。これはバグではなく、それぞれの設計思想だ。

言語 / メソッドデフォルトの動作
Python 3 round()Banker’s Rounding (HALF_EVEN)
Java / Kotlin Math.round()四捨五入 (HALF_UP)
Java / Kotlin BigDecimal明示必須 — 省略すると例外
JavaScript Math.round()四捨五入 (HALF_UP)

JavaScriptのMath.round()は厳密には四捨五入ではない。正の数では四捨五入と同じ動作をするが、負の数では異なる。Math.round(-0.5)-1ではなく0を返す。JavaScriptの丸めは「+∞方向への切り上げ(Round Half toward Positive Infinity)」だからだ。精算システムでマイナス金額(返金、調整など)を扱う場合、この差異が問題になり得る。

小数点が頻繁に登場するドル建てのシナリオを一つ見てみよう。N社は広告代理店に月間広告費の15%を手数料として精算している。今月の広告費は$18,336.30だった。

手数料 = $18,336.30 × 0.15 = $2,750.445

結果は小数第3位まである。ドルは小数第2位までの表現だ。この0.5セントをどう処理するか。

ポリシー請求金額
四捨五入 (HALF_UP)$2,750.45
Banker’s Rounding (HALF_EVEN)$2,750.44
切り捨て (DOWN)$2,750.44

四捨五入は切り上げる。Banker’s Roundingは前の桁の4が偶数なので切り下げる。切り捨ては無条件に切る。同じ金額なのにポリシーによって結果が変わる。

$0.01。1件だけ見れば大した額ではない。しかしクライアントが数十社あり、毎月積み重なれば、年間では無視できない数字になる。さらに重要なのは、「どちらが正しいか」という問いにコードは答えられないということだ。ロジックには何の問題もない。しかしこの差が積み重なり、精算システムと会計帳簿が合わないエラーとして経理チームに発見される。

もし決済システムと精算システムがそれぞれ別の丸めポリシーを持っていたら? フロントエンドとバックエンドが異なるポリシーを使っていて、ユーザーコンソールやアドミンの表示額とAPIの計算額にズレがあったら? 個々のコードやユニットテストだけ見ればバグはない。しかし見えないバグはさらに増幅する可能性がある。

精算システムにおいて丸めは、言語のデフォルト動作ではなく議論すべきポリシーだ。どの方式を使うかは、契約書に明記された条件、請求書の基準、業界慣行が決める。これは技術スペックではなくビジネスの意思決定であり、その決定がポリシーとしてコードに反映されなければならない。(できればコメントも残しておこう。)

// 2024-03-15 経理チームと合意。契約書 第4.2条 準拠。
val ROUNDING_MODE = RoundingMode.HALF_UP

これを明示しなければ、また Slackが鳴るかもしれない。そして今度は1円では済まないかもしれない。

この記事を書いている2026年、丸めはコードの外でもホットな話題になっている。2025年11月、アメリカで最後のペニー(1セント硬貨)が鋳造された。ペニーの供給が減るにつれ、現金取引の丸めポリシーがビジネスと法律の問題に発展している。Squareは現金取引を5セント単位で自動丸めする機能をPOSに組み込み、マクドナルドは店舗ごとに現金の丸めポリシーを導入した。顧客有利に切り下げるチェーンもあれば、切り上げるチェーンもある。州ごとの対応も異なる — インディアナ州は丸め法案を可決し、コネチカット州は無条件切り下げを勧告し、ワシントン州は非対称丸め法案を推進中だ。電子決済は依然としてセント単位まで正確に処理されるが、現金に対する連邦レベルの統一基準はまだない。丸めがポリシーであるということは、コードの中だけでなくレジの前でもまったく同じ現実なのだ。

#参考資料

  • Shopify Engineering — Bound to Round: 8 Tips for Dealing with Hanging Pennies → 丸めの偏り、ステークホルダーとのコミュニケーション、コードの一貫性まで扱う実務ガイド。
  • Founding Minds — Rounding Numbers in the Financial Domain → ビリングプロジェクトで丸め戦略を策定した経験記。IFRS/GAAPの会計基準の文脈まで扱っている。
  • Alipay+ — Banker’s Rounding → Alipay+がBanker’s Roundingを採用した理由と、標準丸めとの偏差比較を数値で示している。
  • Gmarket技術ブログ — BigDecimal A to Z → Java/KotlinでBigDecimalとRoundingModeを実務で使う際のリファレンス。

Share this post