9

I am trying to format some numbers as a currency, with commas and 2 decimal places. I've found "github.com/dustin/go-humanize" for the commas but it doesn't allow for specifying the number of decimal places. fmt.Sprintf will do the currency and decimal formatting but not the commas.

for _, fl := range []float64{123456.789, 123456.0, 123456.0100} {
    log.Println(humanize.Commaf(fl))
  }

Results:

123,456.789
123,456
123,456.01

I am expecting:

$123,456.79
$123,456.00
$123,456.01
  • 5
    Never ever ever use float's for storing currency, its such a bad idea ! - See http://play.golang.org/p/TQBd4yJe6B – LenW Jul 20 '15 at 19:49
  • 1
    I have amended my answer to propose an alternative. – VonC Jul 21 '15 at 05:42
  • You can checkout the leekchan/accounting project, using Go1.5 big.Float. See [my edited answer below](http://stackoverflow.com/a/31503800/6309). – VonC Aug 22 '15 at 09:14
  • If you want to get the job done without a third party lib, maybe this solution works for you https://stackoverflow.com/a/71142159/10435604 – Rafael Affonso Feb 16 '22 at 12:55

3 Answers3

12

That would be what the humanize.FormatFloat() does:

// FormatFloat produces a formatted number as string based on the following user-specified criteria:
// * thousands separator
// * decimal separator
// * decimal precision

In your case:

FormatFloat("#,###.##", afloat)

That being said, as commented by LenW, float (in Go, float64) is not a good fit for currency.
See floating-point-gui.de.

Using a package like go-inf/inf (previously go/dec, used for instance in this currency implementation) is better.

See Dec.go:

// A Dec represents a signed arbitrary-precision decimal.
// It is a combination of a sign, an arbitrary-precision integer coefficient
// value, and a signed fixed-precision exponent value.
// The sign and the coefficient value are handled together as a signed value
// and referred to as the unscaled value.

That type Dec does include a Format() method.


Since July 2015, you now have leekchan/accounting from Kyoung-chan Lee (leekchan) with the same advice:

Please do not use float64 to count money. Floats can have errors when you perform operations on them.
Using big.Rat (< Go 1.5) or big.Float (>= Go 1.5) is highly recommended. (accounting supports float64, but it is just for convenience.)

fmt.Println(ac.FormatMoneyBigFloat(big.NewFloat(123456789.213123))) // "$123,456,789.21"
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • This seems to be more practical than Commaf that the OP mentioned in https://github.com/dustin/go-humanize/issues/18#issuecomment-122625518 – VonC Jul 19 '15 at 17:17
  • With the latest version of humanize, you have to remove the '$' symbol, otherwise you will get the error: "invalid positive sign directive". – Edenshaw May 01 '23 at 19:29
  • 1
    @Edenshaw Thank you for the feedback. I have edited the answer accordingly. – VonC May 01 '23 at 19:45
4

There is a good blog post about why you should never use floats to represent currency here - http://engineering.shopspring.com/2015/03/03/decimal/

From their examples you can :

  d := New(-12345, -3)
  println(d.String())

Will give you :

-12.345
LenW
  • 3,054
  • 1
  • 24
  • 25
  • Link doesn't work, it is available here https://web.archive.org/web/20190518124510/https://engineering.shopspring.com/decimal-an-arbitrary-precision-fixed-point-decimal-library-for-go-ff8b31ca7a5f – meblum Mar 16 '23 at 16:20
0
fmt.Printf("%.2f", 12.3456) 

-- output is 12.34

goenning
  • 6,514
  • 1
  • 35
  • 42
Drew
  • 24,851
  • 10
  • 43
  • 78