decimal_test.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. // Package money 十进制边界用例(极端值/超长小数/符号边界)。
  2. //
  3. // 与 amount_test.go 的分工:amount_test.go 覆盖"功能正确",
  4. // 本文件专门覆盖"精度与量级的边界",即旧库 float(10,3) → decimal(12,2) 收敛(设计 §11-7)
  5. // 最容易出问题的位置:第 3 位小数的进位、9999999999.99 边界、负值方向舍入。
  6. package money
  7. import (
  8. stderrors "errors"
  9. "testing"
  10. )
  11. // TestDecimalBoundary 极端金额的解析与回写一致性。
  12. func TestDecimalBoundary(t *testing.T) {
  13. cases := []struct {
  14. name string
  15. in string
  16. want Amount
  17. }{
  18. // decimal(12,2) 边界的 1 分钱精度
  19. {"上限减一分", "9999999999.98", MaxAmount - 1},
  20. {"下限加一分", "-9999999999.98", MinAmount + 1},
  21. // 第 3 位小数:恰好 5 进位(half-up,远离零)
  22. {"边界进位", "0.005", 1},
  23. {"边界不进位", "0.0049", 0},
  24. {"负边界进位", "-0.005", -1},
  25. // 超长小数(20 位):只看第 3 位,尾部不影响
  26. {"超长小数舍", "0.00499999999999999999", 0},
  27. {"超长小数进", "0.00500000000000000001", 1},
  28. // 大额 + 小数进位(整数位与小数位同时参与)
  29. {"大额进位", "9999999999.994", 999999999999},
  30. {"大额溢出", "9999999999.995", 0}, // 进位后越界 → ErrOverflow(want 占位,见下)
  31. }
  32. for _, tc := range cases {
  33. tc := tc
  34. t.Run(tc.name, func(t *testing.T) {
  35. got, err := NewFromYuanString(tc.in)
  36. if tc.name == "大额溢出" {
  37. if !stderrors.Is(err, ErrOverflow) {
  38. t.Fatalf("进位越界应返回 ErrOverflow,实得 %v(值 %d)", err, got)
  39. }
  40. return
  41. }
  42. if err != nil {
  43. t.Fatalf("NewFromYuanString(%q) 意外失败: %v", tc.in, err)
  44. }
  45. if got != tc.want {
  46. t.Fatalf("NewFromYuanString(%q) = %d,期望 %d", tc.in, got, tc.want)
  47. }
  48. // 幂等性:格式化后再解析必须回到同一值(String ↔ NewFromYuanString 互逆)
  49. again, err := NewFromYuanString(got.String())
  50. if err != nil {
  51. t.Fatalf("回写解析失败: %v", err)
  52. }
  53. if again != got {
  54. t.Fatalf("格式化回写漂移: %d → %q → %d", got, got.String(), again)
  55. }
  56. })
  57. }
  58. }
  59. // TestDecimalNegativeDirection 负数方向:减/加/比较不产生方向错误(欠费与冲正依赖此语义)。
  60. func TestDecimalNegativeDirection(t *testing.T) {
  61. neg, _ := NewFromYuanString("-0.01")
  62. zero := Amount(0)
  63. if !(neg.Cmp(zero) == -1) {
  64. t.Fatalf("负数应小于零")
  65. }
  66. if zero.Sub(neg) != Amount(1) {
  67. t.Fatalf("0 - (-0.01) 应为 0.01,实得 %s", zero.Sub(neg).String())
  68. }
  69. if got := neg.Add(neg).String(); got != "-0.02" {
  70. t.Fatalf("负数相加 = %s,期望 -0.02", got)
  71. }
  72. if got := neg.MulInt(0).String(); got != "0.00" {
  73. t.Fatalf("乘零 = %s,期望 0.00", got)
  74. }
  75. }