//go:build dnum package fixedpoint import ( "bytes" "database/sql/driver" "errors" "fmt" "math" "math/bits" "strconv" "strings" ) type Value struct { coef uint64 sign int8 exp int } const ( signPosInf = +2 signPos = +1 signZero = 0 signNeg = -1 signNegInf = -2 coefMin = 1000_0000_0000_0000 coefMax = 9999_9999_9999_9999 digitsMax = 16 shiftMax = digitsMax - 1 ) // common values var ( Zero = Value{} One = Value{1000_0000_0000_0000, signPos, 1} NegOne = Value{1000_0000_0000_0000, signNeg, 1} PosInf = Value{1, signPosInf, 0} NegInf = Value{1, signNegInf, 0} ) var pow10f = [...]float64{ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000, 10000000000000000000, 100000000000000000000} var pow10 = [...]uint64{ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000} var halfpow10 = [...]uint64{ 0, 5, 50, 500, 5000, 50000, 500000, 5000000, 50000000, 500000000, 5000000000, 50000000000, 500000000000, 5000000000000, 50000000000000, 500000000000000, 5000000000000000, 50000000000000000, 500000000000000000, 5000000000000000000} func min(a int, b int) int { if a < b { return a } return b } func max(a int, b int) int { if a > b { return a } return b } func (v Value) Value() (driver.Value, error) { return v.Float64(), nil } // NewFromInt returns a Value for an int func NewFromInt(n int64) Value { if n == 0 { return Zero } //n0 := n sign := int8(signPos) if n < 0 { n = -n sign = signNeg } return newNoSignCheck(sign, uint64(n), digitsMax) } const log2of10 = 3.32192809488736234 // NewFromFloat converts a float64 to a Value func NewFromFloat(f float64) Value { switch { case math.IsInf(f, +1): return PosInf case math.IsInf(f, -1): return NegInf case math.IsNaN(f): panic("value.NewFromFloat can't convert NaN") } if f == 0 { return Zero } sign := int8(signPos) if f < 0 { f = -f sign = signNeg } n := uint64(f) if float64(n) == f { return newNoSignCheck(sign, n, digitsMax) } _, e := math.Frexp(f) e = int(float32(e) / log2of10) c := uint64(f/math.Pow10(e-16) + 0.5) return newNoSignCheck(sign, c, e) } // Raw constructs a Value without normalizing - arguments must be valid. // Used by SuValue Unpack func Raw(sign int8, coef uint64, exp int) Value { return Value{coef, sign, int(exp)} } func newNoSignCheck(sign int8, coef uint64, exp int) Value { atmax := false for coef > coefMax { coef = (coef + 5) / 10 exp++ atmax = true } if !atmax { p := maxShift(coef) coef *= pow10[p] exp -= p } return Value{coef, sign, exp} } // New constructs a Value, maximizing coef and handling exp out of range // Used to normalize results of operations func New(sign int8, coef uint64, exp int) Value { if sign == 0 || coef == 0 { return Zero } else if sign == signPosInf { return PosInf } else if sign == signNegInf { return NegInf } else { atmax := false for coef > coefMax { coef = (coef + 5) / 10 exp++ atmax = true } if !atmax { p := maxShift(coef) coef *= pow10[p] exp -= p } return Value{coef, sign, exp} } } func maxShift(x uint64) int { i := ilog10(x) if i > shiftMax { return 0 } return shiftMax - i } func ilog10(x uint64) int { // based on Hacker's Delight if x == 0 { return 0 } y := (19 * (63 - bits.LeadingZeros64(x))) >> 6 if y < 18 && x >= pow10[y+1] { y++ } return y } func Inf(sign int8) Value { switch { case sign < 0: return NegInf case sign > 0: return PosInf default: return Zero } } func (dn Value) FormatString(prec int) string { if dn.sign == 0 { return "0" } const maxLeadingZeros = 7 sign := "" if dn.sign < 0 { sign = "-" } if dn.IsInf() { return sign + "inf" } digits := getDigits(dn.coef) nd := len(digits) e := int(dn.exp) - nd if -maxLeadingZeros <= dn.exp && dn.exp <= 0 { // decimal to the left return sign + "0." + strings.Repeat("0", -e-nd) + digits[:min(prec, nd)] + strings.Repeat("0", max(0, prec-nd+e+nd)) } else if -nd < e && e <= -1 { // decimal within dec := nd + e return sign + digits[:dec] + "." + digits[dec:min(dec+prec, nd)] + strings.Repeat("0", max(0, min(dec+prec, nd)-dec-prec)) } else if 0 < dn.exp && dn.exp <= digitsMax { // decimal to the right return sign + digits + strings.Repeat("0", e) + "." + strings.Repeat("0", prec) } else { // scientific notation after := "" if nd > 1 { after = "." + digits[1:min(1+prec, nd)] + strings.Repeat("0", min(1+prec, nd)-1-prec) } return sign + digits[:1] + after + "e" + strconv.Itoa(int(dn.exp-1)) } } // String returns a string representation of the Value func (dn Value) String() string { if dn.sign == 0 { return "0" } const maxLeadingZeros = 7 sign := "" if dn.sign < 0 { sign = "-" } if dn.IsInf() { return sign + "inf" } digits := getDigits(dn.coef) nd := len(digits) e := int(dn.exp) - nd if -maxLeadingZeros <= dn.exp && dn.exp <= 0 { // decimal to the left return sign + "0." + strings.Repeat("0", -e-nd) + digits } else if -nd < e && e <= -1 { // decimal within dec := nd + e return sign + digits[:dec] + "." + digits[dec:] } else if 0 < dn.exp && dn.exp <= digitsMax { // decimal to the right return sign + digits + strings.Repeat("0", e) } else { // scientific notation after := "" if nd > 1 { after = "." + digits[1:] } return sign + digits[:1] + after + "e" + strconv.Itoa(int(dn.exp-1)) } } func (dn Value) Percentage() string { if dn.sign == 0 { return "0%" } const maxLeadingZeros = 7 sign := "" if dn.sign < 0 { sign = "-" } if dn.IsInf() { return sign + "inf%" } digits := getDigits(dn.coef) nd := len(digits) e := int(dn.exp) - nd + 2 if -maxLeadingZeros <= dn.exp && dn.exp <= 0 { // decimal to the left return sign + "0." + strings.Repeat("0", -e-nd) + digits + "%" } else if -nd < e && e <= -1 { // decimal within dec := nd + e return sign + digits[:dec] + "." + digits[dec:] } else if 0 < dn.exp && dn.exp <= digitsMax { // decimal to the right return sign + digits + strings.Repeat("0", e) + "%" } else { // scientific notation after := "" if nd > 1 { after = "." + digits[1:] } return sign + digits[:1] + after + "e" + strconv.Itoa(int(dn.exp-1)) + "%" } } func (dn Value) FormatPercentage(prec int) string { if dn.sign == 0 { return "0" } const maxLeadingZeros = 7 sign := "" if dn.sign < 0 { sign = "-" } if dn.IsInf() { return sign + "inf" } digits := getDigits(dn.coef) nd := len(digits) exp := dn.exp + 2 e := int(exp) - nd if -maxLeadingZeros <= exp && exp <= 0 { // decimal to the left return sign + "0." + strings.Repeat("0", -e-nd) + digits[:min(prec, nd)] + strings.Repeat("0", max(0, prec-nd+e+nd)) + "%" } else if -nd < e && e <= -1 { // decimal within dec := nd + e return sign + digits[:dec] + "." + digits[dec:min(dec+prec, nd)] + strings.Repeat("0", max(0, min(dec+prec, nd)-dec-prec)) + "%" } else if 0 < exp && exp <= digitsMax { // decimal to the right return sign + digits + strings.Repeat("0", e) + "." + strings.Repeat("0", prec) + "%" } else { // scientific notation after := "" if nd > 1 { after = "." + digits[1:min(1+prec, nd)] + strings.Repeat("0", min(1+prec, nd)-1-prec) } return sign + digits[:1] + after + "e" + strconv.Itoa(int(exp-1)) + "%" } } func (dn Value) SignedPercentage() string { if dn.Sign() >= 0 { return "+" + dn.Percentage() } return dn.Percentage() } // get digit length func (a Value) NumDigits() int { i := shiftMax coef := a.coef nd := 0 for coef != 0 && coef < pow10[i] { i-- } for coef != 0 { coef %= pow10[i] i-- nd++ } return nd } // alias of Exp func (a Value) NumIntDigits() int { return a.exp } // get fractional digits func (a Value) NumFractionalDigits() int { nd := a.NumDigits() return nd - a.exp } func getDigits(coef uint64) string { var digits [digitsMax]byte i := shiftMax nd := 0 for coef != 0 { digits[nd] = byte('0' + (coef / pow10[i])) coef %= pow10[i] nd++ i-- } return string(digits[:nd]) } func (v *Value) Scan(src interface{}) error { var err error switch d := src.(type) { case int64: *v = NewFromInt(d) return nil case float64: *v = NewFromFloat(d) return nil case []byte: *v, err = NewFromString(string(d)) if err != nil { return err } return nil default: } return fmt.Errorf("fixedpoint.Value scan error, type %T is not supported, value: %+v", src, src) } // NewFromString parses a numeric string and returns a Value representation. func NewFromString(s string) (Value, error) { length := len(s) isPercentage := s[length-1] == '%' if isPercentage { s = s[:length-1] } r := &reader{s, 0} sign := r.getSign() if r.matchStr("inf") { return Inf(sign), nil } coef, exp := r.getCoef() exp += r.getExp() if r.len() != 0 { // didn't consume entire string return Zero, errors.New("invalid number") } else if coef == 0 || exp < math.MinInt8 { return Zero, nil } else if exp > math.MaxInt8 { return Inf(sign), nil } if isPercentage { exp -= 2 } atmax := false for coef > coefMax { coef = (coef + 5) / 10 exp++ atmax = true } if !atmax { p := maxShift(coef) coef *= pow10[p] exp -= p } //check(coefMin <= coef && coef <= coefMax) return Value{coef, sign, exp}, nil } func MustNewFromString(input string) Value { v, err := NewFromString(input) if err != nil { panic(fmt.Errorf("cannot parse %s into fixedpoint, error: %s", input, err.Error())) } return v } func NewFromBytes(s []byte) (Value, error) { length := len(s) isPercentage := s[length-1] == '%' if isPercentage { s = s[:length-1] } r := &readerBytes{s, 0} sign := r.getSign() if r.matchStr("inf") { return Inf(sign), nil } coef, exp := r.getCoef() exp += r.getExp() if r.len() != 0 { // didn't consume entire string return Zero, errors.New("invalid number") } else if coef == 0 || exp < math.MinInt8 { return Zero, nil } else if exp > math.MaxInt8 { return Inf(sign), nil } if isPercentage { exp -= 2 } atmax := false for coef > coefMax { coef = (coef + 5) / 10 exp++ atmax = true } if !atmax { p := maxShift(coef) coef *= pow10[p] exp -= p } //check(coefMin <= coef && coef <= coefMax) return Value{coef, sign, exp}, nil } func MustNewFromBytes(input []byte) Value { v, err := NewFromBytes(input) if err != nil { panic(fmt.Errorf("cannot parse %s into fixedpoint, error: %s", input, err.Error())) } return v } // TODO: refactor by interface type readerBytes struct { s []byte i int } func (r *readerBytes) cur() byte { if r.i >= len(r.s) { return 0 } return byte(r.s[r.i]) } func (r *readerBytes) prev() byte { if r.i == 0 { return 0 } return byte(r.s[r.i-1]) } func (r *readerBytes) len() int { return len(r.s) - r.i } func (r *readerBytes) match(c byte) bool { if r.cur() == c { r.i++ return true } return false } func (r *readerBytes) matchDigit() bool { c := r.cur() if '0' <= c && c <= '9' { r.i++ return true } return false } func (r *readerBytes) matchStr(pre string) bool { for i, c := range r.s[r.i:] { if pre[i] != c { return false } } r.i += len(pre) return true } func (r *readerBytes) getSign() int8 { if r.match('-') { return int8(signNeg) } r.match('+') return int8(signPos) } func (r *readerBytes) getCoef() (uint64, int) { digits := false beforeDecimal := true for r.match('0') { digits = true } if r.cur() == '.' && r.len() > 1 { digits = false } n := uint64(0) exp := 0 p := shiftMax for { c := r.cur() if r.matchDigit() { digits = true // ignore extra decimal places if c != '0' && p >= 0 { n += uint64(c-'0') * pow10[p] } p-- } else if beforeDecimal { // decimal point or end exp = shiftMax - p if !r.match('.') { break } beforeDecimal = false if !digits { for r.match('0') { digits = true exp-- } } } else { break } } if !digits { panic("numbers require at least one digit") } return n, exp } func (r *readerBytes) getExp() int { e := 0 if r.match('e') || r.match('E') { esign := r.getSign() for r.matchDigit() { e = e*10 + int(r.prev()-'0') } e *= int(esign) } return e } type reader struct { s string i int } func (r *reader) cur() byte { if r.i >= len(r.s) { return 0 } return byte(r.s[r.i]) } func (r *reader) prev() byte { if r.i == 0 { return 0 } return byte(r.s[r.i-1]) } func (r *reader) len() int { return len(r.s) - r.i } func (r *reader) match(c byte) bool { if r.cur() == c { r.i++ return true } return false } func (r *reader) matchDigit() bool { c := r.cur() if '0' <= c && c <= '9' { r.i++ return true } return false } func (r *reader) matchStr(pre string) bool { if strings.HasPrefix(r.s[r.i:], pre) { r.i += len(pre) return true } return false } func (r *reader) getSign() int8 { if r.match('-') { return int8(signNeg) } r.match('+') return int8(signPos) } func (r *reader) getCoef() (uint64, int) { digits := false beforeDecimal := true for r.match('0') { digits = true } if r.cur() == '.' && r.len() > 1 { digits = false } n := uint64(0) exp := 0 p := shiftMax for { c := r.cur() if r.matchDigit() { digits = true // ignore extra decimal places if c != '0' && p >= 0 { n += uint64(c-'0') * pow10[p] } p-- } else if beforeDecimal { // decimal point or end exp = shiftMax - p if !r.match('.') { break } beforeDecimal = false if !digits { for r.match('0') { digits = true exp-- } } } else { break } } if !digits { panic("numbers require at least one digit") } return n, exp } func (r *reader) getExp() int { e := 0 if r.match('e') || r.match('E') { esign := r.getSign() for r.matchDigit() { e = e*10 + int(r.prev()-'0') } e *= int(esign) } return e } // end of FromStr --------------------------------------------------- // IsInf returns true if a Value is positive or negative infinite func (dn Value) IsInf() bool { return dn.sign == signPosInf || dn.sign == signNegInf } // IsZero returns true if a Value is zero func (dn Value) IsZero() bool { return dn.sign == signZero } // Float64 converts a Value to float64 func (dn Value) Float64() float64 { if dn.IsInf() { return math.Inf(int(dn.sign)) } g := float64(dn.coef) if dn.sign == signNeg { g = -g } i := int(dn.exp) - digitsMax return g * math.Pow(10, float64(i)) } // Int64 converts a Value to an int64, returning whether it was convertible func (dn Value) Int64() int64 { if dn.sign == 0 { return 0 } if dn.sign != signNegInf && dn.sign != signPosInf { if 0 < dn.exp && dn.exp < digitsMax { return int64(dn.sign) * int64(dn.coef/pow10[digitsMax-dn.exp]) } else if dn.exp <= 0 && dn.coef != 0 { result := math.Log10(float64(dn.coef)) - float64(digitsMax) + float64(dn.exp) return int64(dn.sign) * int64(math.Pow(10, result)) } if dn.exp == digitsMax { return int64(dn.sign) * int64(dn.coef) } if dn.exp == digitsMax+1 { return int64(dn.sign) * (int64(dn.coef) * 10) } if dn.exp == digitsMax+2 { return int64(dn.sign) * (int64(dn.coef) * 100) } if dn.exp == digitsMax+3 && dn.coef < math.MaxInt64/1000 { return int64(dn.sign) * (int64(dn.coef) * 1000) } } panic("unable to convert Value to int64") } func (dn Value) Int() int { // if int is int64, this is a nop n := dn.Int64() if int64(int(n)) != n { panic("unable to convert Value to int32") } return int(n) } // Sign returns -1 for negative, 0 for zero, and +1 for positive func (dn Value) Sign() int { return int(dn.sign) } // Coef returns the coefficient func (dn Value) Coef() uint64 { return dn.coef } // Exp returns the exponent func (dn Value) Exp() int { return int(dn.exp) } // Frac returns the fractional portion, i.e. x - x.Int() func (dn Value) Frac() Value { if dn.sign == 0 || dn.sign == signNegInf || dn.sign == signPosInf || dn.exp >= digitsMax { return Zero } if dn.exp <= 0 { return dn } frac := dn.coef % pow10[digitsMax-dn.exp] if frac == dn.coef { return dn } return New(dn.sign, frac, int(dn.exp)) } type RoundingMode int const ( Up RoundingMode = iota Down HalfUp ) // Trunc returns the integer portion (truncating any fractional part) func (dn Value) Trunc() Value { return dn.integer(Down) } func (dn Value) integer(mode RoundingMode) Value { if dn.sign == 0 || dn.sign == signNegInf || dn.sign == signPosInf || dn.exp >= digitsMax { return dn } if dn.exp <= 0 { if mode == Up || (mode == HalfUp && dn.exp == 0 && dn.coef >= One.coef*5) { return New(dn.sign, One.coef, int(dn.exp)+1) } return Zero } e := digitsMax - dn.exp frac := dn.coef % pow10[e] if frac == 0 { return dn } i := dn.coef - frac if (mode == Up && frac > 0) || (mode == HalfUp && frac >= halfpow10[e]) { return New(dn.sign, i+pow10[e], int(dn.exp)) // normalize } return Value{i, dn.sign, dn.exp} } func (dn Value) Round(r int, mode RoundingMode) Value { if dn.sign == 0 || dn.sign == signNegInf || dn.sign == signPosInf || r >= digitsMax { return dn } if r <= -digitsMax { return Zero } n := New(dn.sign, dn.coef, int(dn.exp)+r) // multiply by 10^r n = n.integer(mode) if n.sign == signPos || n.sign == signNeg { // i.e. not zero or inf return New(n.sign, n.coef, int(n.exp)-r) } return n } // arithmetic operations ------------------------------------------------------- // Neg returns the Value negated i.e. sign reversed func (dn Value) Neg() Value { return Value{dn.coef, -dn.sign, dn.exp} } // Abs returns the Value with a positive sign func (dn Value) Abs() Value { if dn.sign < 0 { return Value{dn.coef, -dn.sign, dn.exp} } return dn } // Equal returns true if two Value's are equal func Equal(x, y Value) bool { return x.sign == y.sign && x.exp == y.exp && x.coef == y.coef } func (x Value) Eq(y Value) bool { return Equal(x, y) } func Max(x, y Value) Value { if Compare(x, y) > 0 { return x } return y } func Min(x, y Value) Value { if Compare(x, y) < 0 { return x } return y } // Compare compares two Value's returning -1 for <, 0 for ==, +1 for > func Compare(x, y Value) int { switch { case x.sign < y.sign: return -1 case x.sign > y.sign: return 1 case x == y: return 0 } sign := int(x.sign) switch { case sign == 0 || sign == signNegInf || sign == signPosInf: return 0 case x.exp < y.exp: return -sign case x.exp > y.exp: return +sign case x.coef < y.coef: return -sign case x.coef > y.coef: return +sign default: return 0 } } func (x Value) Compare(y Value) int { return Compare(x, y) } func (v *Value) UnmarshalYAML(unmarshal func(a interface{}) error) (err error) { var f float64 if err = unmarshal(&f); err == nil { *v = NewFromFloat(f) return } var i int64 if err = unmarshal(&i); err == nil { *v = NewFromInt(i) return } var s string if err = unmarshal(&s); err == nil { nv, err2 := NewFromString(s) if err2 == nil { *v = nv return } } return err } // FIXME: should we limit to 8 prec? func (v Value) MarshalJSON() ([]byte, error) { return []byte(v.FormatString(8)), nil } func (v *Value) UnmarshalJSON(data []byte) error { // FIXME: do we need to compare {}, [], "", or "null"? if bytes.Compare(data, []byte{'n', 'u', 'l', 'l'}) == 0 { *v = Zero return nil } if len(data) == 0 { *v = Zero return nil } var err error if data[0] == '"' { data = data[1 : len(data)-1] } if *v, err = NewFromBytes(data); err != nil { return err } return nil } func Must(v Value, err error) Value { if err != nil { panic(err) } return v } // v * 10^(exp) func (v Value) MulExp(exp int) Value { return Value{v.coef, v.sign, v.exp + exp} } // Sub returns the difference of two Value's func Sub(x, y Value) Value { return Add(x, y.Neg()) } func (x Value) Sub(y Value) Value { return Sub(x, y) } // Add returns the sum of two Value's func Add(x, y Value) Value { switch { case x.sign == signZero: return y case y.sign == signZero: return x case x.IsInf(): if y.sign == -x.sign { return Zero } return x case y.IsInf(): return y } if !align(&x, &y) { return x } if x.sign != y.sign { return usub(x, y) } return uadd(x, y) } func (x Value) Add(y Value) Value { return Add(x, y) } func uadd(x, y Value) Value { return New(x.sign, x.coef+y.coef, int(x.exp)) } func usub(x, y Value) Value { if x.coef < y.coef { return New(-x.sign, y.coef-x.coef, int(x.exp)) } return New(x.sign, x.coef-y.coef, int(x.exp)) } func align(x, y *Value) bool { if x.exp == y.exp { return true } if x.exp < y.exp { *x, *y = *y, *x // swap } yshift := ilog10(y.coef) e := int(x.exp - y.exp) if e > yshift { return false } yshift = e //check(0 <= yshift && yshift <= 20) y.coef = (y.coef + halfpow10[yshift]) / pow10[yshift] //check(int(y.exp)+yshift == int(x.exp)) return true } const e7 = 10000000 // Mul returns the product of two Value's func Mul(x, y Value) Value { sign := x.sign * y.sign switch { case sign == signZero: return Zero case x.IsInf() || y.IsInf(): return Inf(sign) } e := int(x.exp) + int(y.exp) // split unevenly to use full 64 bit range to get more precision // and avoid needing xlo * ylo xhi := x.coef / e7 // 9 digits xlo := x.coef % e7 // 7 digits yhi := y.coef / e7 // 9 digits ylo := y.coef % e7 // 7 digits c := xhi * yhi if (xlo | ylo) != 0 { c += (xlo*yhi + ylo*xhi) / e7 } return New(sign, c, e-2) } func (x Value) Mul(y Value) Value { return Mul(x, y) } // Div returns the quotient of two Value's func Div(x, y Value) Value { sign := x.sign * y.sign switch { case x.sign == signZero: return x case y.sign == signZero: return Inf(x.sign) case x.IsInf(): if y.IsInf() { if sign < 0 { return NegOne } return One } return Inf(sign) case y.IsInf(): return Zero } coef := div128(x.coef, y.coef) return New(sign, coef, int(x.exp)-int(y.exp)) } func (x Value) Div(y Value) Value { return Div(x, y) } // Hash returns a hash value for a Value func (dn Value) Hash() uint32 { return uint32(dn.coef>>32) ^ uint32(dn.coef) ^ uint32(dn.sign)<<16 ^ uint32(dn.exp)<<8 } // Format converts a number to a string with a specified format func (dn Value) Format(mask string) string { if dn.IsInf() { return "#" } n := dn before := 0 after := 0 intpart := true for _, mc := range mask { switch mc { case '.': intpart = false case '#': if intpart { before++ } else { after++ } } } if before+after == 0 || n.Exp() > before { return "#" // too big to fit in mask } n = n.Round(after, HalfUp) e := n.Exp() var digits []byte if n.IsZero() && after == 0 { digits = []byte("0") e = 1 } else { digits = strconv.AppendUint(make([]byte, 0, digitsMax), n.Coef(), 10) digits = bytes.TrimRight(digits, "0") } nd := len(digits) di := e - before //check(di <= 0) var buf strings.Builder sign := n.Sign() signok := (sign >= 0) frac := false for _, mc := range []byte(mask) { switch mc { case '#': if 0 <= di && di < nd { buf.WriteByte(digits[di]) } else if frac || di >= 0 { buf.WriteByte('0') } di++ case ',': if di > 0 { buf.WriteByte(',') } case '-', '(': signok = true if sign < 0 { buf.WriteByte(mc) } case ')': if sign < 0 { buf.WriteByte(mc) } else { buf.WriteByte(' ') } case '.': frac = true fallthrough default: buf.WriteByte(mc) } } if !signok { return "-" // negative not handled by mask } return buf.String() }