diff --git a/backend/pkg/money/format.go b/backend/pkg/money/format.go index ee223a4..e7c7dab 100644 --- a/backend/pkg/money/format.go +++ b/backend/pkg/money/format.go @@ -11,7 +11,35 @@ func Round(value float64) float64 { return math.Round(value*10) / 10 } -// Min 返回两个金额中较小的值(角精度) +// ToCent 将角转换为分(整数),用于存储 +// 12.3角 -> 123分 +// 12.34角 -> 123分(自动舍入到角) +func ToCent(jiao float64) int64 { + return int64(math.Round(jiao * 10)) +} + +// ToJiao 将分转换为角(0.1元精度),用于API响应 +// 123分 -> 12.3角 +// 1234分 -> 123.4角 +func ToJiao(cent int64) float64 { + return float64(cent) / 10.0 +} + +// Format 格式化分为字符串(角精度,保留1位小数) +// 123分 -> "12.3" +// 1230分 -> "123.0" +func Format(cent int64) string { + jiao := ToJiao(cent) + return fmt.Sprintf("%.1f", jiao) +} + +// FormatWithSymbol 格式化分为带符号的字符串 +// 123分 -> "¥12.3" +func FormatWithSymbol(cent int64) string { + return "¥" + Format(cent) +} + +// Min 返回两个金额中较小的值(角精度,兼容旧代码) func Min(a, b float64) float64 { if a < b { return Round(a) @@ -19,7 +47,7 @@ func Min(a, b float64) float64 { return Round(b) } -// Max 返回两个金额中较大的值(角精度) +// Max 返回两个金额中较大的值(角精度,兼容旧代码) func Max(a, b float64) float64 { if a > b { return Round(a) @@ -27,15 +55,18 @@ func Max(a, b float64) float64 { return Round(b) } -// Format 格式化金额为字符串(保留1位小数) -// 例如:12.3 -> "12.3", 12.0 -> "12.0" -func Format(value float64) string { - rounded := Round(value) - return fmt.Sprintf("%.1f", rounded) +// MinCent 返回两个分值中较小的值 +func MinCent(a, b int64) int64 { + if a < b { + return a + } + return b } -// FormatWithSymbol 格式化金额并添加货币符号 -// 例如:12.3 -> "¥12.3" -func FormatWithSymbol(value float64) string { - return "¥" + Format(value) +// MaxCent 返回两个分值中较大的值 +func MaxCent(a, b int64) int64 { + if a > b { + return a + } + return b } diff --git a/backend/pkg/money/format_test.go b/backend/pkg/money/format_test.go new file mode 100644 index 0000000..9a8e74c --- /dev/null +++ b/backend/pkg/money/format_test.go @@ -0,0 +1,160 @@ +package money + +import "testing" + +func TestToCent(t *testing.T) { + tests := []struct { + name string + jiao float64 + want int64 + }{ + {"12.3角转123分", 12.3, 123}, + {"12.34角舍入到123分", 12.34, 123}, + {"12.36角舍入到124分", 12.36, 124}, + {"0.1角转1分", 0.1, 1}, + {"0.05角舍入到1分", 0.05, 1}, + {"0.04角舍入到0分", 0.04, 0}, + {"100角转1000分", 100.0, 1000}, + {"零值", 0.0, 0}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ToCent(tt.jiao) + if got != tt.want { + t.Errorf("ToCent(%v) = %v, want %v", tt.jiao, got, tt.want) + } + }) + } +} + +func TestToJiao(t *testing.T) { + tests := []struct { + name string + cent int64 + want float64 + }{ + {"123分转12.3角", 123, 12.3}, + {"1234分转123.4角", 1234, 123.4}, + {"1分转0.1角", 1, 0.1}, + {"10分转1.0角", 10, 1.0}, + {"1000分转100.0角", 1000, 100.0}, + {"零值", 0, 0.0}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ToJiao(tt.cent) + if got != tt.want { + t.Errorf("ToJiao(%v) = %v, want %v", tt.cent, got, tt.want) + } + }) + } +} + +func TestFormat(t *testing.T) { + tests := []struct { + name string + cent int64 + want string + }{ + {"123分格式化", 123, "12.3"}, + {"1234分格式化", 1234, "123.4"}, + {"10分格式化", 10, "1.0"}, + {"1分格式化", 1, "0.1"}, + {"1000分格式化", 1000, "100.0"}, + {"零值格式化", 0, "0.0"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Format(tt.cent) + if got != tt.want { + t.Errorf("Format(%v) = %v, want %v", tt.cent, got, tt.want) + } + }) + } +} + +func TestFormatWithSymbol(t *testing.T) { + tests := []struct { + name string + cent int64 + want string + }{ + {"123分带符号", 123, "¥12.3"}, + {"1000分带符号", 1000, "¥100.0"}, + {"零值带符号", 0, "¥0.0"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := FormatWithSymbol(tt.cent) + if got != tt.want { + t.Errorf("FormatWithSymbol(%v) = %v, want %v", tt.cent, got, tt.want) + } + }) + } +} + +func TestRound(t *testing.T) { + tests := []struct { + name string + value float64 + want float64 + }{ + {"12.34舍入到12.3", 12.34, 12.3}, + {"12.36舍入到12.4", 12.36, 12.4}, + {"12.35舍入到12.4", 12.35, 12.4}, + {"0.05舍入到0.1", 0.05, 0.1}, + {"0.04舍入到0.0", 0.04, 0.0}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Round(tt.value) + if got != tt.want { + t.Errorf("Round(%v) = %v, want %v", tt.value, got, tt.want) + } + }) + } +} + +func TestMinMax(t *testing.T) { + t.Run("Min", func(t *testing.T) { + if got := Min(100, 200); got != 100 { + t.Errorf("Min(100, 200) = %v, want 100", got) + } + if got := Min(200, 100); got != 100 { + t.Errorf("Min(200, 100) = %v, want 100", got) + } + }) + + t.Run("Max", func(t *testing.T) { + if got := Max(100, 200); got != 200 { + t.Errorf("Max(100, 200) = %v, want 200", got) + } + if got := Max(200, 100); got != 200 { + t.Errorf("Max(200, 100) = %v, want 200", got) + } + }) +} + +// 测试双向转换的精度 +func TestRoundTrip(t *testing.T) { + tests := []struct { + name string + originalJiao float64 + expectJiao float64 // 因为角精度,可能会有舍入 + }{ + {"12.3角往返", 12.3, 12.3}, + {"12.34角往返(舍入)", 12.34, 12.3}, + {"100.0角往返", 100.0, 100.0}, + {"0.1角往返", 0.1, 0.1}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cent := ToCent(tt.originalJiao) + gotJiao := ToJiao(cent) + if gotJiao != tt.expectJiao { + t.Errorf("往返转换:%v角 -> %v分 -> %v角, 期望 %v角", + tt.originalJiao, cent, gotJiao, tt.expectJiao) + } + }) + } +}