CODE Polyglot / 直した不具合

%オフの支払額が1円少なくなる

90円の30%オフ → 62円(正しくは63円)

何が起きていたか

クーポンや割引を重ねがけしたときの支払額を計算するツールで、2つの表示がずれていました。

  • %オフの支払額が1円少なくなる: 90円の30%オフは 90 × 0.7 = 63円ちょうどです。ツールは 62円 と表示していました
  • 実質割引率が 0.1% 少なくなる: 元の価格400円で実質価格が197円なら、実質割引率は 50.75% で、四捨五入して 50.8% が正解です。ツールは 50.7% と表示していました

どのくらいの入力で起きていたか

直す前の式を Node でそのまま動かし、整数の分数で計算した正確な値と比べました。

%オフの支払額: 元の価格 1〜10,000円(1円刻み)× 割引 1〜99%(1%刻み)の 990,000通り で、%オフを1回かけた支払額を比べました。

ずれ件数
支払額が1円少ない6,569通り

ずれたのはすべて、割引後がちょうど整数になる組み合わせでした。そうした組み合わせは990,000通り中42,000通りあり、そのうち6,569通り(例: 5円の80%オフ → 0円、正しくは1円)で1円少なくなっていました。

実質割引率: 元の価格 1〜2,000円 × 実質価格 0円〜元の価格 の 2,003,000通り で、実質割引率の表示を比べました。20通りで 0.1% 少なくなっていました(例: 400円・197円 → 50.7%、400円・199円 → 50.2%)。

どうやって見つかったか

CODE Polyglot の最初の仕様 請求書の消費税と端数処理 で、税込からの割戻しが1円少なくなる不具合が見つかりました(2026-10-09)。そこで、シルギアの他のツールに同じ型の式がないかを、シルギア側で洗い出しました。

その結果、このツールの Math.floor(pay * (1 - v / 100)) が同じ型の不具合だと分かりました。90円の30%オフで 62.99999… になり、62円になることを、CODE Polyglot から Node で再現しました(2026-10-09)。

このツールに対応する仕様は、CODE Polyglot にはまだありません。同じ型の不具合は、仕様 割引後の価格と税込 でも見つかっています。

原因

0.7 は、2進数の小数では正確に表せません。90 × (1 − 30 ÷ 100) は 63 ではなく 62.99999999999999 になり、切り捨てると 62 になります。

実質割引率も同じです。203 ÷ 400 × 1000 は 507.5 ではなく 507.49999… になり、Math.round で 507(50.7%)に切り下がります。

どう直したか

計算の部分を calc.js に分けました。割引の値を十進の文字列のまま分数(BigInt の分子と分母)にし、切り捨ても四捨五入も整数の商で行います。

- pay = Math.floor(pay * (1 - v / 100));
- var rate = base > 0 ? (base - net) / base : 0;
- return (Math.round(r * 1000) / 10).toLocaleString("ja-JP") + " %";
+ // pay = floor( pay × (100·vd − vn) / (100·vd) )
+ pay = floorDiv(pay * (100n * vf.d - vf.n), 100n * vf.d);
+ const R = (2n * numer * 1000n + base) / (2n * base);
+ ratePct1 = Number(R) / 10;

7.5% のような小数の割引も、十進のまま正確に計算します。計算の決まり(各段階で1円未満を切り捨てる)は変えていません。

確かめたこと

  • 本番が配信している calc.js を、無作為な20,000通り(元の価格 × 割引1〜4段)で正確な値と突き合わせ、20,000件中20,000件が一致(2026-10-10)
  • シルギア側のテスト15件が、すべて通過

← 直した不具合の一覧へ