package humanize import ( "bytes" "math" "math/big" "strconv" "strings" ) // Comma produces a string form of the given number in base 10 with // commas after every three orders of magnitude. // // e.g. Comma(834142) -> 834,142 func Comma(v int64) string { // Shortcut for [0, 7] if v&^0b111 == 0 { return string([]byte{byte(v) + 48}) } // Min int64 can't be negated to a usable value, so it has to be special cased. if v == math.MinInt64 { return "-9,223,372,036,854,775,808" } // Counting the number of digits. var count byte = 0 for n := v; n != 0; n = n / 10 { count++ } count += (count - 1) / 3 if v < 0 { v = 0 - v count++ } output := make([]byte, count) j := len(output) - 1 var counter byte = 0 for v > 9 { output[j] = byte(v%10) + 48 v = v / 10 j-- if counter == 2 { counter = 0 output[j] = ',' j-- } else { counter++ } } output[j] = byte(v) + 48 if j == 1 { output[0] = '-' } return string(output) } // Commaf produces a string form of the given number in base 10 with // commas after every three orders of magnitude. // // e.g. Commaf(834142.32) -> 834,142.32 func Commaf(v float64) string { if math.IsNaN(v) { return "NaN" } if math.IsInf(v, 1) { return "+Inf" } if math.IsInf(v, -1) { return "-Inf" } buf := &bytes.Buffer{} if v < 0 { buf.Write([]byte{'-'}) v = 0 - v } comma := []byte{','} parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".") pos := 0 if len(parts[0])%3 != 0 { pos += len(parts[0]) % 3 buf.WriteString(parts[0][:pos]) buf.Write(comma) } for ; pos < len(parts[0]); pos += 3 { buf.WriteString(parts[0][pos : pos+3]) buf.Write(comma) } buf.Truncate(buf.Len() - 1) if len(parts) > 1 { buf.Write([]byte{'.'}) buf.WriteString(parts[1]) } return buf.String() } // CommafWithDigits works like the Commaf but always formats the // resulting string to exactly the given number of decimal places, // truncating extra digits or padding with zeros as needed. // // e.g. CommafWithDigits(834142.32, 1) -> 834,142.3 // e.g. CommafWithDigits(1000, 2) -> 1,000.00 func CommafWithDigits(f float64, decimals int) string { s := stripTrailingDigits(Commaf(f), decimals) if decimals <= 0 { return s } if dot := strings.IndexByte(s, '.'); dot < 0 { s += "." + strings.Repeat("0", decimals) } else if have := len(s) - dot - 1; have < decimals { s += strings.Repeat("0", decimals-have) } return s } // BigComma produces a string form of the given big.Int in base 10 // with commas after every three orders of magnitude. func BigComma(bin *big.Int) string { b := new(big.Int).Set(bin) sign := "" if b.Sign() < 0 { sign = "-" b.Abs(b) } athousand := big.NewInt(1000) c := (&big.Int{}).Set(b) _, m := oom(c, athousand) parts := make([]string, m+1) j := len(parts) - 1 mod := &big.Int{} for b.Cmp(athousand) >= 0 { b.DivMod(b, athousand, mod) parts[j] = strconv.FormatInt(mod.Int64(), 10) switch len(parts[j]) { case 2: parts[j] = "0" + parts[j] case 1: parts[j] = "00" + parts[j] } j-- } parts[j] = strconv.Itoa(int(b.Int64())) return sign + strings.Join(parts[j:], ",") } // ParseComma parses a string produced by Comma. // It strips commas and returns the int64 value, or an error if the // string cannot be parsed. func ParseComma(s string) (int64, error) { s = strings.ReplaceAll(s, ",", "") return strconv.ParseInt(s, 10, 64) } // ParseCommaf parses a string produced by Commaf. // It strips commas and returns the float64 value, or an error if the // string cannot be parsed. func ParseCommaf(s string) (float64, error) { s = strings.ReplaceAll(s, ",", "") return strconv.ParseFloat(s, 64) }