// Package money 十进制边界用例(极端值/超长小数/符号边界)。 // // 与 amount_test.go 的分工:amount_test.go 覆盖"功能正确", // 本文件专门覆盖"精度与量级的边界",即旧库 float(10,3) → decimal(12,2) 收敛(设计 §11-7) // 最容易出问题的位置:第 3 位小数的进位、9999999999.99 边界、负值方向舍入。 package money import ( stderrors "errors" "testing" ) // TestDecimalBoundary 极端金额的解析与回写一致性。 func TestDecimalBoundary(t *testing.T) { cases := []struct { name string in string want Amount }{ // decimal(12,2) 边界的 1 分钱精度 {"上限减一分", "9999999999.98", MaxAmount - 1}, {"下限加一分", "-9999999999.98", MinAmount + 1}, // 第 3 位小数:恰好 5 进位(half-up,远离零) {"边界进位", "0.005", 1}, {"边界不进位", "0.0049", 0}, {"负边界进位", "-0.005", -1}, // 超长小数(20 位):只看第 3 位,尾部不影响 {"超长小数舍", "0.00499999999999999999", 0}, {"超长小数进", "0.00500000000000000001", 1}, // 大额 + 小数进位(整数位与小数位同时参与) {"大额进位", "9999999999.994", 999999999999}, {"大额溢出", "9999999999.995", 0}, // 进位后越界 → ErrOverflow(want 占位,见下) } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { got, err := NewFromYuanString(tc.in) if tc.name == "大额溢出" { if !stderrors.Is(err, ErrOverflow) { t.Fatalf("进位越界应返回 ErrOverflow,实得 %v(值 %d)", err, got) } return } if err != nil { t.Fatalf("NewFromYuanString(%q) 意外失败: %v", tc.in, err) } if got != tc.want { t.Fatalf("NewFromYuanString(%q) = %d,期望 %d", tc.in, got, tc.want) } // 幂等性:格式化后再解析必须回到同一值(String ↔ NewFromYuanString 互逆) again, err := NewFromYuanString(got.String()) if err != nil { t.Fatalf("回写解析失败: %v", err) } if again != got { t.Fatalf("格式化回写漂移: %d → %q → %d", got, got.String(), again) } }) } } // TestDecimalNegativeDirection 负数方向:减/加/比较不产生方向错误(欠费与冲正依赖此语义)。 func TestDecimalNegativeDirection(t *testing.T) { neg, _ := NewFromYuanString("-0.01") zero := Amount(0) if !(neg.Cmp(zero) == -1) { t.Fatalf("负数应小于零") } if zero.Sub(neg) != Amount(1) { t.Fatalf("0 - (-0.01) 应为 0.01,实得 %s", zero.Sub(neg).String()) } if got := neg.Add(neg).String(); got != "-0.02" { t.Fatalf("负数相加 = %s,期望 -0.02", got) } if got := neg.MulInt(0).String(); got != "0.00" { t.Fatalf("乘零 = %s,期望 0.00", got) } }