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. */ import ( "math" "strconv" ) const rPattern = "#,###.##" 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 }