fix(v2): money 去 float 进制 + 溢出防护 + 补 Format/负数/溢出测试
- money.go: 用纯 int64 循环乘法替代 int64(math.Pow10(exp)) 计算进制因子 - Parse: whole*mult+frac 前加溢出检查(math.MaxInt64 仅作整型常量参与比较,无 float 参与) - money_test.go: 补 Format 未知币种错误分支(errors.Is ErrUnknownCurrency)、 Parse 溢出(整数部分过大)、负数往返测试
This commit is contained in:
+16
-2
@@ -24,6 +24,16 @@ func Exponent(currency string) (int, bool) {
|
|||||||
return e, ok
|
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.
|
// Format renders minor units as a decimal string, trailing zeros trimmed.
|
||||||
func Format(minor int64, currency string) (string, error) {
|
func Format(minor int64, currency string) (string, error) {
|
||||||
exp, ok := Exponent(currency)
|
exp, ok := Exponent(currency)
|
||||||
@@ -37,7 +47,7 @@ func Format(minor int64, currency string) (string, error) {
|
|||||||
if neg {
|
if neg {
|
||||||
minor = -minor
|
minor = -minor
|
||||||
}
|
}
|
||||||
div := int64(math.Pow10(exp))
|
div := pow10Int64(exp)
|
||||||
whole := minor / div
|
whole := minor / div
|
||||||
frac := minor % div
|
frac := minor % div
|
||||||
s := fmt.Sprintf("%d.%0*d", whole, exp, frac)
|
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)
|
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 {
|
if neg {
|
||||||
minor = -minor
|
minor = -minor
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package money_test
|
package money_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/wangjia/pay/internal/money"
|
"github.com/wangjia/pay/internal/money"
|
||||||
@@ -45,3 +46,44 @@ func TestParseErrors(t *testing.T) {
|
|||||||
t.Fatal("非数字应报错")
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user