A Go function to render a number to a string based on the following user-specified criteria: thousands separator, decimal separator, decimal precision. 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.
/* | |
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. | |
*/ | |
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 RenderFloat(format string, n float64) string { | |
// 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 { | |
if (formatDirectiveIndices[1] - formatDirectiveIndices[0]) != 4 { | |
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 { | |
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 | |
} | |
func RenderInteger(format string, n int) string { | |
return RenderFloat(format, float64(n)) | |
} |
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
Version 2, December 2004 | |
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> | |
Everyone is permitted to copy and distribute verbatim or modified | |
copies of this license document, and changing it is allowed as long | |
as the name is changed. | |
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
0. You just DO WHAT THE FUCK YOU WANT TO. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
On my Raspberry Pi which runs a 32-bit version of Raspbian Linux (even though it has a 64-bit CPU) using go version go1.14.2 linux/arm, I had to change this line. Otherwise
RenderInteger("#,###.", n)
would fail when n >2^32
.https://gist.github.com/gorhill/5285193#file-render_number-go-L165
from:
to: