基本数据类型之浮点与复数
一 浮点型介绍
Go语言提供了两种精度的浮点数,单精度float32和双精度float64。它们的算术规范由IEEE754浮点数国际标准定义,该浮点数规范被所有现代的CPU支持。
类型 | 范围 |
---|---|
float32 | 最小近似值1.4e-45,具体可以用通过math.SmallestNonzeroFloat32查看 最大近似值大约是3.4e38,具体可以通过math.MaxFloat32查看 |
float64 | 最小近似值4.9e-324,具体可以用通过math.SmallestNonzeroFloat64查看 最大近似值大约是1.8e308,具体可以通过math.MaxFloat64查看 |
二 声明与初始化
声明语法如下
var 变量名 浮点型类型 = 初始化表达式/字面量
格式化输出
var f float64 = 3.931
fmt.Printf("%8.3f\n", f) // 总宽度为8,保留3位小数(四舍五入),不够在左侧填充空格,输出为3.931,前面有3个填充的空格
注意点:
- 1、宽度也是类型的一部分,float32与float64是两种不同的类型
var x float32 = 10.1
var y float64
y = x // 错误
- 2、类型推导的浮点型默认为float64
f := 3.931
fmt.Printf("%T\n",f) // float64
- 3、如果不加小数点,默认会被推导为整型而不是浮点型
f := 3
fmt.Printf("%T\n",f) // int
f1 := 3.0
fmt.Printf("%T\n",f1) // float64
- 4、通常应该优先使用float64,因float64的精度以及表示的正整数范围都要比float32大
var x float32 =3.6666666666666666666666666666666666666666
fmt.Println(x) // 3.6666667
var y float64 =3.6666666666666666666666666666666666666666
fmt.Println(y) // 3.6666666666666665 ,很明显float64精度也要搞一些
// ps:如果我们存为无类型常量,将会获取更高的精度
- 5、针对字面值运算10+10.3会被隐式转换为float64,结果也为float64,关于数据类型转换详见下一小节
fmt.Printf("%T\n", 10+10.3) // float64
fmt.Printf("%T\n", 10+float32(10.3)) // float32
三 数据类型转换
整型与浮点型为兼容类型,可以相互转换,本节我们主要介绍:整型、浮点型转为—>浮点型,其实与上一小节介绍的转换套路都是一样的,如下
一:显式转换规则如下:
-
1.1、无类型的量(无类型的整数、无类型的浮点数)可以转为浮点型,超出目标类型值域范围就报错
fmt.Println(math.MaxFloat32) // 3.4028234663852886e+38,科学计数法,e+38代表10的38次方 fmt.Println(math.SmallestNonzeroFloat32) // 1.401298464324817e-45 // 超出范围则报错 fmt.Println(float32(1000000000000000000000000000000000000000)) // 超过最大值 // ps:在没有超过值域范围的情况下,因为精度影响,下述三行结果都一样 fmt.Println(float32(340282346638528860000000000000000000000)) // 3.4028235e+38 fmt.Println(float32(340282346638528865555555555555555555555)) // 3.4028235e+38 fmt.Println(float32(340282346638528869999999999999999999999)) // 3.4028235e+38
-
1.2、有类型的量(有类型的整数、有类型的浮点数)也可以转为浮点型,超出目标类型值域范围不报错,但是浮点数会溢出,应该尽量避免
fmt.Println(math.MaxFloat64) // 1.7976931348623157e+308 var x float64 = 1.7976931348623157e+308 fmt.Println(float32(x)) // 输出+Inf代表溢出
强调:我们应该尽量避免溢出!!大多数情况情况下,只是为了统一类型,而不要改变原值
var apples int32 = 1 var oranges int16 = 2 var compote = float32(apples) + float32(oranges) fmt.Printf("%T,%v\n",compote,compote) // float32,3
二:隐式转换,只针对无类型的量
var x float64 = 10.3
fmt.Printf("%T\n",x + 10.66) // float64
四 基本操作
4.1 二元运算
浮点型也属于数字,重申一遍:数字之间支持加减乘除等算数运算以及比较运算,但仅限于同类型之间,非同类型经隐式或显式转换成相同类型后也可以。示例如下
(1)算数运算
// 例1:显式转换
var x float64 = 10
var y float32 = 20
fmt.Println(x + y) // 错误:mismatched types float64 and float32
fmt.Println(x + float64(y)) // 正确
// 例2:隐式转换
var x float64 = 10
fmt.Println(x + 10.3) // 隐式转换float64(10.3),正确,结果为:20.3
fmt.Println(x + 10.0) // 隐式转换float64(10.0),正确,结果为:20
// 例3:显式转换
var x int32 = 1
var y int64 = 2
var z = float64(x) + float64(y)
fmt.Println(z) // 3
(2)相等性比较
// 同类型ok
var x float64 = 10.3
var y float64 = 10.3
fmt.Println(x == y) // true
fmt.Println(x != y) // false
// 不同类型则编译错误
// 再次强调:无论什么类型,宽度或长度都是其重要的组成部分
//var i float64 = 10.3
//var j float32 = 10.3
//fmt.Println(i == j) // 编译错误
// 涉及到无类型的量(无类型的常量或字面量),会隐式转换成相应类型,转换合法就可以进行相等性判断。
var m float64 = 10.3
fmt.Println(m == 10.3) // 可以比较,因为底层:x == float64(10.3),编译通过
fmt.Println(m == 10) // 可以比较,因为底层:x == float64(10),编译通过
(3)比大小
var x float32 = 10
var y float64 = 3
// 显式
//fmt.Println(x > y) // 编译错误
fmt.Println(x > float32(y)) // 编译通过
// 隐式
fmt.Println(x > 3) // x > float32(3),编译通过
fmt.Println(x > 3.1) // x > float32(3.1),编译通过
// 隐式
fmt.Println(10 > 3.1) // 优先级float>int,所以底层float64(10) > float64(3.1),编译通过
4.2 浮点数精度问题提
因为浮点数不是一种精确的表达方式,所以像整型那样直接用==来判断两个浮点数是否相等是不可行的,这可能会导致不稳定的结果,例如
package main
import (
"fmt"
)
func main() {
x := 0.1
y := 0.2
res := x + y
fmt.Println(res) // 0.30000000000000004
fmt.Println(res == 0.3) // false
}
浮点数的精度问题与解决方案详见《附录:浮点数的精度问题》,精度问题解决方案见你文章末尾。
另外,无类型的常量有更高的计算精度
package main
import "fmt"
func main() {
const x = 0.1
const y = 0.2
res := x + y
fmt.Println(res) // 0.3
fmt.Println(res == 0.3) // true
var m = 0.3
fmt.Println(m == 0.1+0.2) // true
4.3 非数
了解:浮点数里有个叫非数的东西
fmt.Println(math.NaN()) // NaN
var z float32
fmt.Println(z) // 0
fmt.Println(z/z) // NaN
x:=z/z
fmt.Println(x < x) // false
fmt.Println(x > x) // false
fmt.Println(x == x) // false
fmt.Println(x != x) // true
五 在函数参数中传递
值传递
六 复数
Go语言提供了两种精度的复数类型:complex64和complex128,复数实际上由两个实数(在计算机中用浮点数表示,complex64对应的是float32,complex128对应的是float64)构成,一个表示实部(real),一个表示虚部(imag)。
内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实部和虚部:
var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y) // "(-5+10i)"
fmt.Println(real(x*y)) // "-5"
fmt.Println(imag(x*y)) // "10"
如果一个浮点数面值或一个十进制整数面值后面跟着一个i,例如3.141592i或2i,它将构成一个复数的虚部,复数的实部是0:
fmt.Println(1i * 1i) // i^2 = -1 ,实部为-1,虚部为0,结果记为复数形式(-1+0i)
在常量算术规则下,一个复数常量可以加到另一个普通数值常量(整数或浮点数、实部或虚部),我们可以用自然的方式书写复数,就像1+2i或与之等价的写法2i+1。上面x和y的声明语句还可以简化:
x := 1 + 2i
y := 3 + 4i
复数也可以用==和!=进行相等比较。只有两个复数的实部和虚部都相等的时候它们才是相等的(译注:浮点数的相等比较是危险的,需要特别小心处理精度问题)。
math/cmplx包提供了复数处理的许多函数,例如求复数的平方根函数和求幂函数,更多关于复数的函数,请查阅math/cmplx标准库的文档。
fmt.Println(cmplx.Sqrt(-1)) // "(0+1i)"