170 lines
4.1 KiB
Go
170 lines
4.1 KiB
Go
package model
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
)
|
|
|
|
// Decimal24_2 用规范十进制文本承载 MySQL DECIMAL(24,2),避免金额经过 float64 丢精度。
|
|
// JSON 输出字符串以保护 22 位整数精度;数据库读写通过 Scanner/Valuer 完成。
|
|
type Decimal24_2 string
|
|
|
|
func ParseDecimal24_2(value string) (Decimal24_2, error) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return "", fmt.Errorf("decimal is blank")
|
|
}
|
|
negative := false
|
|
if value[0] == '-' {
|
|
negative, value = true, value[1:]
|
|
} else if value[0] == '+' {
|
|
return "", fmt.Errorf("decimal plus sign is not supported")
|
|
}
|
|
if value == "" || strings.ContainsAny(value, "eE") {
|
|
return "", fmt.Errorf("invalid decimal")
|
|
}
|
|
parts := strings.Split(value, ".")
|
|
if len(parts) > 2 || parts[0] == "" {
|
|
return "", fmt.Errorf("invalid decimal")
|
|
}
|
|
for _, char := range parts[0] {
|
|
if char < '0' || char > '9' {
|
|
return "", fmt.Errorf("invalid decimal")
|
|
}
|
|
}
|
|
integer := strings.TrimLeft(parts[0], "0")
|
|
if integer == "" {
|
|
integer = "0"
|
|
}
|
|
fraction := ""
|
|
if len(parts) == 2 {
|
|
fraction = parts[1]
|
|
for _, char := range fraction {
|
|
if char < '0' || char > '9' {
|
|
return "", fmt.Errorf("invalid decimal")
|
|
}
|
|
}
|
|
// BigDecimal.stripTrailingZeros 后 scale<=2 的值均可接受,例如 1.2300。
|
|
if len(fraction) > 2 && strings.Trim(fraction[2:], "0") != "" {
|
|
return "", fmt.Errorf("decimal scale exceeds 2")
|
|
}
|
|
}
|
|
if len(integer) > 22 {
|
|
return "", fmt.Errorf("decimal integer digits exceed 22")
|
|
}
|
|
if len(fraction) > 2 {
|
|
fraction = fraction[:2]
|
|
}
|
|
fraction += strings.Repeat("0", 2-len(fraction))
|
|
if integer == "0" && fraction == "00" {
|
|
negative = false
|
|
}
|
|
prefix := ""
|
|
if negative {
|
|
prefix = "-"
|
|
}
|
|
return Decimal24_2(prefix + integer + "." + fraction), nil
|
|
}
|
|
|
|
func (d *Decimal24_2) UnmarshalJSON(data []byte) error {
|
|
raw := strings.TrimSpace(string(data))
|
|
if raw == "" || raw == "null" || raw == `""` {
|
|
*d = DecimalZero()
|
|
return nil
|
|
}
|
|
if strings.HasPrefix(raw, `"`) && strings.HasSuffix(raw, `"`) {
|
|
var value string
|
|
if err := json.Unmarshal(data, &value); err != nil {
|
|
return err
|
|
}
|
|
raw = value
|
|
}
|
|
parsed, err := ParseDecimal24_2(raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*d = parsed
|
|
return nil
|
|
}
|
|
|
|
func (d Decimal24_2) MarshalJSON() ([]byte, error) { return json.Marshal(d.String()) }
|
|
|
|
func (d *Decimal24_2) Scan(value any) error {
|
|
var raw string
|
|
switch typed := value.(type) {
|
|
case nil:
|
|
raw = "0"
|
|
case []byte:
|
|
raw = string(typed)
|
|
case string:
|
|
raw = typed
|
|
case int64:
|
|
raw = fmt.Sprintf("%d", typed)
|
|
default:
|
|
raw = fmt.Sprint(typed)
|
|
}
|
|
parsed, err := ParseDecimal24_2(raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*d = parsed
|
|
return nil
|
|
}
|
|
|
|
func (d Decimal24_2) Value() (driver.Value, error) { return d.String(), nil }
|
|
|
|
func (d Decimal24_2) String() string {
|
|
if strings.TrimSpace(string(d)) == "" {
|
|
return "0.00"
|
|
}
|
|
return string(d)
|
|
}
|
|
|
|
func DecimalZero() Decimal24_2 { return Decimal24_2("0.00") }
|
|
|
|
func (d Decimal24_2) Positive() bool { return d.cents().Sign() > 0 }
|
|
|
|
func (d Decimal24_2) Compare(other Decimal24_2) int { return d.cents().Cmp(other.cents()) }
|
|
|
|
func (d Decimal24_2) MultiplyInt(multiplier int64) (Decimal24_2, error) {
|
|
if multiplier < 0 {
|
|
return "", fmt.Errorf("decimal multiplier must not be negative")
|
|
}
|
|
value := new(big.Int).Mul(d.cents(), big.NewInt(multiplier))
|
|
if len(new(big.Int).Abs(new(big.Int).Set(value)).String()) > 24 {
|
|
return "", fmt.Errorf("decimal exceeds DECIMAL(24,2)")
|
|
}
|
|
return decimalFromCents(value), nil
|
|
}
|
|
|
|
func (d Decimal24_2) cents() *big.Int {
|
|
raw := d.String()
|
|
negative := strings.HasPrefix(raw, "-")
|
|
raw = strings.TrimPrefix(raw, "-")
|
|
digits := strings.ReplaceAll(raw, ".", "")
|
|
value, ok := new(big.Int).SetString(digits, 10)
|
|
if !ok {
|
|
return new(big.Int)
|
|
}
|
|
if negative {
|
|
value.Neg(value)
|
|
}
|
|
return value
|
|
}
|
|
|
|
func decimalFromCents(value *big.Int) Decimal24_2 {
|
|
negative := value.Sign() < 0
|
|
abs := new(big.Int).Abs(new(big.Int).Set(value)).String()
|
|
if len(abs) < 3 {
|
|
abs = strings.Repeat("0", 3-len(abs)) + abs
|
|
}
|
|
result := abs[:len(abs)-2] + "." + abs[len(abs)-2:]
|
|
if negative && result != "0.00" {
|
|
result = "-" + result
|
|
}
|
|
return Decimal24_2(result)
|
|
}
|