7

My problem looks like very simple, but I cat not solve it yet.. User enters a money amount xx.xx as a string. After that I convert string to float value (some dollars, some cents), for example, let it be 655.18 (float64) Then, I need to convert this amount to cents - 655.18*100 and the result is 65517.9999999, so when I cast this value to int, I get 65517, so 1 cent was lost. How can I avoid this behaviour?

Code:

package main

import (
    "fmt"
    "strconv"
)

func CheckParamsPur(sa string) (amount float64) {
    amount, _ = strconv.ParseFloat(sa, 64)
    return amount
}

func main() {

    fmt.Print("Enter a number: ")
    var input string
    fmt.Scanf("%s", &input)
    amount := int(CheckParamsPur(input) * (100))

    fmt.Println(amount)

}
  • 1
    You cannot. That's how floats work. Floats are not reals an simply _cannot_ represent some decimal numbers. You either have to take great care and implement proper rounding or work with cents all the time or use a decimal type. This will get ugly pretty soon. Good luck. – Volker Sep 29 '17 at 15:22
  • 1
    If you want to understand why some values are impossible to represent, this post explains how floats work pretty well: http://fabiensanglard.net/floating_point_visually_explained/ – Peter Sep 29 '17 at 15:31
  • 1
    and the corresponding backchannel of that blog post https://news.ycombinator.com/item?id=15359574 – Scott Stensland Sep 29 '17 at 15:44

2 Answers2

13

From string to float to integer

If you first want to start with parsing the amount to a float, then in the next step instead of converting, use rounding.

Since conversion keeps the integer part, add 0.5 to it before converting. So if the fraction part was less than 0.5, adding 0.5 will not increase the integer part and so after conversion the integer part will be the same (rounding down). If fraction part was greater than 0.5, adding 0.5 to it will increase the integer part, and after conversion we get 1 higher of the original integer part (rounding up).

In your case, after multiplying by 100, add 0.5 before doing the conversion :

f := 655.17
i := int(f*100 + 0.5)
fmt.Println(i)

Output (try it on the Go Playground):

65517

Read related question which covers this and more: Golang Round to Nearest 0.05

Parsing integer and fraction parts separately, as 2 integers

Another option is to skip floating point numbers entirely. To do that, we can parse the input string which supposed to hold a floating point number as 2 integers separated by a dot .: "dollars.cents" (where dollars and cents are both integers):

s := "655.17"

var dollars int64
var cents uint64
if _, err := fmt.Sscanf(s, "%d.%d", &dollars, &cents); err != nil {
    panic(err)
}
if cents > 99 {
    panic("cents cannot be greater than 99")
}

var total int64
if dollars >= 0 {
    total = dollars*100 + int64(cents)
} else {
    total = dollars*100 - int64(cents)
}
fmt.Println(total)

Output again is (try it on the Go Playground):

65517

Note: The above parsing code requires you to enter cents using 2 digits, e.g. 655.70, entering 655.7 would give bad results.

icza
  • 389,944
  • 63
  • 907
  • 827
4

Don't use floats for currency. Use an integer representing cents instead.

package main

import (
    "fmt"
    "strconv"
    "strings"
)

func main() {
    input := "655.18"
    c := strings.Replace(input, ".", "", -1)
    cents, _ := strconv.ParseInt(c, 10, 64)
    fmt.Println(cents)
}

You should probably also tidy up user input as they may give you .40 or 4.50 or $40 but that's easy to do while it is a string and can be part of your conversion method. Do this before conversion and you can then parse the string as cents. You should have one stringToCents method for user input and one centsToString method for formatting the currency which are the boundaries, everywhere else use cents.

If you parse and store as cents you will always have to explicitly convert to float where required for a calc and then back to cents, you will never be surprised by rounding rules or inaccuracies in the floats, as you then explicitly state which rounding is going to be used for each calculation (there are lots of arbitrary rules about rounding on currencies, this becomes necc. quite apart from the problems with floats).

See the answers to this related question for more examples of where this will trip you up.

Why not use Double or Float to represent currency?

Kenny Grant
  • 9,360
  • 2
  • 33
  • 47
  • 1
    Except it's horrible UX to have users enter monetary values in cents (or the equivalent for the currency). – Peter Sep 29 '17 at 15:33
  • 4
    It's not too hard to convert from user representations ( £ 23.00, or 3.43 or £3) to a cent value, and format cents for display as currency for presentation to users. I wasn't advocating making users work in cents, just always storing in cents. – Kenny Grant Sep 29 '17 at 15:38
  • 1
    @KennyGrant Basically that's what asker wants to do: convert user input (dollars which was given as float) to integer (number of cents). – icza Sep 29 '17 at 15:43
  • 4
    No it’s not, you don’t have to convert to float – Kenny Grant Sep 29 '17 at 17:10