14 基本数据类型之浮点与复数

基本数据类型之浮点与复数

一 浮点型介绍

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)"
上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术