196 lines
4.6 KiB
Go
196 lines
4.6 KiB
Go
package app
|
||
|
||
/*
|
||
|
||
Author: https://github.com/gorhill
|
||
Source: https://gist.github.com/gorhill/5285193
|
||
|
||
A Go function to render a number to a string based on
|
||
the following user-specified criteria:
|
||
|
||
* thousands separator
|
||
* decimal separator
|
||
* decimal precision
|
||
|
||
Usage: s := RenderFloat(format, n)
|
||
|
||
The format parameter tells how to render the number n.
|
||
|
||
http://play.golang.org/p/LXc1Ddm1lJ
|
||
|
||
Examples of format strings, given n = 12345.6789:
|
||
|
||
"#,###.##" => "12,345.67"
|
||
"#,###." => "12,345"
|
||
"#,###" => "12345,678"
|
||
"#\u202F###,##" => "12 345,67"
|
||
"#.###,###### => 12.345,678900
|
||
"" (aka default format) => 12,345.67
|
||
|
||
The highest precision allowed is 9 digits after the decimal symbol.
|
||
There is also a version for integer number, RenderInteger(),
|
||
which is convenient for calls within template.
|
||
|
||
I didn't feel it was worth to publish a library just for this piece
|
||
of code, hence the snippet. Feel free to reuse as you wish.
|
||
const rPattern = "#,###.##"
|
||
*/
|
||
|
||
import (
|
||
"math"
|
||
"strconv"
|
||
)
|
||
|
||
var renderFloatPrecisionMultipliers = [10]float64{
|
||
1,
|
||
10,
|
||
100,
|
||
1000,
|
||
10000,
|
||
100000,
|
||
1000000,
|
||
10000000,
|
||
100000000,
|
||
1000000000,
|
||
}
|
||
|
||
var renderFloatPrecisionRounders = [10]float64{
|
||
0.5,
|
||
0.05,
|
||
0.005,
|
||
0.0005,
|
||
0.00005,
|
||
0.000005,
|
||
0.0000005,
|
||
0.00000005,
|
||
0.000000005,
|
||
0.0000000005,
|
||
}
|
||
|
||
// func renderInteger(format string, n int) string {
|
||
// return renderFloat(format, float64(n))
|
||
// }
|
||
|
||
// RenderFloat renders a number to a string
|
||
func RenderFloat(format string, n float64) string { //nolint:funlen,gocyclo // lots of comments
|
||
// Special cases:
|
||
// NaN = "NaN"
|
||
// +Inf = "+Infinity"
|
||
// -Inf = "-Infinity"
|
||
if math.IsNaN(n) {
|
||
return "NaN"
|
||
}
|
||
if n > math.MaxFloat64 {
|
||
return "Infinity"
|
||
}
|
||
if n < -math.MaxFloat64 {
|
||
return "-Infinity"
|
||
}
|
||
|
||
// default format
|
||
precision := 2
|
||
decimalStr := "."
|
||
thousandStr := ","
|
||
positiveStr := ""
|
||
negativeStr := "-"
|
||
|
||
if len(format) > 0 {
|
||
// If there is an explicit format directive,
|
||
// then default values are these:
|
||
precision = 9
|
||
thousandStr = ""
|
||
|
||
// collect indices of meaningful formatting directives
|
||
formatDirectiveChars := []rune(format)
|
||
formatDirectiveIndices := make([]int, 0)
|
||
for i, char := range formatDirectiveChars {
|
||
if char != '#' && char != '0' {
|
||
formatDirectiveIndices = append(formatDirectiveIndices, i)
|
||
}
|
||
}
|
||
|
||
if len(formatDirectiveIndices) > 0 {
|
||
// Directive at index 0:
|
||
// Must be a '+'
|
||
// Raise an error if not the case
|
||
// index: 0123456789
|
||
// +0.000,000
|
||
// +000,000.0
|
||
// +0000.00
|
||
// +0000
|
||
if formatDirectiveIndices[0] == 0 {
|
||
if formatDirectiveChars[formatDirectiveIndices[0]] != '+' {
|
||
panic("RenderFloat(): invalid positive sign directive")
|
||
}
|
||
positiveStr = "+"
|
||
formatDirectiveIndices = formatDirectiveIndices[1:]
|
||
}
|
||
|
||
// Two directives:
|
||
// First is thousands separator
|
||
// Raise an error if not followed by 3-digit
|
||
// 0123456789
|
||
// 0.000,000
|
||
// 000,000.00
|
||
if len(formatDirectiveIndices) == 2 { //nolint:gomnd // not my code
|
||
if (formatDirectiveIndices[1] - formatDirectiveIndices[0]) != 4 { //nolint:gomnd // not my code
|
||
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||
}
|
||
thousandStr = string(formatDirectiveChars[formatDirectiveIndices[0]])
|
||
formatDirectiveIndices = formatDirectiveIndices[1:]
|
||
}
|
||
|
||
// One directive:
|
||
// Directive is decimal separator
|
||
// The number of digit-specifier following the separator indicates wanted precision
|
||
// 0123456789
|
||
// 0.00
|
||
// 000,0000
|
||
if len(formatDirectiveIndices) == 1 {
|
||
decimalStr = string(formatDirectiveChars[formatDirectiveIndices[0]])
|
||
precision = len(formatDirectiveChars) - formatDirectiveIndices[0] - 1
|
||
}
|
||
}
|
||
}
|
||
|
||
// generate sign part
|
||
var signStr string
|
||
if n >= 0.000000001 { //nolint:gomnd,gocritic // not my code!
|
||
signStr = positiveStr
|
||
} else if n <= -0.000000001 {
|
||
signStr = negativeStr
|
||
n = -n
|
||
} else {
|
||
signStr = ""
|
||
n = 0.0
|
||
}
|
||
|
||
// split number into integer and fractional parts
|
||
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
||
|
||
// generate integer part string
|
||
intStr := strconv.Itoa(int(intf))
|
||
|
||
// add thousand separator if required
|
||
if len(thousandStr) > 0 {
|
||
for i := len(intStr); i > 3; {
|
||
i -= 3
|
||
intStr = intStr[:i] + thousandStr + intStr[i:]
|
||
}
|
||
}
|
||
|
||
// no fractional part, we can leave now
|
||
if precision == 0 {
|
||
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
|
||
}
|