Numeric Types

Go提供了非常多了内置numeric类型, 同时在标准库中定义了big.Int和big.Rat(实数), 这两个类型都是无限大小(只受制于内存的大小), 每一种数字类型都是不同的: 因此我们不能在非相同的类型中使用位操作符(<<, &, ~) 或者比较操作符(e.g., + 或 <), 比如类型int32与int。 未指定类于是的constants可以任意的内置数字进行比较。 所以我们可以对未指定类型的常量同其它的数字进行加减或者比较,而不用管那个数字的类型是什么(只能是内置的数字类型).

如果你需要在不同的数字类型中进行算术或者比较操作。 我们必须先对类型进行转换-通常是转换为大的数字类型,可以避免精度损失。 通常采用 type(value)的形式. 以下是一此例子:

const factor = 3        //factor 可以与任意的数字类型进行算术或者比较操作
i := 20000              // 类型推断为int
i *= factor
j := int16(20)          // int16 跟 var j int16=20 等效
i += int(j)             // 类型必须匹配(相同), 所以必须进行转换
k := uint8(0)           // save as var k unit 8
k = unit8(i)            // Successd, 但k会被裁减成8位
fmt.Println(i, j, k)    // Prints: 60020 20 116

如果我们想要进行安全的向下类型转换,我们需要创建合适的方法,比如:

func Unit8FromInt(x int) (uint8, error) {
    if 0 <= x && x <= math.MaxUint8 {
        return uint8(x), nil
    }
    return 0, fmt.Errorf("%d is out of the uint8 range", x)
}

相同的数字类型可以使用表2.3的比较操作符. 相同的也适用于表2.4的算数运算符。 而表2.6中的操作符只适用于整型

常量表达式是在编译的时候运行, 它可以使用任意的算数,布尔以及比较操作符。 比如:

const
(
    efri int64 = 10000000000                              // type: int64
    hlutföllum = 16.0 / 9.0                               // type: float64
    mælikvarða = complex(-2, 3.5) * hlutföllum            // type: complex128
    erGjaldgengur = 0.0 <= hlutföllum && hlutföllum <2.0  // type: bool
)

以上这个例子中使用了冰岛语作为标识符。

Table 2.4 算术运行操作符

Syntax Description/result
+x x
-x x的负数
x++ 先使用x, 在对x增加1
x-- 先使用x, 在对x减去1
x += y 对x增加y
x -= y 对x减去y
x *= y 对x乘以y
x /= y 对x除以y. 如果x为整型,那么小数点部份会被舍弃。 如果y为0, 那么会触发运行时panic(异常).
x + y x,y的和
x - y x,y的差
x * y x乘以y
x / y 如果x为整型,那么小数点部份会被舍弃。 如果y为0, 那么会触发运行时panic(异常).

整型

Go 提供了11中整数类型, 5种带符号的,5种不带符号(负数), 加上一种存储指针的数据类型。 它们的名字和值,如表2.5所示. 此外,Go还允许使用byte代替uint8. 当表示字符时(Unicode code points), 可以使用rune 代替int32. 对于大部分的整型,我们只需要使用int. int通常也作为array, slice的索引(index). int通常也提供了最快的处理速度。 当前int代表有符号32-位整型(即使在64位的平台上), 但可能在Go未来的版本中,它可能代表64位。

Table 2.5 Go's Integer Types and Ranges

Type Range
byte 同unit8一样,用于读写原始字节, 比如处理UTF-8编码的文本。
int int32或者int64, 依赖于操作系统
uint 同上
int8 [-128, 127]
uint8 [0, 255]
int16 [-32768, 32767]
unit16 [0, 65535]
int32 [-2,147,483,648 , 2,147,483,647] 20亿
uint32 [0, 4 294 967295]
int64 [−9223372036854775808, 9223372036854775807]
uint64 [0, 18446744 073709551615]
rune 同int32一样,用于在go中表示unicode大小
uintptr 用于存储指针

