├── go.mod ├── formatnumber_32bit_test.go ├── formatnumber15.go ├── .gitignore ├── unformatnumber_test.go ├── accounting15.go ├── .travis.yml ├── go.sum ├── formatnumber15_test.go ├── formatnumber_64bit_test.go ├── accounting15_test.go ├── LICENSE ├── unformatnumber.go ├── formatnumber.go ├── accounting.go ├── formatnumber_test.go ├── accounting_test.go ├── locale.go └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/leekchan/accounting 2 | 3 | require ( 4 | github.com/cockroachdb/apd v1.1.0 5 | github.com/lib/pq v1.0.0 // indirect 6 | github.com/pkg/errors v0.8.1 // indirect 7 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 8 | ) 9 | 10 | go 1.13 11 | -------------------------------------------------------------------------------- /formatnumber_32bit_test.go: -------------------------------------------------------------------------------- 1 | // +build 386 2 | 3 | package accounting 4 | 5 | import ( 6 | "math" 7 | "testing" 8 | ) 9 | 10 | func TestFormatNumber32Bit(t *testing.T) { 11 | AssertEqual(t, FormatNumber(math.MaxInt32, 10, ",", "."), "2,147,483,647.0000000000") 12 | AssertEqual(t, FormatNumber(math.MinInt32, 10, ",", "."), "-2,147,483,648.0000000000") 13 | } 14 | -------------------------------------------------------------------------------- /formatnumber15.go: -------------------------------------------------------------------------------- 1 | // +build go1.5 2 | 3 | package accounting 4 | 5 | import ( 6 | "math/big" 7 | ) 8 | 9 | // FormatNumberBigFloat only supports *big.Float value. 10 | // It is faster than FormatNumber, because it does not do any runtime type evaluation. 11 | func FormatNumberBigFloat(x *big.Float, precision int, thousand string, decimal string) string { 12 | return formatNumberString(x.Text('f', precision), precision, thousand, decimal) 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Ignore jetbrains 27 | .idea/ 28 | # ignore vim 29 | *.swa 30 | *.swp 31 | -------------------------------------------------------------------------------- /unformatnumber_test.go: -------------------------------------------------------------------------------- 1 | package accounting 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUnformatNumberCommaDecimal(t *testing.T) { 8 | AssertEqual(t, UnformatNumber("$4,500.23", 2, "USD"), "4500.23") 9 | } 10 | 11 | func TestUnformatNumberDecimalComma(t *testing.T) { 12 | AssertEqual(t, UnformatNumber("EUR 45.000,33", 2, "eur"), "45000.33") 13 | 14 | func() { 15 | defer func() { 16 | recover() 17 | }() 18 | UnformatNumber("$45,567.10", 2, "zzz") 19 | }() 20 | } 21 | -------------------------------------------------------------------------------- /accounting15.go: -------------------------------------------------------------------------------- 1 | // +build go1.5 2 | 3 | package accounting 4 | 5 | import ( 6 | "math/big" 7 | ) 8 | 9 | // FormatMoneyBigFloat only supports *big.Float value. It is faster than FormatMoney, 10 | // because it does not do any runtime type evaluation. 11 | func (accounting *Accounting) FormatMoneyBigFloat(value *big.Float) string { 12 | accounting.init() 13 | formattedNumber := FormatNumberBigFloat(value, accounting.Precision, accounting.Thousand, accounting.Decimal) 14 | return accounting.formatMoneyString(formattedNumber) 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.11 4 | - tip 5 | env: 6 | global: 7 | - BUILD_GOARCH=amd64 8 | matrix: 9 | - BUILD_GOOS=linux 10 | - BUILD_GOOS=darwin 11 | - BUILD_GOOS=windows 12 | before_install: 13 | - go get -v -t -d ./... 14 | - go get github.com/axw/gocov/gocov 15 | - go get github.com/mattn/goveralls 16 | - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 17 | script: 18 | - go test -v ./... 19 | - $HOME/gopath/bin/goveralls -service=travis-ci 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 2 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 3 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 4 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 5 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 6 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 7 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= 8 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 9 | -------------------------------------------------------------------------------- /formatnumber15_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.5 2 | 3 | package accounting 4 | 5 | import ( 6 | "math/big" 7 | "testing" 8 | ) 9 | 10 | func TestFormatNumberBigFloat(t *testing.T) { 11 | AssertEqual(t, FormatNumberBigFloat(big.NewFloat(123456789.213123), 3, ",", "."), "123,456,789.213") 12 | AssertEqual(t, FormatNumberBigFloat(big.NewFloat(-12345.123123), 5, ",", "."), "-12,345.12312") 13 | AssertEqual(t, FormatNumberBigFloat(big.NewFloat(-1234.123123), 5, ",", "."), "-1,234.12312") 14 | AssertEqual(t, FormatNumberBigFloat(big.NewFloat(-123.123123), 5, ",", "."), "-123.12312") 15 | AssertEqual(t, FormatNumberBigFloat(big.NewFloat(-12.123123), 5, ",", "."), "-12.12312") 16 | AssertEqual(t, FormatNumberBigFloat(big.NewFloat(-1.123123), 5, ",", "."), "-1.12312") 17 | } 18 | -------------------------------------------------------------------------------- /formatnumber_64bit_test.go: -------------------------------------------------------------------------------- 1 | // +build amd64 2 | 3 | package accounting 4 | 5 | import ( 6 | "math" 7 | "testing" 8 | ) 9 | 10 | func TestFormatNumber64Bit(t *testing.T) { 11 | AssertEqual(t, FormatNumber(math.MaxInt64, 10, ",", "."), "9,223,372,036,854,775,807.0000000000") 12 | AssertEqual(t, FormatNumber(math.MinInt64, 10, ",", "."), "-9,223,372,036,854,775,808.0000000000") 13 | } 14 | 15 | func TestFormatNumberInt64Bit(t *testing.T) { 16 | AssertEqual(t, FormatNumberInt(math.MaxInt64, 10, ",", "."), "9,223,372,036,854,775,807.0000000000") 17 | AssertEqual(t, FormatNumberInt(math.MinInt64+1, 10, ",", "."), "-9,223,372,036,854,775,807.0000000000") 18 | AssertEqual(t, FormatNumberInt(math.MinInt64, 10, ",", "."), "-9,223,372,036,854,775,808.0000000000") 19 | } 20 | -------------------------------------------------------------------------------- /accounting15_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.5 2 | 3 | package accounting 4 | 5 | import ( 6 | "math/big" 7 | "testing" 8 | ) 9 | 10 | func TestFormatMoneyBigFloat(t *testing.T) { 11 | accounting := Accounting{Symbol: "$", Precision: 2} 12 | AssertEqual(t, accounting.FormatMoneyBigFloat(big.NewFloat(123456789.213123)), "$123,456,789.21") 13 | 14 | accounting = Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 15 | AssertEqual(t, accounting.FormatMoneyBigFloat(big.NewFloat(4999.99)), "€4.999,99") 16 | 17 | accounting = Accounting{Symbol: "£ ", Precision: 0} 18 | AssertEqual(t, accounting.FormatMoneyBigFloat(big.NewFloat(500000.0)), "£ 500,000") 19 | 20 | accounting = Accounting{Symbol: "GBP", Precision: 0, 21 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 22 | AssertEqual(t, accounting.FormatMoneyBigFloat(big.NewFloat(1000000.0)), "GBP 1,000,000") 23 | AssertEqual(t, accounting.FormatMoneyBigFloat(big.NewFloat(-5000.0)), "GBP (5,000)") 24 | AssertEqual(t, accounting.FormatMoneyBigFloat(big.NewFloat(0.0)), "GBP --") 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kyoung-chan Lee (leekchan@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /unformatnumber.go: -------------------------------------------------------------------------------- 1 | package accounting 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // UnformatNumber takes a string of the number to strip currency info on 11 | // and precision for decimals. 12 | // It pulls the currency descripter from the LocaleInfo map and uses it to return an unformatted value 13 | // based on thous sep and decimal sep 14 | func UnformatNumber(n string, precision int, currency string) string { 15 | var lc Locale 16 | currency = strings.ToUpper(currency) 17 | 18 | if val, ok := LocaleInfo[currency]; ok { 19 | lc = val 20 | } else { 21 | panic("No Locale Info Found") 22 | } 23 | 24 | r := regexp.MustCompile(`[^0-9-., ]`) // Remove anything thats not a space, comma, or decimal 25 | num := r.ReplaceAllString(n, "${1}") 26 | 27 | r = regexp.MustCompile(fmt.Sprintf("\\%v", lc.ThouSep)) // Strip out thousands seperator, whatever it is 28 | num = r.ReplaceAllString(num, "${1}") 29 | 30 | // Replace decimal seperator with a decimal at specified precision 31 | if lc.DecSep != "." { 32 | r = regexp.MustCompile(`\,`) 33 | num = r.ReplaceAllString(num, ".") 34 | } 35 | 36 | num = setPrecision(num, precision) 37 | return num 38 | } 39 | 40 | func setPrecision(num string, precision int) string { 41 | p := fmt.Sprintf("%%.%vf", precision) 42 | num = strings.Trim(num, " ") 43 | v, _ := strconv.ParseFloat(num, 64) 44 | return fmt.Sprintf(p, v) 45 | } 46 | -------------------------------------------------------------------------------- /formatnumber.go: -------------------------------------------------------------------------------- 1 | package accounting 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/big" 7 | "reflect" 8 | "strings" 9 | 10 | "github.com/cockroachdb/apd" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | func formatNumberString(x string, precision int, thousand string, decimalStr string) string { 15 | lastIndex := strings.Index(x, ".") - 1 16 | 17 | if lastIndex < 0 { 18 | lastIndex = len(x) - 1 19 | } 20 | 21 | var buffer []byte 22 | var strBuffer bytes.Buffer 23 | 24 | j := 0 25 | for i := lastIndex; i >= 0; i-- { 26 | j++ 27 | buffer = append(buffer, x[i]) 28 | 29 | if j == 3 && i > 0 && !(i == 1 && x[0] == '-') { 30 | buffer = append(buffer, ',') 31 | j = 0 32 | } 33 | } 34 | 35 | for i := len(buffer) - 1; i >= 0; i-- { 36 | strBuffer.WriteByte(buffer[i]) 37 | } 38 | result := strBuffer.String() 39 | 40 | if thousand != "," { 41 | result = strings.Replace(result, ",", thousand, -1) 42 | } 43 | 44 | extra := x[lastIndex+1:] 45 | if decimalStr != "." { 46 | extra = strings.Replace(extra, ".", decimalStr, 1) 47 | } 48 | 49 | return result + extra 50 | } 51 | 52 | // FormatNumber is a base function of the library which formats a number with custom precision and separators. 53 | // FormatNumber supports various types of value by runtime reflection. 54 | // If you don't need runtime type evaluation, please refer to FormatNumberInt or FormatNumberFloat64. 55 | // (supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, *big.Rat) 56 | // (also supported value types : decimal.Decimal, *decimal.Decimal *apd.Decimal) 57 | func FormatNumber(value interface{}, precision int, thousand string, decimalStr string) string { 58 | v := reflect.ValueOf(value) 59 | var x string 60 | switch v.Kind() { 61 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 62 | x = fmt.Sprintf("%d", v.Int()) 63 | if precision > 0 { 64 | x += "." + strings.Repeat("0", precision) 65 | } 66 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 67 | x = fmt.Sprintf("%d", v.Uint()) 68 | if precision > 0 { 69 | x += "." + strings.Repeat("0", precision) 70 | } 71 | case reflect.Float32, reflect.Float64: 72 | x = fmt.Sprintf(fmt.Sprintf("%%.%df", precision), v.Float()) 73 | case reflect.Struct: 74 | switch v.Type().String() { 75 | case "decimal.Decimal": 76 | x = value.(decimal.Decimal).StringFixed(int32(precision)) 77 | default: 78 | panic("Unsupported type - " + v.Type().String() + " - kind " + v.Kind().String()) 79 | } 80 | case reflect.Ptr: 81 | switch v.Type().String() { 82 | case "*big.Rat": 83 | x = value.(*big.Rat).FloatString(precision) 84 | case "*apd.Decimal": 85 | v := value.(*apd.Decimal) 86 | d := apd.New(0, 0) 87 | apd.BaseContext.WithPrecision(uint32(v.NumDigits()) + uint32(precision)).Quantize(d, v, int32(-precision)) 88 | x = d.Text('f') 89 | case "*decimal.Decimal": 90 | x = value.(*decimal.Decimal).StringFixed(int32(precision)) 91 | default: 92 | panic("Unsupported type - " + v.Type().String() + " - kind " + v.Kind().String()) 93 | } 94 | default: 95 | panic("Unsupported type - " + v.Type().String() + " - kind " + v.Kind().String()) 96 | } 97 | 98 | return formatNumberString(x, precision, thousand, decimalStr) 99 | } 100 | 101 | // FormatNumberInt only supports int value. It is faster than FormatNumber, 102 | // because it does not do any runtime type evaluation. 103 | func FormatNumberInt(x int, precision int, thousand string, decimalStr string) string { 104 | var result string 105 | var minus bool 106 | 107 | if x < 0 { 108 | if x * -1 < 0 { 109 | return FormatNumber(x, precision, thousand, decimalStr) 110 | } 111 | 112 | minus = true 113 | x *= -1 114 | } 115 | 116 | for x >= 1000 { 117 | result = fmt.Sprintf("%s%03d%s", thousand, x%1000, result) 118 | x /= 1000 119 | } 120 | result = fmt.Sprintf("%d%s", x, result) 121 | 122 | if minus { 123 | result = "-" + result 124 | } 125 | 126 | if precision > 0 { 127 | result += decimalStr + strings.Repeat("0", precision) 128 | } 129 | 130 | return result 131 | } 132 | 133 | // FormatNumberFloat64 only supports float64 value. 134 | // It is faster than FormatNumber, because it does not do any runtime type evaluation. 135 | func FormatNumberFloat64(x float64, precision int, thousand string, decimalStr string) string { 136 | return formatNumberString(fmt.Sprintf(fmt.Sprintf("%%.%df", precision), x), precision, thousand, decimalStr) 137 | } 138 | 139 | // FormatNumberBigRat only supports *big.Rat value. 140 | // It is faster than FormatNumber, because it does not do any runtime type evaluation. 141 | func FormatNumberBigRat(x *big.Rat, precision int, thousand string, decimalStr string) string { 142 | return formatNumberString(x.FloatString(precision), precision, thousand, decimalStr) 143 | } 144 | 145 | // FormatNumberBigDecimal only supports *apd.Decimal value. 146 | // It is faster than FormatNumber, because it does not do any runtime type evaluation. 147 | func FormatNumberBigDecimal(x *apd.Decimal, precision int, thousand string, decimalStr string) string { 148 | d := apd.New(0, 0) 149 | apd.BaseContext.WithPrecision(uint32(x.NumDigits()) + uint32(precision)).Quantize(d, x, int32(-precision)) 150 | return formatNumberString(d.Text('f'), precision, thousand, decimalStr) 151 | } 152 | 153 | // FormatNumberDecimal only supports decimal.Decimal value. 154 | // It is faster than FormatNumber, because it does not do any runtime type evaluation. 155 | func FormatNumberDecimal(x decimal.Decimal, precision int, thousand string, decimalStr string) string { 156 | return formatNumberString(x.StringFixed(int32(precision)), precision, thousand, decimalStr) 157 | } 158 | -------------------------------------------------------------------------------- /accounting.go: -------------------------------------------------------------------------------- 1 | package accounting 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | 7 | "github.com/cockroachdb/apd" 8 | "github.com/shopspring/decimal" 9 | ) 10 | 11 | type Accounting struct { 12 | Symbol string // currency symbol (required) 13 | Precision int // currency precision (decimal places) (optional / default: 0) 14 | Thousand string // thousand separator (optional / default: ,) 15 | Decimal string // decimal separator (optional / default: .) 16 | Format string // simple format string allows control of symbol position (%v = value, %s = symbol) (default: %s%v) 17 | FormatNegative string // format string for negative values (optional / default: strings.Replace(strings.Replace(accounting.Format, "-", "", -1), "%v", "-%v", -1)) 18 | FormatZero string // format string for zero values (optional / default: Format) 19 | isInitialized bool // is set to true if used via DefaultAccounting or NewAccounting 20 | } 21 | 22 | 23 | // DefaultAccounting returns the Accounting with default settings 24 | func DefaultAccounting(symbol string, precision int) *Accounting { 25 | ac := &Accounting{Symbol:symbol,Precision:precision} 26 | ac.init() 27 | ac.isInitialized = true 28 | return ac 29 | } 30 | 31 | 32 | // NewAccounting returns the Accounting with default settings 33 | func NewAccounting(symbol string, precision int, thousand, decimal, format, formatNegative, formatZero string) *Accounting { 34 | ac := &Accounting{ 35 | Symbol: symbol, 36 | Precision: precision, 37 | Thousand: thousand, 38 | Decimal: decimal, 39 | Format: format, 40 | FormatNegative: formatNegative, 41 | FormatZero: formatZero, 42 | } 43 | ac.isInitialized = true 44 | return ac 45 | } 46 | 47 | // SetThousandSeparator sets the separator for the thousands separation 48 | func (accounting *Accounting) SetThousandSeparator(str string) { 49 | accounting.Thousand = str 50 | } 51 | 52 | // SetDecimalSeparator sets the separator for the decimal separation 53 | func (accounting *Accounting) SetDecimalSeparator(str string) { 54 | accounting.Decimal = str 55 | } 56 | 57 | // SetFormat sets the Format default: %s%v (%s=Symbol;%v=Value) 58 | func (accounting *Accounting) SetFormat(str string) { 59 | accounting.Format = str 60 | } 61 | 62 | // SetFormatNegative sets the Format for negative values default: -%s%v (%s=Symbol;%v=Value) 63 | func (accounting *Accounting) SetFormatNegative(str string) { 64 | accounting.FormatNegative = str 65 | } 66 | 67 | // SetFormatZero sets the Format for zero values default: %s%v (%s=Symbol;%v=Value) 68 | func (accounting *Accounting) SetFormatZero(str string) { 69 | accounting.FormatZero = str 70 | } 71 | 72 | func (accounting *Accounting) init() { 73 | if accounting.Thousand == "" { 74 | accounting.Thousand = "," 75 | } 76 | 77 | if accounting.Decimal == "" { 78 | accounting.Decimal = "." 79 | } 80 | 81 | if accounting.Format == "" { 82 | accounting.Format = "%s%v" 83 | } 84 | 85 | if accounting.FormatNegative == "" { 86 | accounting.FormatNegative = "-" + accounting.Format 87 | } 88 | 89 | if accounting.FormatZero == "" { 90 | accounting.FormatZero = accounting.Format 91 | } 92 | } 93 | 94 | func (accounting *Accounting) formatMoneyString(formattedNumber string) string { 95 | var format string 96 | 97 | formattedZero := FormatNumber(0, accounting.Precision, accounting.Thousand, accounting.Decimal) 98 | 99 | if formattedNumber[0] == '-' { 100 | format = accounting.FormatNegative 101 | formattedNumber = formattedNumber[1:] 102 | } else if formattedNumber == formattedZero { 103 | format = accounting.FormatZero 104 | } else { 105 | format = accounting.Format 106 | } 107 | 108 | result := strings.Replace(format, "%s", accounting.Symbol, -1) 109 | result = strings.Replace(result, "%v", formattedNumber, -1) 110 | 111 | return result 112 | } 113 | 114 | // FormatMoney is a function for formatting numbers as money values, 115 | // with customisable currency symbol, precision (decimal places), and thousand/decimal separators. 116 | // FormatMoney supports various types of value by runtime reflection. 117 | // If you don't need runtime type evaluation, please refer to FormatMoneyInt or FormatMoneyFloat64. 118 | // (supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, *big.Rat) 119 | func (accounting *Accounting) FormatMoney(value interface{}) string { 120 | if !accounting.isInitialized { 121 | accounting.init() 122 | } 123 | formattedNumber := FormatNumber(value, accounting.Precision, accounting.Thousand, accounting.Decimal) 124 | return accounting.formatMoneyString(formattedNumber) 125 | } 126 | 127 | // FormatMoneyInt only supports int type value. It is faster than FormatMoney, 128 | // because it does not do any runtime type evaluation. 129 | func (accounting *Accounting) FormatMoneyInt(value int) string { 130 | if !accounting.isInitialized { 131 | accounting.init() 132 | } 133 | formattedNumber := FormatNumberInt(value, accounting.Precision, accounting.Thousand, accounting.Decimal) 134 | return accounting.formatMoneyString(formattedNumber) 135 | } 136 | 137 | // FormatMoneyFloat64 only supports float64 value. It is faster than FormatMoney, 138 | // because it does not do any runtime type evaluation. 139 | // (Caution: Please do not use float64 to count money. 140 | // Floats can have errors when you perform operations on them. 141 | // Using big.Rat is highly recommended.) 142 | func (accounting *Accounting) FormatMoneyFloat64(value float64) string { 143 | if !accounting.isInitialized { 144 | accounting.init() 145 | } 146 | formattedNumber := FormatNumberFloat64(value, accounting.Precision, accounting.Thousand, accounting.Decimal) 147 | return accounting.formatMoneyString(formattedNumber) 148 | } 149 | 150 | // FormatMoneyBigRat only supports *big.Rat value. It is faster than FormatMoney, 151 | // because it does not do any runtime type evaluation. 152 | func (accounting *Accounting) FormatMoneyBigRat(value *big.Rat) string { 153 | if !accounting.isInitialized { 154 | accounting.init() 155 | } 156 | formattedNumber := FormatNumberBigRat(value, accounting.Precision, accounting.Thousand, accounting.Decimal) 157 | return accounting.formatMoneyString(formattedNumber) 158 | } 159 | 160 | // FormatMoneyBigDecimal only supports *apd.Decimal value. It is faster than FormatMoney, 161 | // because it does not do any runtime type evaluation. 162 | func (accounting *Accounting) FormatMoneyBigDecimal(value *apd.Decimal) string { 163 | if !accounting.isInitialized { 164 | accounting.init() 165 | } 166 | formattedNumber := FormatNumberBigDecimal(value, accounting.Precision, accounting.Thousand, accounting.Decimal) 167 | return accounting.formatMoneyString(formattedNumber) 168 | } 169 | 170 | // FormatMoneyDecimal only supports decimal.Decimal value. It is faster than FormatMoney, 171 | // because it does not do any runtime type evaluation. 172 | func (accounting *Accounting) FormatMoneyDecimal(value decimal.Decimal) string { 173 | accounting.init() 174 | formattedNumber := FormatNumberDecimal(value, accounting.Precision, accounting.Thousand, accounting.Decimal) 175 | return accounting.formatMoneyString(formattedNumber) 176 | } 177 | -------------------------------------------------------------------------------- /formatnumber_test.go: -------------------------------------------------------------------------------- 1 | package accounting 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/cockroachdb/apd" 8 | "github.com/shopspring/decimal" 9 | ) 10 | 11 | func TestFormatNumber(t *testing.T) { 12 | AssertEqual(t, FormatNumber(123456789.213123, 3, ",", "."), "123,456,789.213") 13 | AssertEqual(t, FormatNumber(123456789.213123, 3, ".", ","), "123.456.789,213") 14 | AssertEqual(t, FormatNumber(-12345.123123, 5, ",", "."), "-12,345.12312") 15 | AssertEqual(t, FormatNumber(-1234.123123, 5, ",", "."), "-1,234.12312") 16 | AssertEqual(t, FormatNumber(-123.123123, 5, ",", "."), "-123.12312") 17 | AssertEqual(t, FormatNumber(-12.123123, 5, ",", "."), "-12.12312") 18 | AssertEqual(t, FormatNumber(-1.123123, 5, ",", "."), "-1.12312") 19 | AssertEqual(t, FormatNumber(-1, 3, ",", "."), "-1.000") 20 | AssertEqual(t, FormatNumber(-10, 3, ",", "."), "-10.000") 21 | AssertEqual(t, FormatNumber(-100, 3, ",", "."), "-100.000") 22 | AssertEqual(t, FormatNumber(-1000, 3, ",", "."), "-1,000.000") 23 | AssertEqual(t, FormatNumber(-10000, 3, ",", "."), "-10,000.000") 24 | AssertEqual(t, FormatNumber(-100000, 3, ",", "."), "-100,000.000") 25 | AssertEqual(t, FormatNumber(-1000000, 3, ",", "."), "-1,000,000.000") 26 | AssertEqual(t, FormatNumber(1, 3, ",", "."), "1.000") 27 | AssertEqual(t, FormatNumber(10, 3, ",", "."), "10.000") 28 | AssertEqual(t, FormatNumber(100, 3, ",", "."), "100.000") 29 | AssertEqual(t, FormatNumber(1000, 3, ",", "."), "1,000.000") 30 | AssertEqual(t, FormatNumber(10000, 3, ",", "."), "10,000.000") 31 | AssertEqual(t, FormatNumber(100000, 3, ",", "."), "100,000.000") 32 | AssertEqual(t, FormatNumber(1000000, 3, ",", "."), "1,000,000.000") 33 | AssertEqual(t, FormatNumber(1000000, 10, " ", "."), "1 000 000.0000000000") 34 | AssertEqual(t, FormatNumber(1000000, 10, " ", "."), "1 000 000.0000000000") 35 | AssertEqual(t, FormatNumber(uint(1000000), 3, ",", "."), "1,000,000.000") 36 | 37 | AssertEqual(t, FormatNumber(big.NewRat(77777777, 3), 3, ",", "."), "25,925,925.667") 38 | AssertEqual(t, FormatNumber(big.NewRat(-77777777, 3), 3, ",", "."), "-25,925,925.667") 39 | AssertEqual(t, FormatNumber(big.NewRat(-7777777, 3), 3, ",", "."), "-2,592,592.333") 40 | AssertEqual(t, FormatNumber(big.NewRat(-777776, 3), 3, ",", "."), "-259,258.667") 41 | 42 | AssertEqual(t, FormatNumber(apd.New(123456789213123, -6), 3, ",", "."), "123,456,789.213") 43 | AssertEqual(t, FormatNumber(apd.New(-12345123123, -6), 5, ",", "."), "-12,345.12312") 44 | AssertEqual(t, FormatNumber(apd.New(-1234123123, -6), 5, ",", "."), "-1,234.12312") 45 | AssertEqual(t, FormatNumber(apd.New(-123123123, -6), 5, ",", "."), "-123.12312") 46 | AssertEqual(t, FormatNumber(apd.New(-12123123, -6), 5, ",", "."), "-12.12312") 47 | AssertEqual(t, FormatNumber(apd.New(-1123123, -6), 5, ",", "."), "-1.12312") 48 | 49 | d1 := decimal.New(123456789213123, -6) 50 | d2 := decimal.New(-12345123123, -6) 51 | d3 := decimal.New(-1234123123, -6) 52 | d4 := decimal.New(-123123123, -6) 53 | d5 := decimal.New(-12123123, -6) 54 | d6 := decimal.New(-1123123, -6) 55 | 56 | AssertEqual(t, FormatNumber(d1, 3, ",", "."), "123,456,789.213") 57 | AssertEqual(t, FormatNumber(d2, 5, ",", "."), "-12,345.12312") 58 | AssertEqual(t, FormatNumber(d3, 5, ",", "."), "-1,234.12312") 59 | AssertEqual(t, FormatNumber(d4, 5, ",", "."), "-123.12312") 60 | AssertEqual(t, FormatNumber(d5, 5, ",", "."), "-12.12312") 61 | AssertEqual(t, FormatNumber(d6, 5, ",", "."), "-1.12312") 62 | 63 | AssertEqual(t, FormatNumber(&d1, 3, ",", "."), "123,456,789.213") 64 | AssertEqual(t, FormatNumber(&d2, 5, ",", "."), "-12,345.12312") 65 | AssertEqual(t, FormatNumber(&d3, 5, ",", "."), "-1,234.12312") 66 | AssertEqual(t, FormatNumber(&d4, 5, ",", "."), "-123.12312") 67 | AssertEqual(t, FormatNumber(&d5, 5, ",", "."), "-12.12312") 68 | AssertEqual(t, FormatNumber(&d6, 5, ",", "."), "-1.12312") 69 | 70 | func() { 71 | defer func() { 72 | recover() 73 | }() 74 | FormatNumber(false, 3, ",", ".") // panic: Unsupported type - bool 75 | }() 76 | func() { 77 | defer func() { 78 | recover() 79 | }() 80 | FormatNumber(big.NewInt(1), 3, ",", ".") // panic: Unsupported type - *big.Int 81 | }() 82 | func() { 83 | type demo struct { 84 | Value int 85 | } 86 | defer func() { 87 | recover() 88 | }() 89 | FormatNumber(demo{Value: 1}, 3, ",", ".") // panic: Unsupported type - *big.Int 90 | }() 91 | } 92 | 93 | func TestFormatNumberInt(t *testing.T) { 94 | AssertEqual(t, FormatNumberInt(-1, 3, ",", "."), "-1.000") 95 | AssertEqual(t, FormatNumberInt(-10, 3, ",", "."), "-10.000") 96 | AssertEqual(t, FormatNumberInt(-100, 3, ",", "."), "-100.000") 97 | AssertEqual(t, FormatNumberInt(-1000, 3, ",", "."), "-1,000.000") 98 | AssertEqual(t, FormatNumberInt(-10000, 3, ",", "."), "-10,000.000") 99 | AssertEqual(t, FormatNumberInt(-100000, 3, ",", "."), "-100,000.000") 100 | AssertEqual(t, FormatNumberInt(-1000000, 3, ",", "."), "-1,000,000.000") 101 | AssertEqual(t, FormatNumberInt(1, 3, ",", "."), "1.000") 102 | AssertEqual(t, FormatNumberInt(10, 3, ",", "."), "10.000") 103 | AssertEqual(t, FormatNumberInt(100, 3, ",", "."), "100.000") 104 | AssertEqual(t, FormatNumberInt(1000, 3, ",", "."), "1,000.000") 105 | AssertEqual(t, FormatNumberInt(10000, 3, ",", "."), "10,000.000") 106 | AssertEqual(t, FormatNumberInt(100000, 3, ",", "."), "100,000.000") 107 | AssertEqual(t, FormatNumberInt(1000000, 3, ",", "."), "1,000,000.000") 108 | AssertEqual(t, FormatNumberInt(1000000, 10, " ", "."), "1 000 000.0000000000") 109 | AssertEqual(t, FormatNumberInt(1000000, 10, " ", "."), "1 000 000.0000000000") 110 | } 111 | 112 | func TestFormatNumberFloat64(t *testing.T) { 113 | AssertEqual(t, FormatNumberFloat64(123456789.213123, 3, ",", "."), "123,456,789.213") 114 | AssertEqual(t, FormatNumberFloat64(-12345.123123, 5, ",", "."), "-12,345.12312") 115 | AssertEqual(t, FormatNumberFloat64(-1234.123123, 5, ",", "."), "-1,234.12312") 116 | AssertEqual(t, FormatNumberFloat64(-123.123123, 5, ",", "."), "-123.12312") 117 | AssertEqual(t, FormatNumberFloat64(-12.123123, 5, ",", "."), "-12.12312") 118 | AssertEqual(t, FormatNumberFloat64(-1.123123, 5, ",", "."), "-1.12312") 119 | } 120 | 121 | func TestFormatNumberBigRat(t *testing.T) { 122 | AssertEqual(t, FormatNumberBigRat(big.NewRat(77777777, 3), 3, ",", "."), "25,925,925.667") 123 | AssertEqual(t, FormatNumberBigRat(big.NewRat(-77777777, 3), 3, ",", "."), "-25,925,925.667") 124 | AssertEqual(t, FormatNumberBigRat(big.NewRat(-7777777, 3), 3, ",", "."), "-2,592,592.333") 125 | AssertEqual(t, FormatNumberBigRat(big.NewRat(-777776, 3), 3, ",", "."), "-259,258.667") 126 | } 127 | 128 | func TestFormatNumberBigDecimal(t *testing.T) { 129 | AssertEqual(t, FormatNumberBigDecimal(apd.New(123456789213123, -6), 3, ",", "."), "123,456,789.213") 130 | AssertEqual(t, FormatNumberBigDecimal(apd.New(-12345123123, -6), 5, ",", "."), "-12,345.12312") 131 | AssertEqual(t, FormatNumberBigDecimal(apd.New(-1234123123, -6), 5, ",", "."), "-1,234.12312") 132 | AssertEqual(t, FormatNumberBigDecimal(apd.New(-123123123, -6), 5, ",", "."), "-123.12312") 133 | AssertEqual(t, FormatNumberBigDecimal(apd.New(-12123123, -6), 5, ",", "."), "-12.12312") 134 | AssertEqual(t, FormatNumberBigDecimal(apd.New(-1123123, -6), 5, ",", "."), "-1.12312") 135 | } 136 | 137 | func TestFormatNumberDecimal(t *testing.T) { 138 | AssertEqual(t, FormatNumberDecimal(decimal.New(123456789213123, -6), 3, ",", "."), "123,456,789.213") 139 | AssertEqual(t, FormatNumberDecimal(decimal.New(-12345123123, -6), 5, ",", "."), "-12,345.12312") 140 | AssertEqual(t, FormatNumberDecimal(decimal.New(-1234123123, -6), 5, ",", "."), "-1,234.12312") 141 | AssertEqual(t, FormatNumberDecimal(decimal.New(-123123123, -6), 5, ",", "."), "-123.12312") 142 | AssertEqual(t, FormatNumberDecimal(decimal.New(-12123123, -6), 5, ",", "."), "-12.12312") 143 | AssertEqual(t, FormatNumberDecimal(decimal.New(-1123123, -6), 5, ",", "."), "-1.12312") 144 | } 145 | -------------------------------------------------------------------------------- /accounting_test.go: -------------------------------------------------------------------------------- 1 | package accounting 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "runtime" 7 | "testing" 8 | 9 | "github.com/cockroachdb/apd" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | func AssertEqual(t *testing.T, x, y string) { 14 | if x != y { 15 | t.Error("Expected ", y, ", got ", x) 16 | } 17 | } 18 | 19 | func init() { 20 | fmt.Printf("version: %s", runtime.Version()) 21 | } 22 | 23 | func TestAccounting_SetFormat(t *testing.T) { 24 | accounting := DefaultAccounting("$", 2) 25 | accounting.SetFormat("%v %s") 26 | AssertEqual(t, accounting.FormatMoney(123456789.213123), "123,456,789.21 $") 27 | } 28 | 29 | func TestAccounting_SetFormatZero(t *testing.T) { 30 | accounting := DefaultAccounting("$", 2) 31 | accounting.SetFormatZero("%s --") 32 | AssertEqual(t, accounting.FormatMoney(0), "$ --") 33 | } 34 | 35 | func TestAccounting_SetFormatNegative(t *testing.T) { 36 | accounting := DefaultAccounting("$", 2) 37 | accounting.SetFormatNegative("%s(%v)") 38 | AssertEqual(t, accounting.FormatMoney(-123456789.213123), "$(123,456,789.21)") 39 | } 40 | 41 | func TestAccounting_SetThousandSeparator(t *testing.T) { 42 | accounting := DefaultAccounting("$", 2) 43 | accounting.SetThousandSeparator("'") 44 | AssertEqual(t, accounting.FormatMoney(123456789.213123), "$123'456'789.21") 45 | } 46 | 47 | func TestAccounting_SetDecimalSeparator(t *testing.T) { 48 | accounting := DefaultAccounting("$", 2) 49 | accounting.SetDecimalSeparator("'") 50 | AssertEqual(t, accounting.FormatMoney(123456789.213123), "$123,456,789'21") 51 | } 52 | 53 | func TestFormatMoney(t *testing.T) { 54 | accounting := DefaultAccounting("$", 2) 55 | AssertEqual(t, accounting.FormatMoney(123456789.213123), "$123,456,789.21") 56 | AssertEqual(t, accounting.FormatMoney(12345678), "$12,345,678.00") 57 | AssertEqual(t, accounting.FormatMoney(-12345678), "-$12,345,678.00") 58 | AssertEqual(t, accounting.FormatMoney(0), "$0.00") 59 | AssertEqual(t, accounting.FormatMoney(big.NewRat(77777777, 3)), "$25,925,925.67") 60 | AssertEqual(t, accounting.FormatMoney(big.NewRat(-77777777, 3)), "-$25,925,925.67") 61 | AssertEqual(t, accounting.FormatMoney(apd.New(499999, -2)), "$4,999.99") 62 | AssertEqual(t, accounting.FormatMoney(apd.New(500000, 0)), "$500,000.00") 63 | AssertEqual(t, accounting.FormatMoney(decimal.New(499999, -2)), "$4,999.99") 64 | AssertEqual(t, accounting.FormatMoney(decimal.New(500000, 0)), "$500,000.00") 65 | 66 | accounting = NewAccounting("$", 0, ",", ".", "%s %v", "-%s %v", "%s %v") 67 | AssertEqual(t, accounting.FormatMoney(123456789.213123), "$ 123,456,789") 68 | AssertEqual(t, accounting.FormatMoney(12345678), "$ 12,345,678") 69 | AssertEqual(t, accounting.FormatMoney(-12345678), "-$ 12,345,678") 70 | AssertEqual(t, accounting.FormatMoney(0), "$ 0") 71 | 72 | accounting2 := Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 73 | AssertEqual(t, accounting2.FormatMoney(4999.99), "€4.999,99") 74 | 75 | accounting2 = Accounting{Symbol: "£ ", Precision: 0} 76 | AssertEqual(t, accounting2.FormatMoney(500000), "£ 500,000") 77 | 78 | accounting2 = Accounting{Symbol: "GBP", Precision: 0, 79 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 80 | AssertEqual(t, accounting2.FormatMoney(1000000), "GBP 1,000,000") 81 | AssertEqual(t, accounting2.FormatMoney(-5000), "GBP (5,000)") 82 | AssertEqual(t, accounting2.FormatMoney(0), "GBP --") 83 | 84 | accounting2 = Accounting{Symbol: "GBP", Precision: 2, 85 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 86 | AssertEqual(t, accounting2.FormatMoney(1000000), "GBP 1,000,000.00") 87 | AssertEqual(t, accounting2.FormatMoney(-5000), "GBP (5,000.00)") 88 | AssertEqual(t, accounting2.FormatMoney(0), "GBP --") 89 | 90 | accounting2 = Accounting{Symbol: "€", Precision: 2, 91 | Decimal: ",", FormatZero: "0.-"} 92 | AssertEqual(t, accounting2.FormatMoney(0), "0.-") 93 | } 94 | 95 | func TestFormatMoneyInt(t *testing.T) { 96 | accounting := Accounting{Symbol: "$", Precision: 2} 97 | AssertEqual(t, accounting.FormatMoneyInt(12345678), "$12,345,678.00") 98 | 99 | accounting = Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 100 | AssertEqual(t, accounting.FormatMoneyInt(4999), "€4.999,00") 101 | 102 | accounting = Accounting{Symbol: "£ ", Precision: 0} 103 | AssertEqual(t, accounting.FormatMoneyInt(500000), "£ 500,000") 104 | 105 | accounting = Accounting{Symbol: "GBP", Precision: 0, 106 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 107 | AssertEqual(t, accounting.FormatMoneyInt(1000000), "GBP 1,000,000") 108 | AssertEqual(t, accounting.FormatMoneyInt(-5000), "GBP (5,000)") 109 | AssertEqual(t, accounting.FormatMoneyInt(0), "GBP --") 110 | } 111 | 112 | func TestFormatMoneyFloat64(t *testing.T) { 113 | accounting := Accounting{Symbol: "$", Precision: 2} 114 | AssertEqual(t, accounting.FormatMoneyFloat64(123456789.213123), "$123,456,789.21") 115 | 116 | accounting = Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 117 | AssertEqual(t, accounting.FormatMoneyFloat64(4999.99), "€4.999,99") 118 | 119 | accounting = Accounting{Symbol: "£ ", Precision: 0} 120 | AssertEqual(t, accounting.FormatMoneyFloat64(500000.0), "£ 500,000") 121 | 122 | accounting = Accounting{Symbol: "GBP", Precision: 0, 123 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 124 | AssertEqual(t, accounting.FormatMoneyFloat64(1000000.0), "GBP 1,000,000") 125 | AssertEqual(t, accounting.FormatMoneyFloat64(-5000.0), "GBP (5,000)") 126 | AssertEqual(t, accounting.FormatMoneyFloat64(0.0), "GBP --") 127 | } 128 | 129 | func TestFormatMoneyBigRat(t *testing.T) { 130 | accounting := Accounting{Symbol: "$", Precision: 2} 131 | AssertEqual(t, accounting.FormatMoneyBigRat(big.NewRat(77777777, 3)), "$25,925,925.67") 132 | AssertEqual(t, accounting.FormatMoneyBigRat(big.NewRat(-77777777, 3)), "-$25,925,925.67") 133 | 134 | accounting = Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 135 | AssertEqual(t, accounting.FormatMoneyBigRat(big.NewRat(77777777, 3)), "€25.925.925,67") 136 | 137 | accounting = Accounting{Symbol: "£ ", Precision: 0} 138 | AssertEqual(t, accounting.FormatMoneyBigRat(big.NewRat(77777777, 3)), "£ 25,925,926") 139 | 140 | accounting = Accounting{Symbol: "GBP", Precision: 0, 141 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 142 | AssertEqual(t, accounting.FormatMoneyBigRat(big.NewRat(77777777, 3)), "GBP 25,925,926") 143 | AssertEqual(t, accounting.FormatMoneyBigRat(big.NewRat(-77777777, 3)), "GBP (25,925,926)") 144 | AssertEqual(t, accounting.FormatMoneyBigRat(big.NewRat(0, 3)), "GBP --") 145 | } 146 | 147 | func TestFormatMoneyBigDecimal(t *testing.T) { 148 | accounting := Accounting{Symbol: "$", Precision: 2} 149 | AssertEqual(t, accounting.FormatMoneyBigDecimal(apd.New(123456789213123, -6)), "$123,456,789.21") 150 | 151 | accounting = Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 152 | AssertEqual(t, accounting.FormatMoneyBigDecimal(apd.New(499999, -2)), "€4.999,99") 153 | 154 | accounting = Accounting{Symbol: "£ ", Precision: 0} 155 | AssertEqual(t, accounting.FormatMoneyBigDecimal(apd.New(500000, 0)), "£ 500,000") 156 | 157 | accounting = Accounting{Symbol: "GBP", Precision: 0, 158 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 159 | AssertEqual(t, accounting.FormatMoneyBigDecimal(apd.New(1000000, 0)), "GBP 1,000,000") 160 | AssertEqual(t, accounting.FormatMoneyBigDecimal(apd.New(-5000, 0)), "GBP (5,000)") 161 | AssertEqual(t, accounting.FormatMoneyBigDecimal(apd.New(0, 0)), "GBP --") 162 | } 163 | 164 | func TestFormatMoneyDecimal(t *testing.T) { 165 | accounting := Accounting{Symbol: "$", Precision: 2} 166 | AssertEqual(t, accounting.FormatMoneyDecimal(decimal.New(123456789213123, -6)), "$123,456,789.21") 167 | 168 | accounting = Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 169 | AssertEqual(t, accounting.FormatMoneyDecimal(decimal.New(499999, -2)), "€4.999,99") 170 | 171 | accounting = Accounting{Symbol: "£ ", Precision: 0} 172 | AssertEqual(t, accounting.FormatMoneyDecimal(decimal.New(500000, 0)), "£ 500,000") 173 | 174 | accounting = Accounting{Symbol: "GBP", Precision: 0, 175 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 176 | AssertEqual(t, accounting.FormatMoneyDecimal(decimal.New(1000000, 0)), "GBP 1,000,000") 177 | AssertEqual(t, accounting.FormatMoneyDecimal(decimal.New(-5000, 0)), "GBP (5,000)") 178 | AssertEqual(t, accounting.FormatMoneyDecimal(decimal.New(0, 0)), "GBP --") 179 | } 180 | -------------------------------------------------------------------------------- /locale.go: -------------------------------------------------------------------------------- 1 | package accounting 2 | 3 | type Locale struct { 4 | Name string // currency name 5 | FractionLength int // default decimal length 6 | ThouSep string // thousands seperator 7 | DecSep string // decimal seperator 8 | SpaceSep string // space seperator 9 | UTFSymbol string // UTF symbol 10 | HTMLSymbol string // HTML symbol 11 | ComSymbol string // Common symbol 12 | Pre bool // symbol before or after currency 13 | } 14 | 15 | const empty string = "" 16 | 17 | var LocaleInfo map[string]Locale = map[string]Locale{ 18 | "AED": Locale{"UAE Dirham", 2, ",", ".", " ", empty, empty, "Dhs.", true}, 19 | "AFA": Locale{"Afghani", 0, empty, empty, "060B", "؋", empty, "؋", true}, 20 | "ALL": Locale{"Lek", 2, empty, empty, "", empty, empty, "Lek", true}, 21 | "AMD": Locale{"Armenian Dram", 2, ",", ".", "", empty, empty, "֏", false}, 22 | "ANG": Locale{"Antillian Guilder", 2, ".", ",", " ", "0192", "ƒ", "ƒ", true}, 23 | "AOA": Locale{"New Kwanza", 0, empty, empty, "", empty, empty, "Kz", true}, 24 | "ARS": Locale{"Argentine Peso", 2, ".", ",", "", "20B1", "₱", "$", true}, 25 | "ATS": Locale{"Schilling", 2, ".", ",", " ", empty, empty, "öS", true}, 26 | "AUD": Locale{"Australian Dollar", 2, " ", ".", "", "0024", "$", "$", true}, 27 | "AWG": Locale{"Aruban Guilder", 2, ",", ".", " ", "0192", "ƒ", "ƒ", true}, 28 | "AZN": Locale{"Azerbaijanian Manat", 2, empty, empty, "", empty, empty, "₼", true}, 29 | "BAM": Locale{"Convertible Marks", 2, ",", ".", "", empty, empty, "KM", false}, 30 | "BBD": Locale{"Barbados Dollar", 2, empty, empty, "", "0024", "$", "Bds$", true}, 31 | "BDT": Locale{"Taka", 2, ",", ".", " ", empty, empty, "Tk", true}, 32 | "BEF": Locale{"Belgian Franc", 0, ".", "", " ", "20A3", "₣", "BEF", true}, 33 | "BGN": Locale{"Lev", 2, " ", ",", " ", empty, empty, "лв", false}, 34 | "BHD": Locale{"Bahraini Dinar", 3, ",", ".", " ", empty, empty, "د.ب", true}, 35 | "BIF": Locale{"Burundi Franc", 0, empty, empty, "", empty, empty, "FBu", true}, 36 | "BMD": Locale{"Bermudian Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 37 | "BND": Locale{"Brunei Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 38 | "BOB": Locale{"Bolivian Boliviano", 2, ",", ".", "", empty, empty, "$b", true}, 39 | "BRL": Locale{"Brazilian Real", 2, ".", ",", " ", "0052 0024", "R$", "R$", true}, 40 | "BSD": Locale{"Bahamian Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 41 | "BTN": Locale{"Bhutan Ngultrum", 2, empty, empty, "", empty, empty, "BTN", true}, 42 | "BWP": Locale{"Pula", 2, ",", ".", "", empty, empty, "P", true}, 43 | "BYR": Locale{"Belarussian Ruble", 0, empty, empty, "", empty, empty, "p.", true}, 44 | "BZD": Locale{"Belize Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 45 | "CAD": Locale{"Canadian Dollar", 2, ",", ".", "", "0024", "$", "CA$", true}, 46 | "CDF": Locale{"Franc Congolais", 2, empty, empty, "", empty, empty, "FC", true}, 47 | "CHF": Locale{"Swiss Franc", 2, "'", ".", " ", empty, empty, "CHF", true}, 48 | "CLP": Locale{"Chilean Peso", 0, ".", "", "", "20B1", "₱", "$", true}, 49 | "CNY": Locale{"Yuan Renminbi", 2, ",", ".", "", "5713", "圓", "¥", true}, 50 | "COP": Locale{"Colombian Peso", 2, ".", ",", "", "20B1", "₱", "$", true}, 51 | "CRC": Locale{"Costa Rican Colon", 2, ".", ",", " ", "20A1", "₡", "₡", true}, 52 | "CUP": Locale{"Cuban Peso", 2, ",", ".", " ", "20B1", "₱", "$", true}, 53 | "CVE": Locale{"Cape Verde Escudo", 0, empty, empty, "", empty, empty, "$", true}, 54 | "CYP": Locale{"Cyprus Pound", 2, ".", ",", "", "00A3", "£", "£", true}, 55 | "CZK": Locale{"Czech Koruna", 2, ".", ",", " ", empty, empty, "Kč", false}, 56 | "DEM": Locale{"Deutsche Mark", 2, ".", ",", "", empty, empty, "DM", false}, 57 | "DJF": Locale{"Djibouti Franc", 0, empty, empty, "", empty, empty, "DJF", true}, 58 | "DKK": Locale{"Danish Krone", 2, ".", ",", "", empty, empty, "kr.", true}, 59 | "DOP": Locale{"Dominican Peso", 2, ",", ".", " ", "20B1", "₱", "$", true}, 60 | "DZD": Locale{"Algerian Dinar", 2, empty, empty, "", empty, empty, "DA", true}, 61 | "ECS": Locale{"Sucre", 0, empty, empty, "", empty, empty, "S.", true}, 62 | "EEK": Locale{"Kroon", 2, " ", ",", " ", empty, empty, "kr", false}, 63 | "EGP": Locale{"Egyptian Pound", 2, ",", ".", " ", "00A3", "£", "£", true}, 64 | "ERN": Locale{"Nakfa", 0, empty, empty, "", empty, empty, "NKf", true}, 65 | "ESP": Locale{"Spanish Peseta", 0, ".", "", " ", "20A7", "₧", "Ptas", false}, 66 | "ETB": Locale{"Ethiopian Birr", 0, empty, empty, "", empty, empty, "BR", true}, 67 | "EUR": Locale{"Euro", 2, ".", ",", "", "20AC", "€", "€", true}, 68 | "FIM": Locale{"Markka", 2, " ", ",", " ", empty, empty, "mk", false}, 69 | "FJD": Locale{"Fiji Dollar", 0, empty, empty, "", "0024", "$", "FJ$", true}, 70 | "FKP": Locale{"Pound", 0, empty, empty, "", "00A3", "£", "£", true}, 71 | "FRF": Locale{"French Franc", 2, " ", ",", " ", "20A3", "₣", "Fr", false}, 72 | "GBP": Locale{"Pound Sterling", 2, ",", ".", "", "00A3", "£", "£", true}, 73 | "GEL": Locale{"Lari", 0, empty, empty, "", empty, empty, "GEL", true}, 74 | "GHS": Locale{"Cedi", 2, ",", ".", "", "20B5", "₵", "₵", true}, 75 | "GIP": Locale{"Gibraltar Pound", 2, ",", ".", "", "00A3", "£", "£", true}, 76 | "GMD": Locale{"Dalasi", 0, empty, empty, "", empty, empty, "GMD", true}, 77 | "GNF": Locale{"Guinea Franc", 0, empty, empty, empty, empty, empty, "FG", true}, 78 | "GRD": Locale{"Drachma", 2, ".", ",", " ", "20AF", "₯", "GRD", false}, 79 | "GTQ": Locale{"Quetzal", 2, ",", ".", "", empty, empty, "Q.", true}, 80 | "GWP": Locale{"Guinea-Bissau Peso", 0, empty, empty, empty, empty, empty, "GWP", true}, 81 | "GYD": Locale{"Guyana Dollar", 0, empty, empty, "", "0024", "$", "$", true}, 82 | "HKD": Locale{"Hong Kong Dollar", 2, ",", ".", "", "0024", "$", "HK$", true}, 83 | "HNL": Locale{"Lempira", 2, ",", ".", " ", empty, empty, "L", true}, 84 | "HRK": Locale{"Kuna", 2, ".", ",", " ", empty, empty, "kn", false}, 85 | "HTG": Locale{"Gourde", 0, empty, empty, "", empty, empty, "G", true}, 86 | "HUF": Locale{"Forint", 0, ".", "", " ", empty, empty, "Ft", false}, 87 | "IDR": Locale{"Rupiah", 0, ".", ",", "", empty, empty, "Rp", true}, 88 | "IEP": Locale{"Irish Pound", 2, ",", ".", "", "00A3", "£", "£", true}, 89 | "ILS": Locale{"New Israeli Sheqel", 2, ",", ".", " ", "20AA", "₪", "₪", false}, 90 | "INR": Locale{"Indian Rupee", 2, ",", ".", "", "20A8", "₨", "₹", true}, 91 | "IQD": Locale{"Iraqi Dinar", 3, empty, empty, "", empty, empty, "د.ع", true}, 92 | "IRR": Locale{"Iranian Rial", 2, ",", ".", " ", "FDFC", "﷼", "﷼", true}, 93 | "ISK": Locale{"Iceland Krona", 2, ".", ",", " ", empty, empty, "kr", false}, 94 | "ITL": Locale{"Italian Lira", 0, ".", "", " ", "20A4", "₤", "L.", true}, 95 | "JMD": Locale{"Jamaican Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 96 | "JOD": Locale{"Jordanian Dinar", 3, ",", ".", " ", empty, empty, "JD", true}, 97 | "JPY": Locale{"Yen", 0, ",", "", "", "00A5", "¥", "¥", true}, 98 | "KES": Locale{"Kenyan Shilling", 2, ",", ".", "", empty, empty, "Ksh", true}, 99 | "KGS": Locale{"Som", 0, empty, empty, "", empty, empty, "лв", true}, 100 | "KHR": Locale{"Riel", 2, empty, empty, "", "17DB", "៛", "៛", true}, 101 | "KMF": Locale{"Comoro Franc", 0, empty, empty, "", empty, empty, "KMF", true}, 102 | "KPW": Locale{"North Korean Won", 0, empty, empty, "", "20A9", "₩", "₩", true}, 103 | "KRW": Locale{"Won", 0, ",", "", "", "20A9", "₩", "₩", true}, 104 | "KWD": Locale{"Kuwaiti Dinar", 3, ",", ".", " ", empty, empty, "ك", true}, 105 | "KYD": Locale{"Cayman Islands Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 106 | "KZT": Locale{"Tenge", 0, empty, empty, "", empty, empty, "₸", true}, 107 | "LAK": Locale{"Kip", 0, empty, empty, "", "20AD", "₭", "₭", true}, 108 | "LBP": Locale{"Lebanese Pound", 0, " ", "", "", "00A3", "£", "ل.ل", false}, 109 | "LKR": Locale{"Sri Lanka Rupee", 0, empty, empty, "", "0BF9", "௹", "₨", true}, 110 | "LRD": Locale{"Liberian Dollar", 0, empty, empty, "", "0024", "$", "$", true}, 111 | "LSL": Locale{"Lesotho Maloti", 0, empty, empty, "", empty, empty, "LSL", true}, 112 | "LTL": Locale{"Lithuanian Litas", 2, " ", ",", " ", empty, empty, "Lt", false}, 113 | "LUF": Locale{"Luxembourg Franc", 0, "'", "", " ", "20A3", "₣", "F", false}, 114 | "LVL": Locale{"Latvian Lats", 2, ",", ".", " ", empty, empty, "Ls", true}, 115 | "LYD": Locale{"Libyan Dinar", 0, empty, empty, "", empty, empty, "LD", true}, 116 | "MAD": Locale{"Moroccan Dirham", 0, empty, empty, "", empty, empty, "MAD", true}, 117 | "MDL": Locale{"Moldovan Leu", 0, empty, empty, "", empty, empty, "MDL", true}, 118 | "MGF": Locale{"Malagasy Franc", 0, empty, empty, "", empty, empty, "MF", true}, 119 | "MKD": Locale{"Denar", 2, ",", ".", " ", empty, empty, "ден", false}, 120 | "MMK": Locale{"Kyat", 0, empty, empty, "", empty, empty, "K", true}, 121 | "MNT": Locale{"Tugrik", 0, empty, empty, "", "20AE", "₮", "₮", true}, 122 | "MOP": Locale{"Pataca", 0, empty, empty, "", empty, empty, "MOP$", true}, 123 | "MRO": Locale{"Ouguiya", 0, empty, empty, "", empty, empty, "MRO", true}, 124 | "MTL": Locale{"Maltese Lira", 2, ",", ".", "", "20A4", "₤", "Lm", true}, 125 | "MUR": Locale{"Mauritius Rupee", 0, ",", "", "", "20A8", "₨", "Rs", true}, 126 | "MVR": Locale{"Rufiyaa", 0, empty, empty, "", empty, empty, "MVR", true}, 127 | "MWK": Locale{"Kwacha", 2, ",", ".", "", empty, empty, "MK", true}, 128 | "MXN": Locale{"Mexican Peso", 2, ",", ".", " ", "0024", "$", "$", true}, 129 | "MYR": Locale{"Malaysian Ringgit", 2, ",", ".", "", empty, empty, "RM", true}, 130 | "MZN": Locale{"Metical", 2, ".", ",", " ", empty, empty, "Mt", false}, 131 | "NAD": Locale{"Namibian Dollar", 0, empty, empty, "", "0024", "$", "$", true}, 132 | "NGN": Locale{"Naira", 2, ",", ".", ".", "20A6", "₦", "₦", true}, 133 | "NIO": Locale{"Cordoba Oro", 0, empty, empty, "", empty, empty, "C$", true}, 134 | "NLG": Locale{"Netherlands Guilder", 2, ".", ",", " ", "0192", "ƒ", "ƒ", true}, 135 | "NOK": Locale{"Norwegian Krone", 2, ".", ",", " ", "kr", "kr", "kr", true}, 136 | "NPR": Locale{"Nepalese Rupee", 2, ",", ".", " ", "20A8", "₨", "Rs.", true}, 137 | "NZD": Locale{"New Zealand Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 138 | "OMR": Locale{"Rial Omani", 3, ",", ".", " ", "FDFC", "﷼", "RO", true}, 139 | "PAB": Locale{"Balboa", 0, empty, empty, "", empty, empty, "B/.", true}, 140 | "PEN": Locale{"Nuevo Sol", 2, ",", ".", " ", "S/.", "S/.", "S/.", true}, 141 | "PGK": Locale{"Kina", 0, empty, empty, "", empty, empty, "K", true}, 142 | "PHP": Locale{"Philippine Peso", 2, ",", ".", "", "20B1", "₱", "₱", true}, 143 | "PKR": Locale{"Pakistan Rupee", 2, ",", ".", "", "20A8", "₨", "Rs", true}, 144 | "PLN": Locale{"Zloty", 2, " ", ",", " ", empty, empty, "zł", false}, 145 | "PTE": Locale{"Portuguese Escudo", 0, ".", "", " ", empty, empty, "$", false}, 146 | "PYG": Locale{"Guarani", 0, empty, empty, "", "20B2", "₲", "Gs", true}, 147 | "QAR": Locale{"Qatari Rial", 0, empty, empty, "", "FDFC", "﷼", "﷼", true}, 148 | "RON": Locale{"Leu", 2, ".", ",", " ", empty, empty, "lei", false}, 149 | "RSD": Locale{"Serbian Dinar", 2, empty, empty, empty, empty, empty, "РСД", false}, 150 | "RUB": Locale{"Russian Ruble", 2, ".", ",", empty, "0440 0443 0431", "руб", "₽", true}, 151 | "RWF": Locale{"Rwanda Franc", 0, empty, empty, "", empty, empty, "RWF", true}, 152 | "SAC": Locale{"S. African Rand Commerc.", 0, empty, empty, "", empty, empty, "SAC", true}, 153 | "SAR": Locale{"Saudi Riyal", 2, ",", ".", " ", "FDFC", "﷼", "﷼", true}, 154 | "SBD": Locale{"Solomon Islands Dollar", 0, empty, empty, "", "0024", "$", "$", true}, 155 | "SCR": Locale{"Seychelles Rupee", 0, empty, empty, "", "20A8", "₨", "₨", true}, 156 | "SDG": Locale{"Sudanese Dinar", 0, empty, empty, empty, empty, empty, "LSd", true}, 157 | "SDP": Locale{"Sudanese Pound", 0, empty, empty, "", empty, empty, "SDP", true}, 158 | "SEK": Locale{"Swedish Krona", 2, " ", ",", " ", empty, empty, "kr", false}, 159 | "SGD": Locale{"Singapore Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 160 | "SHP": Locale{"St Helena Pound", 0, empty, empty, "", "00A3", "£", "£", true}, 161 | "SIT": Locale{"Tolar", 2, ".", ",", " ", empty, empty, "SIT", false}, 162 | "SKK": Locale{"Slovak Koruna", 2, " ", ",", " ", empty, empty, "Sk", false}, 163 | "SLL": Locale{"Leone", 0, empty, empty, "", empty, empty, "Le", true}, 164 | "SOS": Locale{"Somali Shilling", 0, empty, empty, "", empty, empty, "S", true}, 165 | "SRG": Locale{"Surinam Guilder", 0, empty, empty, "", empty, empty, "SRG", true}, 166 | "STD": Locale{"Dobra", 0, empty, empty, "", empty, empty, "DB", true}, 167 | "SVC": Locale{"El Salvador Colon", 2, ",", ".", "", "20A1", "₡", "¢", true}, 168 | "SYP": Locale{"Syrian Pound", 0, empty, empty, "", "00A3", "£", "£", true}, 169 | "SZL": Locale{"Lilangeni", 2, "", ".", "", empty, empty, "E", true}, 170 | "THB": Locale{"Baht", 2, ",", ".", " ", "0E3F", "฿", "Bt", false}, 171 | "TJR": Locale{"Tajik Ruble", 0, empty, empty, "", empty, empty, "TJR", true}, 172 | "TJS": Locale{"Somoni", 0, empty, empty, empty, empty, empty, "TJS", true}, 173 | "TMM": Locale{"Manat", 0, empty, empty, "", empty, empty, "T", true}, 174 | "TND": Locale{"Tunisian Dinar", 3, empty, empty, "", empty, empty, "TND", true}, 175 | "TOP": Locale{"Pa'anga", 2, ",", ".", " ", empty, empty, "$", true}, 176 | "TPE": Locale{"Timor Escudo", 0, empty, empty, empty, empty, empty, "TPE", true}, 177 | "TRY": Locale{"Turkish Lira", 0, ",", "", "", "20A4", "₤", "TL", false}, 178 | "TTD": Locale{"Trinidad and Tobago Dollar", 0, empty, empty, "", "0024", "$", "TT$", true}, 179 | "TWD": Locale{"New Taiwan Dollar", 0, empty, empty, "", "0024", "$", "NT$", true}, 180 | "TZS": Locale{"Tanzanian Shilling", 2, ",", ".", " ", empty, empty, "TZs", false}, 181 | "UAH": Locale{"Hryvnia", 2, " ", ",", "", "20B4", "₴", "UAH", false}, 182 | "UGX": Locale{"Uganda Shilling", 0, empty, empty, "", empty, empty, "UGX", true}, 183 | "USD": Locale{"US Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 184 | "UYU": Locale{"Peso Uruguayo", 2, ".", ",", "", "20B1", "₱", "$", true}, 185 | "UZS": Locale{"Uzbekistan Sum", 0, empty, empty, "", empty, empty, "лв", true}, 186 | "VEF": Locale{"Bolivar", 2, ".", ",", " ", empty, empty, "Bs.", true}, 187 | "VND": Locale{"Dong", 2, ".", ",", " ", "20AB", "₫", "₫", true}, 188 | "VUV": Locale{"Vatu", 0, ",", "", "", empty, empty, "VT", false}, 189 | "WST": Locale{"Tala", 0, empty, empty, "", empty, empty, "WST", true}, 190 | "XAF": Locale{"CFA Franc BEAC", 0, empty, empty, "", empty, empty, "$", true}, 191 | "XCD": Locale{"East Caribbean Dollar", 2, ",", ".", "", "0024", "$", "$", true}, 192 | "XOF": Locale{"CFA Franc BCEAO", 0, empty, empty, empty, empty, empty, "XOF", true}, 193 | "XPF": Locale{"CFP Franc", 0, empty, empty, "", empty, empty, "XPF", true}, 194 | "YER": Locale{"Yemeni Rial", 0, empty, empty, "", "FDFC", "﷼", "﷼", true}, 195 | "YUN": Locale{"New Dinar", 0, empty, empty, "", empty, empty, "YUN", true}, 196 | "ZAR": Locale{"Rand", 2, " ", ".", " ", "0052", "R", "R", true}, 197 | "ZMK": Locale{"Kwacha", 0, empty, empty, "", empty, empty, "ZMK", true}, 198 | "ZRN": Locale{"New Zaire", 0, empty, empty, empty, empty, empty, "ZRN", true}, 199 | "ZWD": Locale{"Zimbabwe Dollar ", 2, " ", ".", "", "0024", "$", "Z$", true}, 200 | } 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # accounting - money and currency formatting for golang 2 | [![Build Status](https://travis-ci.org/leekchan/accounting.svg?branch=master)](https://travis-ci.org/leekchan/accounting) 3 | [![Coverage Status](https://coveralls.io/repos/leekchan/accounting/badge.svg?branch=master&service=github)](https://coveralls.io/github/leekchan/accounting?branch=master) 4 | [![GoDoc](https://godoc.org/github.com/leekchan/accounting?status.svg)](https://godoc.org/github.com/leekchan/accounting) 5 | 6 | accounting is a library for money and currency formatting. (inspired by [accounting.js](https://github.com/openexchangerates/accounting.js)) 7 | 8 | 9 | ## Quick Start 10 | 11 | ``` 12 | go get github.com/leekchan/accounting 13 | ``` 14 | 15 | example.go 16 | 17 | ```Go 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "math/big" 23 | 24 | "github.com/shopspring/decimal" 25 | "github.com/leekchan/accounting" 26 | ) 27 | 28 | func main() { 29 | ac := accounting.Accounting{Symbol: "$", Precision: 2} 30 | fmt.Println(ac.FormatMoney(123456789.213123)) // "$123,456,789.21" 31 | fmt.Println(ac.FormatMoney(12345678)) // "$12,345,678.00" 32 | fmt.Println(ac.FormatMoney(big.NewRat(77777777, 3))) // "$25,925,925.67" 33 | fmt.Println(ac.FormatMoney(big.NewRat(-77777777, 3))) // "-$25,925,925.67" 34 | fmt.Println(ac.FormatMoneyBigFloat(big.NewFloat(123456789.213123))) // "$123,456,789.21" 35 | fmt.Println(ac.FormatMoneyDecimal(decimal.New(123456789.213123, 0))) // "$123,456,789.21" 36 | 37 | ac = accounting.Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 38 | fmt.Println(ac.FormatMoney(4999.99)) // "€4.999,99" 39 | 40 | // Or retrieve currency info from Locale struct 41 | lc := LocaleInfo["USD"] 42 | ac = accounting.Accounting{Symbol: lc.ComSymbol, Precision: 2, Thousand: lc.ThouSep, Decimal: lc.DecSep} 43 | fmt.Println(ac.FormatMoney(500000)) // "$500,000.00" 44 | 45 | ac = accounting.Accounting{Symbol: "£ ", Precision: 0} 46 | fmt.Println(ac.FormatMoney(500000)) // "£ 500,000" 47 | 48 | ac = accounting.Accounting{Symbol: "GBP", Precision: 0, 49 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 50 | fmt.Println(ac.FormatMoney(1000000)) // "GBP 1,000,000" 51 | fmt.Println(ac.FormatMoney(-5000)) // "GBP (5,000)" 52 | fmt.Println(ac.FormatMoney(0)) // "GBP --" 53 | 54 | ac = accounting.DefaultAccounting("GBP", 2) 55 | fmt.Println(ac.FormatMoney(1000000)) // "GBP 1,000,000" 56 | fmt.Println(ac.FormatMoney(-5000)) // "GBP (5,000)" 57 | fmt.Println(ac.FormatMoney(0)) // "GBP --" 58 | 59 | ac = accounting.NewAccounting("GBP", 2, ",", ".", "%s %v", "%s (%v)", "%s --") 60 | fmt.Println(ac.FormatMoney(1000000)) // "GBP 1,000,000" 61 | fmt.Println(ac.FormatMoney(-5000)) // "GBP (5,000)" 62 | fmt.Println(ac.FormatMoney(0)) // "GBP --" 63 | } 64 | ``` 65 | 66 | ## Caution 67 | 68 | Please do not use float64 to count money. Floats can have errors when you perform operations on them. Using [big.Rat](https://golang.org/pkg/math/big/#Rat) (< Go 1.5) or [big.Float](https://golang.org/pkg/math/big/#Float) (>= Go 1.5) is highly recommended. 69 | (accounting supports float64, but it is just for convenience.) 70 | 71 | * [FormatMoneyBigFloat(value *big.Float) string](#formatmoneybigfloatvalue-bigfloat-string) 72 | * [FormatMoneyBigRat(value *big.Rat) string](#formatmoneybigratvalue-bigrat-string) 73 | 74 | ## Initialization 75 | 76 | ### Accounting struct 77 | 78 | ```Go 79 | type Accounting struct { 80 | Symbol string // currency symbol (required) 81 | Precision int // currency precision (decimal places) (optional / default: 0) 82 | Thousand string // thousand separator (optional / default: ,) 83 | Decimal string // decimal separator (optional / default: .) 84 | Format string // simple format string allows control of symbol position (%v = value, %s = symbol) (default: %s%v) 85 | FormatNegative string // format string for negative values (optional / default: strings.Replace(strings.Replace(accounting.Format, "-", "", -1), "%v", "-%v", -1)) 86 | FormatZero string // format string for zero values (optional / default: Format) 87 | } 88 | ``` 89 | 90 | Field | Type | Description | Default | Example 91 | -------------| ------------- | ------------- | ------------- | ------------- 92 | Symbol | string | currency symbol | no default value | $ 93 | Precision | int | currency precision (decimal places) | 0 | 2 94 | Thousand | string | thousand separator | , | . 95 | Decimal | string | decimal separator | . | , 96 | Format | string | simple format string allows control of symbol position (%v = value, %s = symbol) | %s%v | %s %v 97 | FormatNegative | string | format string for negative values | strings.Replace(strings.Replace(accounting.Format, "-", "", -1), "%v", "-%v", -1)) | %s (%v) 98 | FormatZero | string | format string for zero values | Format | %s -- 99 | 100 | **Examples:** 101 | 102 | ```Go 103 | # Via functions 104 | ac := accounting.DefaultAccounting("$", 2) 105 | ac := accounting.NewAccounting("$", 2, ",", ".", "%s %v", "%s (%v)", "%s --") 106 | 107 | # Via Accounting struct 108 | ac := accounting.Accounting{Symbol: "$", Precision: 2} 109 | 110 | ac = accounting.Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 111 | 112 | ac = accounting.Accounting{Symbol: "GBP", Precision: 0, 113 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 114 | ``` 115 | 116 | ## SetThousandSeparator(str string) 117 | 118 | SetThousandSeparator sets the separator for the thousands separation 119 | 120 | ## SetDecimalSeparator(str string) 121 | 122 | SetDecimalSeparator sets the separator for the decimal separation 123 | 124 | ## SetFormat(str string) 125 | 126 | SetFormat sets the Format default: `%s%v` (%s=Symbol;%v=Value) 127 | 128 | ## SetFormatNegative(str string) 129 | 130 | SetFormatNegative sets the Format for negative values default: `-%s%v` (%s=Symbol;%v=Value) 131 | 132 | ## SetFormatZero(str string) 133 | 134 | SetFormatZero sets the Format for zero values default: `%s%v` (%s=Symbol;%v=Value) 135 | 136 | ### Locale struct 137 | 138 | ```Go 139 | type Locale struct { 140 | Name string // currency name 141 | FractionLength int // default decimal length 142 | ThouSep string // thousands seperator 143 | DecSep string // decimal seperator 144 | SpaceSep string // space seperator 145 | UTFSymbol string // UTF symbol 146 | HTMLSymbol string // HTML symbol 147 | ComSymbol string // Common symbol 148 | Pre bool // symbol before or after currency 149 | } 150 | ``` 151 | 152 | Field | Type | Description | Default | Example 153 | -------------| ------------- | ------------- | ------------- | ------------- 154 | Name | string | currency name | no default value | US Dollar 155 | FractionLength | int | default precision (decimal places) | no default value | 2 156 | ThouSep | string | thousand separator | no default value | , 157 | DecSep | string | decimal separator | no default value | . 158 | SpaceSep | string | space separator | no default value | " " 159 | UTFSymbol | string | UTF symbol | no default value | "0024" 160 | HTMLSymbol | string | HTML symbol | no default value | "$" 161 | ComSymbol | string | Common symbol | no default value | "$" 162 | Pre | bool | symbol before currency | no default value | true 163 | 164 | **Example:** 165 | 166 | ```Go 167 | // LocaleInfo map[string]Locale 168 | 169 | var lc Locale 170 | if val, ok := LocaleInfo["USD"]; ok { 171 | lc = val 172 | } else { 173 | panic("No Locale Info Found") 174 | } 175 | 176 | fmt.Println(lc.Name) // "US Dollar" 177 | fmt.Println(lc.FractionLength) // 2 178 | fmt.Println(lc.ThouSep) // "," 179 | fmt.Println(lc.DecSep) // "." 180 | fmt.Println(lc.SpaceSep) // "" 181 | fmt.Println(lc.UTFSymbol) // "0024" 182 | fmt.Println(lc.HTMLSymbol) // "$" 183 | fmt.Println(lc.ComSymbol) // "$" 184 | fmt.Println(lc.Pre) // true 185 | ``` 186 | There are currently 181 currencies supported in LocaleInfo 187 | 188 | ## FormatMoney(value interface{}) string 189 | 190 | FormatMoney is a function for formatting numbers as money values, with customisable currency symbol, precision (decimal places), and thousand/decimal separators. 191 | 192 | FormatMoney supports various types of value by runtime reflection. If you don't need runtime type evaluation, please refer to FormatMoneyInt, FormatMoneyBigRat, FormatMoneyBigRat, or FormatMoneyFloat64. 193 | 194 | * supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, *big.Rat 195 | 196 | **Examples:** 197 | 198 | ```Go 199 | ac := accounting.Accounting{Symbol: "$", Precision: 2} 200 | fmt.Println(ac.FormatMoney(123456789.213123)) // "$123,456,789.21" 201 | fmt.Println(ac.FormatMoney(12345678)) // "$12,345,678.00" 202 | fmt.Println(ac.FormatMoney(big.NewRat(77777777, 3))) // "$25,925,925.67" 203 | fmt.Println(ac.FormatMoney(big.NewRat(-77777777, 3))) // "-$25,925,925.67" 204 | 205 | ac = accounting.Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 206 | fmt.Println(ac.FormatMoney(4999.99)) // "€4.999,99" 207 | 208 | ac = accounting.Accounting{Symbol: "£ ", Precision: 0} 209 | fmt.Println(ac.FormatMoney(500000)) // "£ 500,000" 210 | 211 | ac = accounting.Accounting{Symbol: "GBP", Precision: 0, 212 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 213 | fmt.Println(ac.FormatMoney(1000000)) // "GBP 1,000,000" 214 | fmt.Println(ac.FormatMoney(-5000)) // "GBP (5,000)" 215 | fmt.Println(ac.FormatMoney(0)) // "GBP --" 216 | ``` 217 | 218 | ## FormatMoneyBigFloat(value *big.Float) string 219 | 220 | **(>= Go 1.5)** 221 | 222 | FormatMoneyBigFloat only supports [*big.Float](https://golang.org/pkg/math/big/#Float) value. It is faster than FormatMoney, because it does not do any runtime type evaluation. 223 | 224 | **Examples:** 225 | 226 | ```Go 227 | ac = accounting.Accounting{Symbol: "$", Precision: 2} 228 | fmt.Println(ac.FormatMoneyBigFloat(big.NewFloat(123456789.213123))) // "$123,456,789.21" 229 | fmt.Println(ac.FormatMoneyBigFloat(big.NewFloat(12345678))) // "$12,345,678.00" 230 | 231 | ac = accounting.Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 232 | fmt.Println(ac.FormatMoneyBigFloat(big.NewFloat(4999.99))) // "€4.999,99" 233 | 234 | ac = accounting.Accounting{Symbol: "£ ", Precision: 0} 235 | fmt.Println(ac.FormatMoneyBigFloat(big.NewFloat(500000))) // "£ 500,000" 236 | 237 | ac = accounting.Accounting{Symbol: "GBP", Precision: 0, 238 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 239 | fmt.Println(ac.FormatMoneyBigFloat(big.NewFloat(1000000))) // "GBP 1,000,000" 240 | fmt.Println(ac.FormatMoneyBigFloat(big.NewFloat(-5000))) // "GBP (5,000)" 241 | fmt.Println(ac.FormatMoneyBigFloat(big.NewFloat(0))) // "GBP --" 242 | ``` 243 | 244 | ## FormatMoneyInt(value int) string 245 | 246 | FormatMoneyInt only supports int value. It is faster than FormatMoney, because it does not do any runtime type evaluation. 247 | 248 | **Examples:** 249 | 250 | ```Go 251 | ac = accounting.Accounting{Symbol: "$", Precision: 2} 252 | fmt.Println(ac.FormatMoneyInt(12345678)) // "$12,345,678.00" 253 | 254 | ac = accounting.Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 255 | fmt.Println(ac.FormatMoneyInt(4999)) // "€4.999,00" 256 | 257 | ac = accounting.Accounting{Symbol: "£ ", Precision: 0} 258 | fmt.Println(ac.FormatMoneyInt(500000)) // "£ 500,000" 259 | 260 | ac = accounting.Accounting{Symbol: "GBP", Precision: 0, 261 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 262 | fmt.Println(ac.FormatMoneyInt(1000000)) // "GBP 1,000,000" 263 | fmt.Println(ac.FormatMoneyInt(-5000)) // "GBP (5,000)" 264 | fmt.Println(ac.FormatMoneyInt(0)) // "GBP --" 265 | ``` 266 | 267 | ## FormatMoneyBigRat(value *big.Rat) string 268 | 269 | FormatMoneyBigRat only supports [*big.Rat](https://golang.org/pkg/math/big/#Rat) value. It is faster than FormatMoney, because it does not do any runtime type evaluation. 270 | 271 | **Examples:** 272 | 273 | ```Go 274 | ac = accounting.Accounting{Symbol: "$", Precision: 2} 275 | fmt.Println(ac.FormatMoneyBigRat(big.NewRat(77777777, 3))) // "$25,925,925.67" 276 | fmt.Println(ac.FormatMoneyBigRat(big.NewRat(-77777777, 3))) // "-$25,925,925.67" 277 | 278 | ac = accounting.Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 279 | fmt.Println(ac.FormatMoneyBigRat(big.NewRat(77777777, 3))) // "€25.925.925,67" 280 | 281 | ac = accounting.Accounting{Symbol: "£ ", Precision: 0} 282 | fmt.Println(ac.FormatMoneyBigRat(big.NewRat(77777777, 3))) // "£ 25,925,926" 283 | 284 | ac = accounting.Accounting{Symbol: "GBP", Precision: 0, 285 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 286 | fmt.Println(ac.FormatMoneyBigRat(big.NewRat(77777777, 3))) // "GBP 25,925,926" 287 | fmt.Println(ac.FormatMoneyBigRat(big.NewRat(-77777777, 3))) // "GBP (25,925,926)" 288 | fmt.Println(ac.FormatMoneyBigRat(big.NewRat(0, 3))) // "GBP --" 289 | ``` 290 | 291 | ## FormatMoneyFloat64(value float64) string 292 | 293 | FormatMoneyFloat64 only supports float64 value. It is faster than FormatMoney, because it does not do any runtime type evaluation. 294 | 295 | **Examples:** 296 | 297 | ```Go 298 | ac = accounting.Accounting{Symbol: "$", Precision: 2} 299 | fmt.Println(ac.FormatMoneyFloat64(123456789.213123)) // "$123,456,789.21" 300 | fmt.Println(ac.FormatMoneyFloat64(12345678)) // "$12,345,678.00" 301 | 302 | ac = accounting.Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","} 303 | fmt.Println(ac.FormatMoneyFloat64(4999.99)) // "€4.999,99" 304 | 305 | ac = accounting.Accounting{Symbol: "£ ", Precision: 0} 306 | fmt.Println(ac.FormatMoneyFloat64(500000)) // "£ 500,000" 307 | 308 | ac = accounting.Accounting{Symbol: "GBP", Precision: 0, 309 | Format: "%s %v", FormatNegative: "%s (%v)", FormatZero: "%s --"} 310 | fmt.Println(ac.FormatMoneyFloat64(1000000)) // "GBP 1,000,000" 311 | fmt.Println(ac.FormatMoneyFloat64(-5000)) // "GBP (5,000)" 312 | fmt.Println(ac.FormatMoneyFloat64(0)) // "GBP --" 313 | ``` 314 | 315 | ## FormatNumber(value interface{}, precision int, thousand string, decimal string) string 316 | 317 | FormatNumber is a base function of the library which formats a number with custom precision and separators. 318 | 319 | FormatNumber supports various types of value by runtime reflection. If you don't need runtime type evaluation, please refer to FormatNumberInt, FormatNumberBigRat, or FormatNumberFloat64. 320 | 321 | * supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, *big.Rat 322 | 323 | **Examples:** 324 | 325 | ```Go 326 | fmt.Println(accounting.FormatNumber(123456789.213123, 3, ",", ".")) // "123,456,789.213" 327 | fmt.Println(accounting.FormatNumber(1000000, 3, " ", ",")) // "1 000 000,000" 328 | ``` 329 | 330 | ## FormatNumberBigFloat(value *big.Float, precision int, thousand string, decimal string) string 331 | 332 | **(>= Go 1.5)** 333 | 334 | FormatNumberBigFloat only supports [*big.Float](https://golang.org/pkg/math/big/#Float) value. It is faster than FormatNumber, because it does not do any runtime type evaluation. 335 | 336 | **Examples:** 337 | 338 | ```Go 339 | fmt.Println(accounting.FormatNumberBigFloat(big.NewFloat(123456789.213123), 3, ",", ".")) // "123,456,789.213" 340 | ``` 341 | 342 | ## FormatNumberInt(value int, precision int, thousand string, decimal string) string 343 | 344 | FormatNumberInt only supports int value. It is faster than FormatNumber, because it does not do any runtime type evaluation. 345 | 346 | **Examples:** 347 | 348 | ```Go 349 | fmt.Println(accounting.FormatNumberInt(123456789, 3, ",", ".")) // "123,456,789.000" 350 | ``` 351 | 352 | ## FormatNumberBigRat(value *big.Rat, precision int, thousand string, decimal string) string 353 | 354 | FormatNumberBigRat only supports [*big.Rat](https://golang.org/pkg/math/big/#Rat) value. It is faster than FormatNumber, because it does not do any runtime type evaluation. 355 | 356 | **Examples:** 357 | 358 | ```Go 359 | fmt.Println(accounting.FormatNumberBigRat(big.NewRat(77777777, 3), 3, ",", ".")) // "25,925,925.667" 360 | ``` 361 | 362 | ## FormatNumberFloat64(value float64, precision int, thousand string, decimal string) string 363 | 364 | FormatNumberFloat64 only supports float64 value. It is faster than FormatNumber, because it does not do any runtime type evaluation. 365 | 366 | **Examples:** 367 | 368 | ```Go 369 | fmt.Println(accounting.FormatNumberFloat64(123456789.213123, 3, ",", ".")) // "123,456,789.213" 370 | ``` 371 | 372 | ## FormatNumberDecimal(value decimal.Decimal, precision int, thousand string, decimal string) string 373 | 374 | FormatNumberDecimal only supports [decimal.Decimal](https://github.com/shopspring/decimal) value. It is faster than FormatNumber, because it does not do any runtime type evaluation. 375 | 376 | **Examples:** 377 | 378 | ```Go 379 | import "github.com/shopspring/decimal" 380 | fmt.Println(accounting.FormatNumberBigDecimal(apd.New(apd.New(4999999, -3), 3, ",", ".")) // "4,999.999" 381 | ``` 382 | 383 | ## FormatNumberBigDecimal(value apd.Decimal, precision int, thousand string, decimal string) string 384 | 385 | FormatNumberDecimal only supports [apd.Decimal](https://github.com/cockroachdb/apd) value. It is faster than FormatNumber, because it does not do any runtime type evaluation. 386 | 387 | **Examples:** 388 | 389 | ```Go 390 | import "github.com/cockroachdb/apd" 391 | fmt.Println(accounting.FormatNumberDecimal(decimal.New(123456789.213123,3), 3, ",", ".")) // "123,456,789.213" 392 | ``` 393 | 394 | ## UnformatNumber(number string, precision int, currency string) string 395 | 396 | UnformatNumber is the inverse of FormatNumber. It strips out all currency formatting and returns the number with a point for the decimal seperator. 397 | 398 | **Examples:** 399 | 400 | ```Go 401 | fmt.Println(accounting.UnformatNumber("$45,000.50", 2, "USD")) // "45000.50" 402 | fmt.Println(accounting.UnformatNumber("EUR 12.500,3474", 3, "EUR")) // "12500.347" 403 | ``` 404 | 405 | --------------------------------------------------------------------------------