about summary refs log blame commit diff stats
path: root/renderfloat/renderfloat.go
blob: 042a36dfaef3c5fc94ede8bb374f9fb9d3c5ad7c (plain) (tree)

































































































































































































                                                                                            
/*

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.
[my note: the WTFPL was attached: DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE]

*/
package renderfloat

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))
}