Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 14 additions & 40 deletions number.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,9 @@ Source: https://gist.github.com/gorhill/5285193

import (
"math"
"math/big"
"strconv"
)

var (
renderFloatPrecisionMultipliers = [...]float64{
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
}

renderFloatPrecisionRounders = [...]float64{
0.5,
0.05,
0.005,
0.0005,
0.00005,
0.000005,
0.0000005,
0.00000005,
0.000000005,
0.0000000005,
}
"strings"
)

// FormatFloat produces a formatted number as string based on the following user-specified criteria:
Expand Down Expand Up @@ -157,11 +131,7 @@ func FormatFloat(format string, n float64) string {
n = 0.0
}

// split number into integer and fractional parts
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])

// generate integer part string
intStr := strconv.FormatFloat(intf, 'f', 0, 64)
intStr, fracStr, _ := strings.Cut(formatHalfUp(n, precision), ".")

// add thousand separator if required
if len(thousandStr) > 0 {
Expand All @@ -176,16 +146,20 @@ func FormatFloat(format string, n float64) string {
return signStr + intStr
}

// generate fractional part
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
// may need padding
if len(fracStr) < precision {
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
}

return signStr + intStr + decimalStr + fracStr
}

// formatHalfUp formats n with precision decimals, rounding halves up.
func formatHalfUp(n float64, precision int) string {
// strconv rounds the exact binary value correctly, but breaks ties to even.
if s := strconv.FormatFloat(n, 'f', precision+1, 64); s[len(s)-1] == '5' {
if r, _ := new(big.Rat).SetString(s); r.Cmp(new(big.Rat).SetFloat64(n)) == 0 {
return r.FloatString(precision)
}
}
return strconv.FormatFloat(n, 'f', precision, 64)
}

// FormatInteger produces a formatted number as string.
// See FormatFloat.
func FormatInteger(format string, n int) string {
Expand Down
7 changes: 7 additions & 0 deletions number_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ func TestFormatFloat(t *testing.T) {
{"negative int64 boundary", "#,###.##", -9223372036854775808, "-9,223,372,036,854,775,808.00"},
{"large integer", "#.", 1e20, "100000000000000000000"},
{"large integer with sign", "+#.###,##", 1e20, "+100.000.000.000.000.000.000,00"},
{"carry into the last digit", "#,###.#", 8894.75, "8,894.8"},
{"carry into the last digit, negative", "#,###.#", -8894.75, "-8,894.8"},
{"just above a tie", "#,###.##", 71.575, "71.58"},
{"exact tie rounds up", "#,###.##", 945.125, "945.13"},
{"exact tie rounds up, small", "#,###.##", 0.125, "0.13"},
{"exact tie rounds up, large", "#,###.#", 731701778190334.75, "731,701,778,190,334.8"},
{"just below a tie", "#,###.##", 1.005, "1.00"},
{"#\u202f###,##", "#\u202f###,##", 12345.6789, "12 345,68"},

// special cases
Expand Down