Go语言浮点数精度问题与格式化技巧
在现代编程中,浮点数是我们经常处理的基本数据类型。然而,浮点数的精度问题一直是编程语言中一个令人头痛的难题,Go语言也不例外。尤其是在涉及财务计算、科学计算等需要高精度的场景时,浮点数的误差可能带来严重的后果。本文将重点讨论 Go 语言中的浮点数精度问题,探讨如何通过合理的声明和格式化技巧来减少误差,帮助开发者写出更健壮、精确的代码。
Go语言提供了两种浮点数类型:float32 和 float64。其中,float64 是默认的浮点数类型,提供了更高的精度,但也带来了一些精度误差,尤其在计算过程中需要小数点精度时。在本篇文章中,我们将深入解析浮点数的声明、使用以及精度控制,介绍 Go 语言中的常见陷阱和如何避免浮点数误差带来的问题。通过结合实际的代码示例,我们将展示如何使用 fmt.Printf 来格式化浮点数输出,从而让数值显示更加精确。此外,我们还将探讨如何比较浮点数,以避免因微小误差导致的逻辑问题。
实数
声明浮点型变量
-
下面这三个语句的效果是一样的:
days := 365.2425var days = 365.2425var days float64 = 365.2425
-
只要数字含有小数部分,那么它的类型就是float64
声明浮点型变量
-
如果你使用一个整数来初始化某个变量,那么你必须指定它的类型为float64,否则它就是一个整数类型:
var answer float64 = 42
测试
-
answer是什么类型?
answer := 42.0
单精度浮点数类型
-
Go语言里有两种浮点数类型:
-
默认是float64
- 64位的浮点类型
- 占用8字节内存
- 某些编程语言把这种类型叫做double(双精度)
-
float32
- 占用4字节内存
- 精度比float64低
- 有时叫做单精度类型
单精度浮点类型
- 想要使用单精度类型,你必须在声明变量的时候指定该类型
var pi64 = math.Pi
var pi32 float32 = math.Pi
fmt.Println("pi64: ", pi64)
fmt.Println("pi32: ", pi32)
单双精度的使用场景
- 当处理大量数据时,例如3D游戏中的数千个顶点,使用float32牺牲精度来节省内存是很有意义的。
- math包里面的函数操作的都是float64类型,所以应该首选使用float64,除非你有足够的理由不去使用它。
零值
- Go里面每个类型都有一个默认值,它称作零值。
- 当你声明变量却不对它进行初始化的时候,它的值就是零值。
var price float64
fmt.Println("price: ", price)
price1 := 0.0
fmt.Println("price: ", price1)
显示浮点类型
- 使用Print或Println打印浮点类型的时候,默认的行为是尽可能的多显示几位小数
- 如果你不想这样,那么你应该使用Printf函数,结合%f格式化动词来指定显示小数的位数:
third := 1.0 / 3
fmt.Println(third)
fmt.Printf("%v\n", third)
fmt.Printf("%f\n", third)
fmt.Printf("%.3f\n", third)
fmt.Printf("%4.2f\n", third)
运行
Code/go/hello via 🐹 v1.20.3
➜ go run main.go
0.3333333333333333
0.3333333333333333
0.333333
0.333
0.33
Code/go/hello via 🐹 v1.20.3
➜
%f 格式化动词
-
它由两部分组成:
-
宽度:会显示出的最少字符个数(包含小数点和小数)
- 如果宽度大于数字的个数,那么左边会填充空格
- 如果没指定宽度,那么就按实际的位数进行显示
-
精度:小数点后边显示的位数
-
如果想使用0代替空格作为填充:
fmt.Printf("%05.2f\n", third)
浮点类型的精度
third := 1.0 / 3.0
fmt.Println(third + third + third)
piggyBank := 0.1
piggyBank += 0.2
fmt.Println(piggyBank)
可以看到,浮点类型不适合用于金融类计算为了尽量最小化舍入错误,建议先做乘法,再做除法
- 可以看到,浮点类型不适合用于金融类计算
- 为了尽量最小化舍入错误,建议先做乘法,再做除法
celsius := 21.0
fmt.Print((celsius/5.0*9.0)+32, "° F\n")
fmt.Print((9.0/5.0*celsius)+32, "° F\n")
celsius := 21.0
fahrenheit := (celsius * 9.0 / 5.0) + 32.0
fmt.Print(fahrenheit, "° F")
问题
- 如何避免上述的舍入错误?
如何比较浮点类型
piggyBank := 0.1
piggyBank += 0.2
fmt.Println(piggyBank == 0.3)
fmt.Println(math.Abs(piggyBank-0.3) < 0.0001)
作业题
-
编写一个程序:
- 随机地将五分镍币(0.05美元)、一角硬币(0.10美元)和25美分硬币(0.25美元)放入一个空的储蓄罐,直到里面至少有20美元。
- 每次存款后显示存钱罐的余额
- 并以适当的宽度和精度格式化。
总结
浮点数精度问题是计算机编程中的常见难题,Go语言通过 float32 和 float64 两种类型为我们提供了灵活的选择,但也因此产生了浮点数计算精度不足的问题。开发者在使用浮点数时需要特别小心,尤其是在涉及金额、科学计算等高精度需求的场景。通过合理的格式化输出和精度控制,可以有效地减少精度误差,提升程序的可靠性。掌握浮点数的精度控制技巧和输出格式化方式,不仅能让我们的代码更加健壮,也能避免一些常见的错误和陷阱。希望通过本文的讲解,你能在 Go 语言开发中更好地理解和运用浮点数类型,编写出更高效、更准确的程序。