Table2.6 只适用于整行的算数运算

Syntax Description/result
^x 对x按位取反
x %= y Mod操作符,如果y为零,则发生一个运行时异常(panic)
x &= y 设置x的值为 x与y的按位与,00101 & 11100 = 00100
x |= y 设置x的值为 x与y的按位或, 00101 | 11100 = 11101
x ^= y 按位异或,两个位不同为1,相同则为0, 00101 ^ 11100 = 11001
x &^= y bitwise clear, 根据y的位(1的位)来清除x中对应的位 11100 &^ 1000 = 10100
x >>= u 向右移u位
x <<= u 向左移u位
x % y mod
x & y and
x | y or
x ^ y xor
x &^ y bitwise clear(AND NOT)
x << u 向左移u位
x >> u 向右移u位

对于整型来说,从低位到高位的转换总是安全的,但是从高位到低位的或者将一个负数转换为无符号位,则会被直接截断。 最好使用自定义的方法进行安全的降级。 同样,整型也可以转换为浮点数(e.g., float64(integer))

Big Integers

有的情况下,我们需要计算的数字范围超出了int64或者uint64位。 同样也不能使用浮点类型来表示(近似值)。 这时我们只能使用GO标准库中的integer类型(无限制大小) - big.Int 和 big.Rat(实数: 可以表示2/3, 1.1496, 但不能表示无理数e or π). big.Int可以表示任意的数字大小,只受限于机器是否有足够的内存,但通常速度上要比内置的整型要慢。

由于GO语法上类似于C和Java. 不支持操作符的重载, 我们只能使用big.Int和big.Rat中的Add(), Mul()等方法进行计算,而不能使用 +,- 等算数符号对两个big.Int类型的数字进行计算。 我们在这里不能全部讲解这些方法,可以在线查看math/big package.

package main
import (
    "fmt"
    "math/big"
)
func main() {
    sum := big.NewInt(0)
    sum.Div(big.NewInt(1000), big.NewInt(5))
    fmt.Printf("%d", sum) // 200
}

使用float64位允许我们使用15位的小数点(适用于大部分的情况) 可如果我们想要计算更大的小数位数(数十或上百位的小数点,比如π),则没有合适的内置类型可以使用。

1706年,John Machin 开发了一个计算π任意小数的公司。我们可以采用这个公式并结合Big.Int来计算π。 公式如下Figure2.1(不需要了解这个公司的使用,我们只是介绍big.Int, 让你有更深的了解)

func main() {
places := handleCommandLine(1000)
scaledPi := fmt.Sprint(π(places))
fmt.Printf("3.%s\n", scaledPi[1:])
}

此程序默认的小数点位数是1000, 同时用户也可以在command中输入想要的小数点位数(handleCommandLine函数处理输入的参数).

package main

import (
    "fmt"
    "math/big"
    "os"
    "path/filepath"
    "strconv"
)

func main() {
    places := handleCommandLine(1000)
    scaledPi := fmt.Sprint(π(places))
    fmt.Printf("3.%s\n", scaledPi[1:])
}

