diff --git a/internal/money/money.go b/internal/money/money.go index b25a248..5071c86 100644 --- a/internal/money/money.go +++ b/internal/money/money.go @@ -24,6 +24,16 @@ func Exponent(currency string) (int, bool) { return e, ok } +// pow10Int64 computes 10^exp as a pure int64 (no float), matching the +// currency exponents above which never exceed a handful of digits. +func pow10Int64(exp int) int64 { + mult := int64(1) + for i := 0; i < exp; i++ { + mult *= 10 + } + return mult +} + // Format renders minor units as a decimal string, trailing zeros trimmed. func Format(minor int64, currency string) (string, error) { exp, ok := Exponent(currency) @@ -37,7 +47,7 @@ func Format(minor int64, currency string) (string, error) { if neg { minor = -minor } - div := int64(math.Pow10(exp)) + div := pow10Int64(exp) whole := minor / div frac := minor % div s := fmt.Sprintf("%d.%0*d", whole, exp, frac) @@ -81,7 +91,11 @@ func Parse(s, currency string) (int64, error) { return 0, fmt.Errorf("money.Parse frac %q: %w", s, err) } } - minor := whole*int64(math.Pow10(exp)) + frac + mult := pow10Int64(exp) + if mult > 0 && whole > (math.MaxInt64-frac)/mult { + return 0, fmt.Errorf("money.Parse: %q overflows int64 for %s", s, currency) + } + minor := whole*mult + frac if neg { minor = -minor } diff --git a/internal/money/money_test.go b/internal/money/money_test.go index d99044f..7997554 100644 --- a/internal/money/money_test.go +++ b/internal/money/money_test.go @@ -1,6 +1,7 @@ package money_test import ( + "errors" "testing" "github.com/wangjia/pay/internal/money" @@ -45,3 +46,44 @@ func TestParseErrors(t *testing.T) { t.Fatal("非数字应报错") } } + +func TestFormatUnknownCurrency(t *testing.T) { + _, err := money.Format(123, "JPYX") + if err == nil { + t.Fatal("未知币种应报错") + } + if !errors.Is(err, money.ErrUnknownCurrency) { + t.Fatalf("want ErrUnknownCurrency, got %v", err) + } +} + +func TestParseOverflow(t *testing.T) { + // whole part fits int64 on its own, but whole*mult(+frac) overflows int64. + if _, err := money.Parse("92233720368547759", "CNY"); err == nil { + t.Fatal("整数部分过大导致 minor 溢出 int64 应报错") + } + if _, err := money.Parse("92233720368547758.07", "CNY"); err != nil { + t.Fatalf("恰好等于 int64 上限不应报错: %v", err) + } +} + +func TestParseFormatNegative(t *testing.T) { + minor, err := money.Parse("-12.34", "CNY") + if err != nil { + t.Fatalf("Parse(-12.34): %v", err) + } + if minor != -1234 { + t.Fatalf("Parse(-12.34)=%d want -1234", minor) + } + back, err := money.Format(-1234, "CNY") + if err != nil { + t.Fatalf("Format(-1234): %v", err) + } + again, err := money.Parse(back, "CNY") + if err != nil { + t.Fatalf("Parse(%q): %v", back, err) + } + if again != -1234 { + t.Fatalf("round-trip -1234 → %q → %d", back, again) + } +}