言語ごとの結果
正解表 13件のうち、正解と一致した件数です。
PHPPHP 8.4.26
よくある書き方9 / 13
直した書き方13 / 13
PythonPython 3.14.7
よくある書き方8 / 13
直した書き方13 / 13
GoGo 1.27.2
よくある書き方8 / 13
直した書き方13 / 13
JavaScriptNode 24.15.0
よくある書き方8 / 13
直した書き方13 / 13
正解表と、よくある書き方の答え
正解は実装より先に決めて固定しています。言語の列の ✓ は正解と一致、赤い値はその言語が実際に返した間違った答えです。
| ケース | 入力 | 正解 | PHP | Python | Go | JavaScript |
|---|---|---|---|---|---|---|
| c001基本・1万円の1% |
|
|
✓ | ✓ | ✓ | ✓ |
| c0020.7%還元・ちょうど整数 1000 × 0.7% = 7pt ちょうど。浮動小数点では 6.999… になり切り捨てで 6pt |
|
|
points6net994effective_rate"0.6" | points6net994effective_rate"0.6" | points6net994effective_rate"0.6" | points6net994effective_rate"0.6" |
| c0031.4%還元・ちょうど整数 7pt ちょうど。浮動小数点では 6pt |
|
|
points6net494effective_rate"1.2" | points6net494effective_rate"1.2" | points6net494effective_rate"1.2" | points6net494effective_rate"1.2" |
| c0045.8%還元・ちょうど整数 58pt ちょうど。浮動小数点では 57pt |
|
|
points57net943effective_rate"5.7" | points57net943effective_rate"5.7" | points57net943effective_rate"5.7" | points57net943effective_rate"5.7" |
| c0055.6%還元・ちょうど整数 7pt ちょうど。浮動小数点では 6pt |
|
|
points6net119effective_rate"4.8" | points6net119effective_rate"4.8" | points6net119effective_rate"4.8" | points6net119effective_rate"4.8" |
| c006端数の切り捨て 9.99 → 9pt。実質割引率 0.9009…% → 0.9 |
|
|
✓ | ✓ | ✓ | ✓ |
| c007実質割引率が .x5 ちょうど 1.2 → 1pt。1/400 = 0.25% → 0.3。偶数丸めだと 0.2 |
|
|
✓ | effective_rate"0.2" | effective_rate"0.2" | ✓ |
| c008実質割引率 0.5% |
|
|
✓ | ✓ | ✓ | ✓ |
| c009支払金額0円 境界値。0 で割らない |
|
|
✓ | ✓ | ✓ | ✓ |
| c010還元率0% 境界値 |
|
|
✓ | ✓ | ✓ | ✓ |
| c011還元率100% 境界値 |
|
|
✓ | ✓ | ✓ | ✓ |
| c0122^53 を超える金額 倍精度浮動小数点で正確に表せない整数(2^53+1) |
|
|
✓ | ✓ | ✓ | net8917127262193583 |
| c013小数2桁の還元率 12.5 → 12pt。実質割引率 1.2 |
|
|
✓ | ✓ | ✓ | ✓ |
コード
「違い」では、よくある書き方から直した書き方へ、どの行をどう変えたかを示しています(赤が消した行、緑が足した行、濃い色が行の中で変わった部分)。計算の部分だけを載せ、入出力を受け渡す部分は除いています。
Reward.php+8 −4よくある書き方(f3752ff)→ 直した書き方
| @@ 変更前 1行目〜 / 変更後 1行目〜 @@ | |||
| 1 | 1 | <?php | |
| 2 | 2 | declare(strict_types=1); | |
| 3 | 3 | ||
| 4 | + | use BcMath\Number; | |
| 5 | + | ||
| 4 | 6 | /** | |
| 5 | 7 | * ポイント還元と実質割引率。 | |
| 6 | − | * v1: 素直に書いた版(float と floor、表示は number_format)。 | |
| 8 | + | * v2: 十進(BcMath\Number)で計算し、切り捨て・四捨五入は RoundingMode で明示する。 | |
| 7 | 9 | * @return array{points:int, net:int, effective_rate:string} | |
| 8 | 10 | */ | |
| 9 | 11 | function calcReward(int $amount, string $rate): array | |
| 10 | 12 | { | |
| 11 | − | $points = (int) floor($amount * ((float) $rate / 100)); | |
| 12 | − | $eff = $amount > 0 ? $points / $amount * 100 : 0.0; | |
| 13 | − | return ['points' => $points, 'net' => $amount - $points, 'effective_rate' => number_format($eff, 1, '.', '')]; | |
| 13 | + | $points = (int) (new Number($amount) * new Number($rate) / 100)->round(0, RoundingMode::TowardsZero)->value; | |
| 14 | + | $eff = $amount > 0 | |
| 15 | + | ? (new Number($points) * 100)->div($amount, 10)->round(1, RoundingMode::HalfAwayFromZero)->value | |
| 16 | + | : '0.0'; | |
| 17 | + | return ['points' => $points, 'net' => $amount - $points, 'effective_rate' => $eff]; | |
| 14 | 18 | } |
Reward.php
<?php
declare(strict_types=1);
use BcMath\Number;
/**
* ポイント還元と実質割引率。
* v2: 十進(BcMath\Number)で計算し、切り捨て・四捨五入は RoundingMode で明示する。
* @return array{points:int, net:int, effective_rate:string}
*/
function calcReward(int $amount, string $rate): array
{
$points = (int) (new Number($amount) * new Number($rate) / 100)->round(0, RoundingMode::TowardsZero)->value;
$eff = $amount > 0
? (new Number($points) * 100)->div($amount, 10)->round(1, RoundingMode::HalfAwayFromZero)->value
: '0.0';
return ['points' => $points, 'net' => $amount - $points, 'effective_rate' => $eff];
}
Reward.phpコミット f3752ff
<?php
declare(strict_types=1);
/**
* ポイント還元と実質割引率。
* v1: 素直に書いた版(float と floor、表示は number_format)。
* @return array{points:int, net:int, effective_rate:string}
*/
function calcReward(int $amount, string $rate): array
{
$points = (int) floor($amount * ((float) $rate / 100));
$eff = $amount > 0 ? $points / $amount * 100 : 0.0;
return ['points' => $points, 'net' => $amount - $points, 'effective_rate' => number_format($eff, 1, '.', '')];
}
reward.py+10 −5よくある書き方(f3752ff)→ 直した書き方
| @@ 変更前 1行目〜 / 変更後 1行目〜 @@ | |||
| 1 | 1 | """ポイント還元と実質割引率。 | |
| 2 | 2 | ||
| 3 | − | v1: 素直に書いた版(float と math.floor、表示は f"{x:.1f}")。 | |
| 3 | + | v2: decimal で計算し、切り捨て・四捨五入は丸めモードで明示する | |
| 4 | + | (float だと 1000 × 0.7% が 6.999… になり、f"{x:.1f}" は偶数丸め)。 | |
| 4 | 5 | """ | |
| 5 | − | import math | |
| 6 | + | from decimal import ROUND_DOWN, ROUND_HALF_UP, Decimal, localcontext | |
| 6 | 7 | ||
| 7 | 8 | ||
| 8 | 9 | def calc_reward(amount: int, rate: str) -> dict: | |
| 9 | − | points = math.floor(amount * (float(rate) / 100)) | |
| 10 | − | eff = points / amount * 100 if amount > 0 else 0.0 | |
| 11 | − | return {"points": points, "net": amount - points, "effective_rate": f"{eff:.1f}"} | |
| 10 | + | points = int((amount * Decimal(rate) / 100).quantize(Decimal(1), rounding=ROUND_DOWN)) | |
| 11 | + | if amount == 0: | |
| 12 | + | eff = Decimal("0.0") | |
| 13 | + | else: | |
| 14 | + | with localcontext(prec=50): | |
| 15 | + | eff = (Decimal(points) * 100 / amount).quantize(Decimal("0.1"), rounding=ROUND_HALF_UP) | |
| 16 | + | return {"points": points, "net": amount - points, "effective_rate": str(eff)} |
reward.py
"""ポイント還元と実質割引率。
v2: decimal で計算し、切り捨て・四捨五入は丸めモードで明示する
(float だと 1000 × 0.7% が 6.999… になり、f"{x:.1f}" は偶数丸め)。
"""
from decimal import ROUND_DOWN, ROUND_HALF_UP, Decimal, localcontext
def calc_reward(amount: int, rate: str) -> dict:
points = int((amount * Decimal(rate) / 100).quantize(Decimal(1), rounding=ROUND_DOWN))
if amount == 0:
eff = Decimal("0.0")
else:
with localcontext(prec=50):
eff = (Decimal(points) * 100 / amount).quantize(Decimal("0.1"), rounding=ROUND_HALF_UP)
return {"points": points, "net": amount - points, "effective_rate": str(eff)}
reward.pyコミット f3752ff
"""ポイント還元と実質割引率。
v1: 素直に書いた版(float と math.floor、表示は f"{x:.1f}")。
"""
import math
def calc_reward(amount: int, rate: str) -> dict:
points = math.floor(amount * (float(rate) / 100))
eff = points / amount * 100 if amount > 0 else 0.0
return {"points": points, "net": amount - points, "effective_rate": f"{eff:.1f}"}
reward.go+22 −10よくある書き方(f3752ff)→ 直した書き方
| @@ 変更前 1行目〜 / 変更後 1行目〜 @@ | |||
| 1 | 1 | package main | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | − | "math" | |
| 5 | − | "strconv" | |
| 4 | + | "fmt" | |
| 5 | + | "math/big" | |
| 6 | 6 | ) | |
| 7 | 7 | ||
| 8 | 8 | // Output は獲得ポイント・実質負担額・実質割引率。 | |
| @@ 変更前 13行目〜 / 変更後 13行目〜 @@ | |||
| 13 | 13 | } | |
| 14 | 14 | ||
| 15 | 15 | // CalcReward はポイント還元を計算する。 | |
| 16 | − | // v1: 素直に書いた版(float64 と math.Floor、表示は strconv.FormatFloat)。 | |
| 16 | + | // v2: math/big.Rat で正確に計算し、切り捨て・四捨五入は自前で行う | |
| 17 | + | // (float64 だと 1000 × 0.7% が 6.999… になり、FormatFloat は偶数丸め)。 | |
| 17 | 18 | func CalcReward(amount int64, rate string) (Output, error) { | |
| 18 | − | r, err := strconv.ParseFloat(rate, 64) | |
| 19 | − | if err != nil { | |
| 20 | − | return Output{}, err | |
| 19 | + | r, ok := new(big.Rat).SetString(rate) | |
| 20 | + | if !ok { | |
| 21 | + | return Output{}, fmt.Errorf("invalid decimal: %q", rate) | |
| 21 | 22 | } | |
| 22 | − | points := int64(math.Floor(float64(amount) * (r / 100))) | |
| 23 | − | eff := 0.0 | |
| 23 | + | v := new(big.Rat).Mul(new(big.Rat).SetInt64(amount), r) | |
| 24 | + | v.Quo(v, big.NewRat(100, 1)) | |
| 25 | + | p := new(big.Int).Quo(v.Num(), v.Denom()) // 0 以上なので Quo は切り捨て | |
| 26 | + | if !p.IsInt64() { | |
| 27 | + | return Output{}, fmt.Errorf("out of int64 range: %s", p) | |
| 28 | + | } | |
| 29 | + | points := p.Int64() | |
| 30 | + | eff := "0.0" | |
| 24 | 31 | if amount > 0 { | |
| 25 | − | eff = float64(points) / float64(amount) * 100 | |
| 32 | + | // points/amount×100 の10倍を四捨五入: floor((2·points·1000 + amount) / (2·amount)) | |
| 33 | + | num := new(big.Int).Mul(big.NewInt(points), big.NewInt(2000)) | |
| 34 | + | num.Add(num, big.NewInt(amount)) | |
| 35 | + | tenths := num.Quo(num, new(big.Int).Mul(big.NewInt(amount), big.NewInt(2))) | |
| 36 | + | q, m := new(big.Int).QuoRem(tenths, big.NewInt(10), new(big.Int)) | |
| 37 | + | eff = fmt.Sprintf("%s.%s", q, m) | |
| 26 | 38 | } | |
| 27 | − | return Output{points, amount - points, strconv.FormatFloat(eff, 'f', 1, 64)}, nil | |
| 39 | + | return Output{points, amount - points, eff}, nil | |
| 28 | 40 | } |
reward.go
package main
import (
"fmt"
"math/big"
)
// Output は獲得ポイント・実質負担額・実質割引率。
type Output struct {
Points int64 `json:"points"`
Net int64 `json:"net"`
EffectiveRate string `json:"effective_rate"`
}
// CalcReward はポイント還元を計算する。
// v2: math/big.Rat で正確に計算し、切り捨て・四捨五入は自前で行う
// (float64 だと 1000 × 0.7% が 6.999… になり、FormatFloat は偶数丸め)。
func CalcReward(amount int64, rate string) (Output, error) {
r, ok := new(big.Rat).SetString(rate)
if !ok {
return Output{}, fmt.Errorf("invalid decimal: %q", rate)
}
v := new(big.Rat).Mul(new(big.Rat).SetInt64(amount), r)
v.Quo(v, big.NewRat(100, 1))
p := new(big.Int).Quo(v.Num(), v.Denom()) // 0 以上なので Quo は切り捨て
if !p.IsInt64() {
return Output{}, fmt.Errorf("out of int64 range: %s", p)
}
points := p.Int64()
eff := "0.0"
if amount > 0 {
// points/amount×100 の10倍を四捨五入: floor((2·points·1000 + amount) / (2·amount))
num := new(big.Int).Mul(big.NewInt(points), big.NewInt(2000))
num.Add(num, big.NewInt(amount))
tenths := num.Quo(num, new(big.Int).Mul(big.NewInt(amount), big.NewInt(2)))
q, m := new(big.Int).QuoRem(tenths, big.NewInt(10), new(big.Int))
eff = fmt.Sprintf("%s.%s", q, m)
}
return Output{points, amount - points, eff}, nil
}
reward.goコミット f3752ff
package main
import (
"math"
"strconv"
)
// Output は獲得ポイント・実質負担額・実質割引率。
type Output struct {
Points int64 `json:"points"`
Net int64 `json:"net"`
EffectiveRate string `json:"effective_rate"`
}
// CalcReward はポイント還元を計算する。
// v1: 素直に書いた版(float64 と math.Floor、表示は strconv.FormatFloat)。
func CalcReward(amount int64, rate string) (Output, error) {
r, err := strconv.ParseFloat(rate, 64)
if err != nil {
return Output{}, err
}
points := int64(math.Floor(float64(amount) * (r / 100)))
eff := 0.0
if amount > 0 {
eff = float64(points) / float64(amount) * 100
}
return Output{points, amount - points, strconv.FormatFloat(eff, 'f', 1, 64)}, nil
}
reward.mjs+18 −4よくある書き方(f3752ff)→ 直した書き方
| @@ 変更前 1行目〜 / 変更後 1行目〜 @@ | |||
| 1 | 1 | // ポイント還元と実質割引率。 | |
| 2 | − | // v1: 素直に書いた版(Number と Math.floor、表示は toFixed(1))。 | |
| 2 | + | // v2: BigInt の分数で正確に計算し、切り捨て・四捨五入も自前で行う | |
| 3 | + | // (Number だと 1000 × 0.7% が 6.999… になる)。amount は BigInt で受け取り、BigInt で返す。 | |
| 4 | + | ||
| 5 | + | /** "1.25" → { n: 125n, d: 100n } */ | |
| 6 | + | function parseDecimal(s) { | |
| 7 | + | const m = /^(\d+)(?:\.(\d+))?$/.exec(s); | |
| 8 | + | if (!m) throw new RangeError(`invalid decimal: ${s}`); | |
| 9 | + | const frac = m[2] ?? ""; | |
| 10 | + | return { n: BigInt(m[1] + frac), d: 10n ** BigInt(frac.length) }; | |
| 11 | + | } | |
| 3 | 12 | ||
| 4 | 13 | export function calcReward({ amount, rate }) { | |
| 5 | − | const points = Math.floor(amount * (Number(rate) / 100)); | |
| 6 | − | const eff = amount > 0 ? (points / amount) * 100 : 0; | |
| 7 | − | return { points, net: amount - points, effective_rate: eff.toFixed(1) }; | |
| 14 | + | const r = parseDecimal(rate); | |
| 15 | + | const points = (amount * r.n) / (100n * r.d); // 0 以上なので BigInt の / は切り捨て | |
| 16 | + | let eff = "0.0"; | |
| 17 | + | if (amount > 0n) { | |
| 18 | + | const tenths = (2n * points * 1000n + amount) / (2n * amount); // points/amount×100 の10倍を四捨五入 | |
| 19 | + | eff = `${tenths / 10n}.${tenths % 10n}`; | |
| 20 | + | } | |
| 21 | + | return { points, net: amount - points, effective_rate: eff }; | |
| 8 | 22 | } |
reward.mjs
// ポイント還元と実質割引率。
// v2: BigInt の分数で正確に計算し、切り捨て・四捨五入も自前で行う
// (Number だと 1000 × 0.7% が 6.999… になる)。amount は BigInt で受け取り、BigInt で返す。
/** "1.25" → { n: 125n, d: 100n } */
function parseDecimal(s) {
const m = /^(\d+)(?:\.(\d+))?$/.exec(s);
if (!m) throw new RangeError(`invalid decimal: ${s}`);
const frac = m[2] ?? "";
return { n: BigInt(m[1] + frac), d: 10n ** BigInt(frac.length) };
}
export function calcReward({ amount, rate }) {
const r = parseDecimal(rate);
const points = (amount * r.n) / (100n * r.d); // 0 以上なので BigInt の / は切り捨て
let eff = "0.0";
if (amount > 0n) {
const tenths = (2n * points * 1000n + amount) / (2n * amount); // points/amount×100 の10倍を四捨五入
eff = `${tenths / 10n}.${tenths % 10n}`;
}
return { points, net: amount - points, effective_rate: eff };
}
reward.mjsコミット f3752ff
// ポイント還元と実質割引率。
// v1: 素直に書いた版(Number と Math.floor、表示は toFixed(1))。
export function calcReward({ amount, rate }) {
const points = Math.floor(amount * (Number(rate) / 100));
const eff = amount > 0 ? (points / amount) * 100 : 0;
return { points, net: amount - points, effective_rate: eff.toFixed(1) };
}
仕様
入力
| 項目 | 型 | 説明 |
|---|---|---|
amount | 整数(円) | 支払金額。0 以上 |
rate | 十進文字列 | 還元率(%)。0 以上 100 以下。"0.7"・"1.25" のような小数を含む |
出力
| 項目 | 型 | 定義 |
|---|---|---|
points | 整数(pt) | 切り捨て(amount × rate / 100) |
net | 整数(円) | amount − points(実質負担額。1pt = 1円) |
effective_rate | 十進文字列(%・小数1桁) | points / amount × 100 を小数第1位に四捨五入。amount = 0 なら "0.0" |
- 計算は正確な十進(または有理数)で行う。 2進浮動小数点の誤差を結果に出さない
- 四捨五入は 0.05 ちょうどを繰り上げる(偶数丸めではない)。出力は常に小数1桁(
"1.0") - 元ツールは実質割引率を
"1%"のように末尾の 0 を省いて表示する。本 spec は小数1桁固定の数値文字列にした amountは 64bit 符号付き整数に収まる範囲(c012は 2^53 を超える値)
期待値の作り方
expect は oracle.py で有理数による定義の直訳から生成した(2026-10-10)。実装ではない。runner は使わない。