func handleCommandLine(defaultValue int) int {
    if len(os.Args) > 1 { //如果console输入了-h 或者--help
        if os.Args[1] == "-h" || os.Args[1] == "--help" {
            usage := "usage: %s [digits]\n e.g.: %s 10000"
            app := filepath.Base(os.Args[0])
            fmt.Fprintln(os.Stderr, fmt.Sprintf(usage, app, app))
            os.Exit(1)
        }
    }
    if x, err := strconv.Atoi(os.Args[1]); err != nil { //第二个参数不是数字则返回默认值1000
        fmt.Fprintf(os.Stderr, "ignoreing invalid number of "+
            "digits: will display %d\n", defaultValue)
    } else {
        return x
    }
    return defaultValue
}
func π(places int) *big.Int { //返回big.Int指针
    digits := big.NewInt(int64(places)) //指定的小数点位数, func NewInt(x int64) *Int
    unity := big.NewInt(0)
    ten := big.NewInt(10)
    exponent := big.NewInt(0)
    //并没有返回值赋值给unity, 因为big.Int的所有方法会直接修改方法的接受者,并且返回这个值。
    unity.Exp(ten, exponent.Add(digits, ten), nil) 

    pi := big.NewInt(4)
    left := arccot(big.NewInt(5), unity)
    left.Mul(left, big.NewInt(4))           //4 *  arccot(5)
    right := arccot(big.NewInt(239), unity) //arccot(239)
    left.Sub(left, right)
    pi.Mul(pi, left) // 4 * (4 * arccot(5) - arccot(239))
    return pi.Div(pi, big.NewInt(0).Exp(ten, ten, nil))
}

func arccot(x, unity *big.Int) *big.Int {
    sum := big.NewInt(0)
    sum.Div(unity, x) // unity/x  10的1000+10次方/5
    xpower := big.NewInt(0)
    xpower.Div(unity, x) // 1/x
    n := big.NewInt(3)
    sign := big.NewInt(-1)
    zero := big.NewInt(0)
    square := big.NewInt(0)
    square.Mul(x, x) // x的平方

    for {
        xpower.Div(xpower, square) // 第一次为 1/x / x*x = 1/x的3次方
        term := big.NewInt(0)      //term 每一次的计算项
        term.Div(xpower, n)
        if term.Cmp(zero) == 0 { //如果计算到等于0的时候,退出
            break
        }
        addend := big.NewInt(0)
        sum.Add(sum, addend.Mul(sign, term))
        sign.Neg(sign) //取反
        n.Add(n, big.NewInt(2))
    }
    return sum
}

π()函数先计算unity变量的值(), unity主要用来统一所有的计算,使得计算的值都为整数(0.123 * unity),+10是为了避免四舍五入而产生的错误。在计算完unity之后,我们采用Machin的公式来计算π, arccot()函数接受unity变量作为它的第二个参数。 最终我们返回一个除以的数字(用来避免四舍五入的10次方).

为了获得unity变量,我们先创建了四个变量,它们都是big.Int类型的指针(func NewInt(x int64) Int). unity和exponent的初始值为0,ten变量的值为10,digits的值由用户输入,并且强制转换为int64. big.Int.Add()方法用来将设置exponent的值为digits+10, 并返回这个和。 big.Int.Exp(x, y, nil) 计算; 如果第三个参数为non-nil. big.Int.Exp(x, y, z) 则计算 mod z. 同时注意,这里没有对unity进行赋值,是因为unity.Exp会修改unity, 同时返回计算会的值(适用于big.Int所有的方法,Add, Div)。

在arccot每一次的计算都是 或者 , 这样整个的公式就是.

浮点类型

Go提供两种浮点数和两种复合数, 如表2.7所示。 Table2.7 Go's Floating-Point Types

Type Range
float32 精确到7位小数
float64 精确到15位小数
complex64 实数和虚数的类型为float32
complex128 实数和虚数的类型为float64

Go的浮点数字支持表2.4中的算数操作。math package的主要常量函数如表2.8所示.

浮点数可以写成小数点或者指数形式,比如0.0,3.,8.2,-7.4,-6e4,.1,5.9E-3. 大部分的浮点数可以精确的表示,比如0.5, 相反0.1,0.2却不能够精确的表示。这也存在于Javascript, 比如0.1+0.2 != 0.3

Table2.8 The Math Package 常量和函数

math package函数的参数和返回值在没有指定其它类型时返回float64.

Syntax Description/result
math.Abs(x) 求x的绝对值
math.Ceil(x) the smallest integer greater than or
math.Floor(x) 比x小的最大整数 math.Floor(5.6) == 5.0
math.Pi The constant π; approximately 3.141592653589793
math.Max(x,y) x,y中的最大值
math.Min(x,y) x,y中的最小值
math.Modf(x) 返回x的整数部分和小数部分

