Golang - float to int conversion

Viewed 10718

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)

}
2 Answers

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.

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?

Related