53 lines
1.3 KiB
Go
53 lines
1.3 KiB
Go
package amount
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"regexp"
|
|
)
|
|
|
|
// This fixture's business policy is exact decimal HALF_UP. The framework does
|
|
// not choose its precision, rounding mode or currency.
|
|
type Money string
|
|
|
|
var decimalPattern = regexp.MustCompile(`^-?[0-9]+(\.[0-9]+)?$`)
|
|
|
|
func (m Money) String() string {
|
|
if m == "" {
|
|
return "0.00"
|
|
}
|
|
return string(m)
|
|
}
|
|
func (m Money) MarshalJSON() ([]byte, error) { return json.Marshal(m.String()) }
|
|
func (m *Money) UnmarshalJSON(data []byte) error {
|
|
var text string
|
|
if err := json.Unmarshal(data, &text); err != nil {
|
|
return err
|
|
}
|
|
if !decimalPattern.MatchString(text) {
|
|
return errors.New("invalid decimal")
|
|
}
|
|
value, ok := new(big.Rat).SetString(text)
|
|
if !ok {
|
|
return errors.New("invalid decimal")
|
|
}
|
|
value.Mul(value, big.NewRat(100, 1))
|
|
sign := value.Sign()
|
|
value.Abs(value)
|
|
cents, rem := new(big.Int), new(big.Int)
|
|
cents.QuoRem(value.Num(), value.Denom(), rem)
|
|
if rem.Mul(rem, big.NewInt(2)).Cmp(value.Denom()) >= 0 {
|
|
cents.Add(cents, big.NewInt(1))
|
|
}
|
|
whole, fraction := new(big.Int), new(big.Int)
|
|
whole.QuoRem(cents, big.NewInt(100), fraction)
|
|
prefix := ""
|
|
if sign < 0 && cents.Sign() != 0 {
|
|
prefix = "-"
|
|
}
|
|
*m = Money(fmt.Sprintf("%s%s.%02d", prefix, whole.String(), fraction.Int64()))
|
|
return nil
|
|
}
|