所有的比较运算符都在Table 2.3中。可由于浮点数都是近似值表示的,所以比较两个浮点通常不是很准确

x, y := 0.0, 0.0
for i:= 0; i< 10; i++ {
    x += 0.1
    if i%2 == 0{
        y += 0.2
    }else{
        fmt.Printf("%-5t %-5t %-5t %-5t", x==y, EqualFloat(x, y, -1),
            EqualFloat(x, y, 0.000000000001), EqualFloatPrec(x, y, 6))
        fmt.Println(x, y)
    }

func EqualFloat(x, y, limit float64) bool {
    //如果小于0. 则采用机器可以表示的精度, 否则使用给定的精度
    if limit <=0.0 {
        limit = math.SmallestNonzeroFloat64
    }
    //比较两个数的误差是否小于最小的数乘以精度
    //比如0.6 * 0.000000000001 == 6e-13
    // 0.60000000000000001 - 0.6 = 1.1102230246251565e-16
    return math.Abs(x-y) <= 
    (limit * math.Min(math.Abs(x), math.Abs(y)))
}

func EqualFloatPrec(x, y float64, decimals int) bool {
    a := fmt.Sprintf("%.*f", decimals, x)
    b := fmt.Sprintf("%.*f", decimals, y)
    return len(a) == len(b) && a == b
}
true  true  true  true 0.2 0.2
true  true  true  true 0.4 0.4
false false true  true 0.6 0.6000000000000001
false false true  true 0.7999999999999999 0.8
false false true  true 0.9999999999999999 1

我们初始化了两个float64类型的浮点数0. 在每次循环中给x加0.1, 每两次给y加0.2. 最后的值都应该为1. 但如输出所示, 两者的值却不一定相等。所以我们在使用 == 和 !=比较两个浮点数时,需要特别注意。 但这并不是说就不能使用== !=来比较两个浮点数,比如,为了避免除以0, 可以使用以下的语法 if y != 0.0 {return x / y }

大部分情况下只需要使用到float64位(也是0.0默认的类型),特别是当使用math package时(参数和返回值都为float64). 但对于类存有限,同时可以不需要使用到math package时,我们可以使用float32.

浮点类型可以转换为整型。int(float). 这种情况下,小数部分会被舍弃。当然如果浮点数的大小超过了整型。则结果是不可预测的值。 我们可以使用下面的方法。

func IntFromFloat64(x float64) int {
    if math.MinInt32 <= x && x <= math.MaxInt32 {
        whole, fraction := math.Modf(x)
        if fraction >= 0.5 {
            whole++
        }
        return int(whole)
    }
    panic(fmt.Sprintf("%g is out of the int32 range", x))
}

在之前的Uint8FromInt()函数中,我们使用的是返回一个error. 而这里,我们build-in panic()函数,抛出运行时异常(panic), 终止程序的运行(除非使用recover()捕获了异常)。

复合类型(Complex Types)

Go支持两种复合类型complex64和complex128. 可以使用complex()函数创建一个复合类型,或者使用字面量创建。可以通过real()和imag()获得实数和虚数。complex128对应的实数和虚数分别为float64, 而complex64则对应的为float32.

Complex numbers支持表2.4的算数运算. 但对于比较运算符,只支持== 和!=. 但这同float一样,不是精确的比较.

f := 3.2e5                        // type: float64
x := -7.3 - 8.9i                  // type: complex128 (literal)
y := complex64(-18.3+8.9i)        // type: complex64 (conversion) 
z := complex(f, 13.2)             // type: complex128 (construction)
fmt.Println(x, real(y), imag(z))  // Prints: (-7.3-8.9i) -18.3 13.2

对于complex的运算,可以看math/cmplx package文档