CODE Polyglot / 直した不具合

獲得ポイントが1pt少なくなる

1,000円の0.7%還元 → 6pt(正しくは7pt)

どの言語で起きるか

シルギアのツールは JavaScript で書かれています。この不具合が見つかったケースを、CODE Polyglot で10言語の「よくある書き方」で回した結果です(言語を返した答えごとにまとめ、赤い値は実際に返した間違った答え)。 JavaScript は 4件中4件で外れました。

ケース正解よくある書き方の答え(10言語)
c0020.7%還元・ちょうど整数
points
7
net
993
effective_rate
0.7
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftpoints6net994effective_rate"0.6"
  • C#✓ 正解
c0031.4%還元・ちょうど整数
points
7
net
493
effective_rate
1.4
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftpoints6net494effective_rate"1.2"
  • C#✓ 正解
c0045.8%還元・ちょうど整数
points
58
net
942
effective_rate
5.8
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftpoints57net943effective_rate"5.7"
  • C#✓ 正解
c0055.6%還元・ちょうど整数
points
7
net
118
effective_rate
5.6
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftpoints6net119effective_rate"4.8"
  • C#✓ 正解

何が起きていたか

ポイント還元を計算するツールで、獲得ポイントが1pt少なくなることがありました。

  • 1,000円の0.7%還元は 1,000 × 0.007 = 7pt ちょうどです。ツールは 6pt と表示していました
  • 125円の5.6%還元(7pt が正解)も 6pt で、実質割引率も 5.6% ではなく 4.8% と表示していました

ずれるのは、ポイントがちょうど整数になる組み合わせです。このほか、実質割引率の四捨五入で、表示が 0.1% 少なくなることもありました。

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

直す前の式を Node でそのまま動かし、整数で計算した正確な値と比べました。範囲は、支払金額 1〜20,000円(1円刻み)× 還元率 0.1〜100%(0.1%刻み・1,000種)の 20,000,000通り です。

ずれ件数
獲得ポイントが1pt少ない6,830通り
獲得ポイントは正しいのに、実質割引率の表示が 0.1% 少ない187通り

ポイントがちょうど整数になる組み合わせは20,000,000通り中170,000通りあり、そのうち6,830通りで1pt少なくなっていました。1,000種の還元率のうち、192種(0.7%・1.4%・2.8%・5.6%・5.8% など)で起きています。

実質割引率は、獲得ポイントが正しいときでも、四捨五入で 0.1% 少なくなる組み合わせがありました。例えば400円の7.3%還元は 29pt で、実質割引率は 7.25% です。四捨五入して 7.3% が正解ですが、ツールは 7.2% と表示していました。

どうやって見つかったか

CODE Polyglot の4本目の仕様 ポイント還元と実質割引率 で、正解表に「ポイントがちょうど整数になる」ケース c002〜c005 を入れました(2026-10-10)。よくある書き方は4言語(PHP・Python・Go・JavaScript)とも、4件すべてで1pt少ない答えを返しました。

元のツールも同じ式でした。Node で動かすと、1,000円の0.7%還元が 6pt、125円の5.6%還元が 6pt・実質割引率 4.8% になりました。

原因

0.7% を 0.007 にした値は、2進数の小数では正確に表せません。1,000 × (0.7 ÷ 100) は 7 ではなく 6.999999999999999 になり、切り捨てると 6 になります。

実質割引率も同じです。29 ÷ 400 × 100 × 10 は 72.5 ではなく 72.49999… になり、Math.round で 72(7.2%)に切り下がります。

どう直したか

計算の部分を calc.js に分けました。支払金額と還元率を十進の文字列のまま分数(BigInt の分子と分母)にし、切り捨ても四捨五入も整数の商で行います。計算の決まり(ポイントは切り捨て、実質割引率は小数第1位に四捨五入)は変えていません。

- var points = Math.floor(amount * (rate / 100));
- var effRate = amount > 0 ? (points / amount) * 100 : 0;
- var effRateStr = (Math.round(effRate * 10) / 10).toLocaleString("ja-JP") + "%";
+ const points = (A.n * R.n) / (A.d * 100n * R.d);
+ const net = basePrice - points;
+ const R10 = roundHalfUpDiv(points * 1000n * A.d, A.n);
+ effRatePct1 = Number(R10) / 10;

1.25% のような小数2桁の還元率も、十進のまま正確に計算します。

確かめたこと

  • 本番が配信している calc.js を、3,204,001通り(支払金額 0〜3,000円 × 還元率 0〜100% の0.1%刻みの全組み合わせ+無作為な200,000通り)で正確な値と突き合わせ、3,204,001件中3,204,001件が一致(2026-10-10)
  • 修正後の本番のページをヘッドレス Chrome で開き、1,000円・0.7% → 7pt・0.7%、500円・1.4% → 7pt・1.4%、125円・5.6% → 7pt・5.6% と表示されることを確認
  • シルギア側のテスト16件が、すべて通過

← 直した不具合の一覧へ