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文档