Golang将float64转换为int时出现错误。

huangapple go评论96阅读模式
英文:

Golang converting float64 to int error

问题

如何在将浮点数转换为整数时避免浮点数误差?例如,以下代码输出为0.5499999999999972,而我期望输出为0.55

package main

import "fmt"

func main() {
    x := 100.55
    fmt.Println(x - float64(int(x)))	
}

输出:

0.5499999999999972

要避免浮点数误差,可以使用四舍五入函数来处理浮点数。在Go语言中,可以使用math.Round()函数来实现四舍五入。修改代码如下:

package main

import (
    "fmt"
    "math"
)

func main() {
    x := 100.55
    rounded := math.Round(x*100) / 100
    fmt.Println(rounded)
}

输出:

0.55

这样就可以得到正确的输出0.55

英文:

How can I avoid floating point errors when converting float's to int's.
For example the following code prints: 0.5499999999999972 when I wound expect it to print 0.55.

package main

import "fmt"

func main() {
    x := 100.55
    fmt.Println(x - float64(int(x)))	
}

Output:
0.5499999999999972

答案1

得分: 10

你需要理解一些事情:100.55是一个十进制数(以十进制基数表示)。100.55在十进制中是一个有限的数,确切地是100.55

一般情况下,计算机以二进制表示存储数字。数字100.55无法用有限的二进制数表示:100.55在二进制表示中是一个无限的数(与1/3无法用有限的十进制数表示的原因相同,它是一个无尽的序列:0.333333333....)。

但是Go(像其他任何语言一样)使用IEEE-754标准存储float64类型,这是一种有限的二进制表示。一个float64值在内存中使用64位来描述该数字,其中53位用于描述数字,11位用于描述指数。

当你这样“说”时:

x := 100.55

这是一个短变量声明,它将创建一个名为x的新变量,并从右侧表达式中推断出其类型,该表达式是一个浮点数字面量,因此根据Go规范,x的类型将是float64。浮点数字面量将需要“转换”以使用64位来表示(根据IEEE-754指定的规则)。由于100.55需要无限位才能在二进制基数中准确表示,因此仅使用64位(53位用于数字),结果将不会(也不能)完全等于100.55(而是最接近它的IEEE-754格式的64位二进制数),即:

x := 100.55
fmt.Printf("%.50f\n", x)

100.54999999999999715782905695959925651550292968750000

所以你已经从一个不是100.55的数字开始了。

你从中减去100float64(int(x))将完全等于100.0):

x = x - float64(int(x))
fmt.Printf("%.50f\n", x)

0.54999999999999715782905695959925651550292968750000

你能做些什么呢?实际上没有什么可以做的。你期望的结果(0.55)在二进制表示中也是一个无限的数,所以你不能在float64类型的变量中得到一个精确的0.55

你可以像处理普通数字一样处理这个数,但在打印时,将其四舍五入到你选择的小数位数。最简单的方法是使用fmt.Printf(),并使用格式字符串使用%f占位符指定精度:

fmt.Printf("%.2f\n", x)

结果:

0.55

另一个选择是避免使用浮点数。例如,如果你要表示美元金额,你可以将所有的值“乘以”100,将金额表示为以分为单位的整数。只有在需要将结果打印为美元时,你可以打印类似于:

cents := 10055
fmt.Printf("%d.%d $\n", cents/100, cents%100)

输出:

100.55 $
英文:

You need to understand something: 100.55 is a decimal number (presented in decimal radix). 100.55 in decimal is a finite number and is exactly this: 100.55.

Computers in general store numbers in binary representation. The number 100.55 cannot be represented with a finite binary number: 100.55 is an infinite number in binary representation (same reason why 1/3 cannot be represented with a finite decimal number, it is an endless sequence: 0.333333333....).

But Go (like any other language) stores float64 types using the IEEE-754 standard, which is a finite binary representation. A float64 value uses 64 bits in memory to describe the number, of which 53 bits are used to describe the digits and 11 bits are used for the exponent.

Now when you "say" this:

x := 100.55

It is a short variable declaration which will create a new variable named x and infer its type from the right hand side expression which is a floating point literal, so by the Go spec x's type will be float64. The floating point literal will have to be "converted" in order to be represented using 64 bits (by rules specified by IEEE-754). And since 100.55 would require infinite bits to be represented precisely in binary radix, by using only 64 bits (53 for the digits) the result will not (cannot) be exactly 100.55 (but a 64-bit binary number in IEEE-754 format that is closest to it), which is:

x := 100.55
fmt.Printf("%.50f\n", x)

100.54999999999999715782905695959925651550292968750000

So you are already starting off with a number not being 100.55.

You subtract 100 from it (float64(int(x)) will be exactly 100.0):

x = x - float64(int(x))
fmt.Printf("%.50f\n", x)

0.54999999999999715782905695959925651550292968750000

What can you do about it? Nothing really. The result you expect (0.55) is also an infinite number in binary representation, so you can't have an exact number of 0.55 in a variable of type float64.

What you can do is work with the number as normal, but when you print it, round it to decimal places of your choice. The easiest is to use fmt.Printf(), and specify a format string using the verb %f including the precision:

fmt.Printf("%.2f\n", x)

Result:

0.55

Another option is to avoid using floating point numbers. E.g. if you were to represent USD amounts, you could "multiply" all your values by 100 and represent amounts as cents (1$*100) which is an integer. Only if you need to print the result as USD, you could print something like

cents := 10055
fmt.Printf("%d.%d $", cents/100, cents%100)

Output:

100.55 $

huangapple
  • 本文由 发表于 2016年3月17日 14:13:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/36052922.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定