整数和浮点数

整数和浮点值是算术和计算的基础。这些数值的内置表示被称作原始数值类型(numeric primitive),且整数和浮点数在代码中作为立即数时称作数值字面量(numeric literal)。例如,1 是个整型字面量,1.0 是个浮点型字面量,它们在内存中作为对象的二进制表示就是原始数值类型。

Julia 提供了很丰富的原始数值类型,并基于它们定义了一整套算术操作,还提供按位运算符以及一些标准数学函数。这些函数能够直接映射到现代计算机原生支持的数值类型及运算上,因此 Julia 可以充分地利用运算资源。此外,Julia 还为任意精度算术提供了软件支持,对于无法使用原生硬件表示的数值类型,Julia 也能够高效地处理其数值运算。当然,这需要相对的牺牲一些性能。

以下是 Julia 的原始数值类型:

  • 整数类型:
类型 带符号? 比特数 最小值 最大值
Int8 ✓ 8 -2^7 2^7 - 1
UInt8 8 0 2^8 - 1
Int16 ✓ 16 -2^15 2^15 - 1
UInt16 16 0 2^16 - 1
Int32 ✓ 32 -2^31 2^31 - 1
UInt32 32 0 2^32 - 1
Int64 ✓ 64 -2^63 2^63 - 1
UInt64 64 0 2^64 - 1
Int128 ✓ 128 -2^127 2^127 - 1
UInt128 128 0 2^128 - 1
Bool N/A 8 false (0) true (1)
  • 浮点类型:
类型 精度 比特数
Float16 半精度 16
Float32 单精度 32
Float64 双精度 64

此外,对复数和有理数的完整支持是在这些原始数据类型之上建立起来的。多亏了 Julia 有一个很灵活的、用户可扩展的类型提升系统,所有的数值类型都无需显式转换就可以很自然地相互进行运算。

整数

整数字面量以标准形式表示:

1. julia> 1
2. 1

4. julia> 1234
5. 1234

整型字面量的默认类型取决于目标系统是 32 位还是 64 位架构:

1. # 32 位系统:
2. julia> typeof(1)
3. Int32

5. # 64 位系统:
6. julia> typeof(1)
7. Int64

Julia 的内置变量 Sys.WORD_SIZE 表明了目标系统是 32 位还是 64 位架构:

1. # 32 位系统:
2. julia> Sys.WORD_SIZE
3. 32

5. # 64 位系统:
6. julia> Sys.WORD_SIZE
7. 64

Julia 也定义了 Int 与 UInt 类型,它们分别是系统有符号和无符号的原生整数类型的别名。

1. # 32 位系统:
2. julia> Int
3. Int32
4. julia> UInt
5. UInt32

7. # 64 位系统:
8. julia> Int
9. Int64
10. julia> UInt
11. UInt64

那些超过 32 位表示范围的大整数,如果能用 64 位表示,那么无论是什么系统都会用 64 位表示:

1. # 32 位或 64 位系统:
2. julia> typeof(3000000000)
3. Int64

无符号整数会通过 0x 前缀以及十六进制数 0-9a-f 来输入和输出(输入也可以使用大写的 A-F)。无符号值的位数取决于十六进制数字使用的数量:

1. julia> x = 0x1
2. 0x01

4. julia> typeof(x)
5. UInt8

7. julia> x = 0x123
8. 0x0123

10. julia> typeof(x)
11. UInt16

13. julia> x = 0x1234567
14. 0x01234567

16. julia> typeof(x)
17. UInt32

19. julia> x = 0x123456789abcdef
20. 0x0123456789abcdef

22. julia> typeof(x)
23. UInt64

25. julia> x = 0x11112222333344445555666677778888
26. 0x11112222333344445555666677778888

28. julia> typeof(x)
29. UInt128

采用这种做法是因为,当人们使用无符号十六进制字面量表示整数值的时候,通常会用它们来表示一个固定的数值字节序列,而不仅仅是个整数值。

二进制和八进制字面量也是支持的:

1. julia> x = 0b10
2. 0x02

4. julia> typeof(x)
5. UInt8

7. julia> x = 0o010
8. 0x08

10. julia> typeof(x)
11. UInt8

13. julia> x = 0x00000000000000001111222233334444
14. 0x00000000000000001111222233334444

16. julia> typeof(x)
17. UInt128

二进制、八进制和十六进制的字面量都会产生无符号的整数类型。当字面量不是开头全是 0 时,它们二进制数据项的位数会是最少需要的位数。当开头都是 0 时,位数取决于一个字面量需要的最少位数,这里的字面量指的是一个有着同样长度但开头都为 1 的数。这样用户就可以控制位数了。那些无法使用 UInt128 类型存储下的值无法写成这样的字面量。

二进制、八进制和十六进制的字面量前面加一个负号 -,这样可以产生一个和原字面量有着同样位数而值为原数的补码的数(二补数):

1. julia> -0x2
2. 0xfe

4. julia> -0x0002
5. 0xfffe

整型等原始数值类型的最小和最大可表示的值可用 typemin 和 typemax 函数得到:

1. julia> (typemin(Int32), typemax(Int32))
2. (-2147483648, 2147483647)

4. julia> for T in [Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128]
5. println("$(lpad(T,7)): [$(typemin(T)),$(typemax(T))]")
6. end
7. Int8: [-128,127]
8. Int16: [-32768,32767]
9. Int32: [-2147483648,2147483647]
10. Int64: [-9223372036854775808,9223372036854775807]
11. Int128: [-170141183460469231731687303715884105728,170141183460469231731687303715884105727]
12. UInt8: [0,255]
13. UInt16: [0,65535]
14. UInt32: [0,4294967295]
15. UInt64: [0,18446744073709551615]
16. UInt128: [0,340282366920938463463374607431768211455]

typemin 和 typemax 返回的值的类型总与所给参数的类型相同。(上面的表达式用了一些目前还没有介绍的功能,包括 for 循环、字符串和字符串插值,但这对于已有一些编程经验的用户应该是很容易理解的。)

溢出行为

在 Julia 里,超出一个类型可表示的最大值会导致环绕 (wraparound) 行为:

1. julia> x = typemax(Int64)
2. 9223372036854775807

4. julia> x + 1
5. -9223372036854775808

7. julia> x + 1 == typemin(Int64)
8. true

因此,Julia 的整数算术实际上是模算数的一种形式,它反映了现代计算机实现底层算术的特点。在可能有溢出产生的程序中,对最值边界出现循环进行显式检查是必要的。否则,推荐使用任意精度算术中的 BigInt 类型作为替代。

下面是溢出行为的一个例子以及如何解决溢出:

1. julia> 10^19
2. -8446744073709551616

4. julia> big(10)^19
5. 10000000000000000000

除法错误

div 函数的整数除法有两种异常情况:除以零,以及使用 -1 去除最小的负数(typemin)。 这两种情况都会抛出一个 DivideError 错误。 rem 取余函数和 mod 取模函数在除零时抛出 DivideError 错误。

浮点数

浮点数字面量以标准格式表示,必要时可使用E-表示法。

1. julia> 1.0
2. 1.0

4. julia> 1.
5. 1.0

7. julia> 0.5
8. 0.5

10. julia> .5
11. 0.5

13. julia> -1.23
14. -1.23

16. julia> 1e10
17. 1.0e10

19. julia> 2.5e-4
20. 0.00025

上面的结果都是 Float64 类型的值。使用 f 替代 e 可以得到 Float32 类型的字面量:

1. julia> x = 0.5f0
2. 0.5f0

4. julia> typeof(x)
5. Float32

7. julia> 2.5f-4
8. 0.00025f0

数值可以很容易地转换为 Float32 类型:

1. julia> x = Float32(-1.5)
2. -1.5f0

4. julia> typeof(x)
5. Float32

也存在十六进制的浮点数字面量,但只适用于 Float64 类型的值。一般使用 p 前缀及以 2 为底的指数来表示:

1. julia> 0x1p0
2. 1.0

4. julia> 0x1.8p3
5. 12.0

7. julia> x = 0x.4p-1
8. 0.125

10. julia> typeof(x)
11. Float64

Julia 也支持半精度浮点数(Float16),但它们是使用 Float32 进行软件模拟实现的。

1. julia> sizeof(Float16(4.))
2. 2

4. julia> 2*Float16(4.)
5. Float16(8.0)

下划线 _ 可用作数字分隔符:

1. julia> 10_000, 0.000_000_005, 0xdead_beef, 0b1011_0010
2. (10000, 5.0e-9, 0xdeadbeef, 0xb2)

浮点数中的零

浮点数有两种零,正零和负零。它们相互相等但有着不同的二进制表示,可以使用 bitstring 函数来查看:

1. julia> 0.0 == -0.0
2. true

4. julia> bitstring(0.0)
5. "0000000000000000000000000000000000000000000000000000000000000000"

7. julia> bitstring(-0.0)
8. "1000000000000000000000000000000000000000000000000000000000000000"

特殊的浮点值

有三种特定的标准浮点值不和实数轴上任何一点对应:

Float16 Float32 Float64 名称 描述
Inf16 Inf32 Inf 正无穷 一个大于所有有限浮点数的数
-Inf16 -Inf32 -Inf 负无穷 一个小于所有有限浮点数的数
NaN16 NaN32 NaN 不是数(Not a Number) 一个不和任何浮点值(包括自己)相等(==)的值

对于这些非有限浮点值相互之间以及关于其它浮点值的顺序的更多讨论,请参见数值比较。根据 IEEE 754 标准,这些浮点值是某些算术运算的结果:

1. julia> 1/Inf
2. 0.0

4. julia> 1/0
5. Inf

7. julia> -5/0
8. -Inf

10. julia> 0.000001/0
11. Inf

13. julia> 0/0
14. NaN

16. julia> 500 + Inf
17. Inf

19. julia> 500 - Inf
20. -Inf

22. julia> Inf + Inf
23. Inf

25. julia> Inf - Inf
26. NaN

28. julia> Inf * Inf
29. Inf

31. julia> Inf / Inf
32. NaN

34. julia> 0 * Inf
35. NaN

37. julia> NaN == NaN
38. false

40. julia> NaN != NaN
41. true

43. julia> NaN < NaN
44. false

46. julia> NaN > NaN
47. false

typemin 和 typemax 函数同样适用于浮点类型:

1. julia> (typemin(Float16),typemax(Float16))
2. (-Inf16, Inf16)

4. julia> (typemin(Float32),typemax(Float32))
5. (-Inf32, Inf32)

7. julia> (typemin(Float64),typemax(Float64))
8. (-Inf, Inf)

机器精度

大多数实数都无法用浮点数准确地表示,因此有必要知道两个相邻可表示的浮点数间的距离。它通常被叫做机器精度。

Julia 提供了 eps 函数,它可以给出 1.0 与下一个 Julia 能表示的浮点数之间的差值:

1. julia> eps(Float32)
2. 1.1920929f-7

4. julia> eps(Float64)
5. 2.220446049250313e-16

7. julia> eps() # 与 eps(Float64) 相同
8. 2.220446049250313e-16

这些值分别是 Float32 中的 2.0^-23 和 Float64 中的 2.0^-52。eps 函数也可以接受一个浮点值作为参数,然后给出这个值与下一个可表示的浮点数值之间的绝对差。也就是说,eps(x) 产生一个和 x 类型相同的值,并且 x + eps(x) 恰好是比 x 更大的下一个可表示的浮点值:

1. julia> eps(1.0)
2. 2.220446049250313e-16

4. julia> eps(1000.)
5. 1.1368683772161603e-13

7. julia> eps(1e-27)
8. 1.793662034335766e-43

10. julia> eps(0.0)
11. 5.0e-324

两个相邻可表示的浮点数之间的距离并不是常数,数值越小,间距越小,数值越大,间距越大。换句话说,可表示的浮点数在实数轴上的零点附近最稠密,并沿着远离零点的方向以指数型的速度变得越来越稀疏。根据定义,eps(1.0) 与 eps(Float64) 相等,因为 1.0 是个 64 位浮点值。

Julia 也提供了 nextfloat 和 prevfloat 两个函数分别返回基于参数的下一个更大或更小的可表示的浮点数:

1. julia> x = 1.25f0
2. 1.25f0

4. julia> nextfloat(x)
5. 1.2500001f0

7. julia> prevfloat(x)
8. 1.2499999f0

10. julia> bitstring(prevfloat(x))
11. "00111111100111111111111111111111"

13. julia> bitstring(x)
14. "00111111101000000000000000000000"

16. julia> bitstring(nextfloat(x))
17. "00111111101000000000000000000001"

这个例子体现了一般原则,即相邻可表示的浮点数也有着相邻的二进制整数表示。

舍入模式

一个数如果没有精确的浮点表示,就必须被舍入到一个合适的可表示的值。然而,如果想的话,可以根据舍入模式改变舍入的方式,如 IEEE 754 标准 所述。

Julia 所使用的默认模式总是 RoundNearest,指舍入到最接近的可表示的值,这个被舍入的值会使用尽量少的有效位数。

背景知识与参考文献

浮点算术带来了很多微妙之处,它们可能对于那些不熟悉底层实现细节的用户会是很出人意料的。然而,这些微妙之处在大部分科学计算的书籍中以及以下的参考资料中都有详细介绍:

任意精度算术

为了允许使用任意精度的整数与浮点数,Julia 分别包装了 GNU Multiple Precision Arithmetic Library (GMP) 以及 GNU MPFR Library。Julia 中的 BigInt 与 BigFloat 两种类型分别提供了任意精度的整数和浮点数。

可以使用构造函数从原始数据类型创建任意精度的整数和浮点数,或是使用字符串字面量 @big_str、parse函数从 AbstractString 类型来创建。当创建的整型字面量过大而无法被其它内置的整数类型表示时,也会被输入为 BigInt 类型。注意到 Julia 的Base中并没有无符号的任意精度的整型(BigInt在多数情况下已经够用了),(除十进制外)也可以使用十六进制、八进制、二进制的字面量。

得益于 Julia 的类型提升和类型转换机制,一旦被创建,它们就可以像其它数值类型一样参与算术运算。

1. julia> BigInt(typemax(Int64)) + 1
2. 9223372036854775808

4. julia> big"123456789012345678901234567890" + 1
5. 123456789012345678901234567891

7. julia> parse(BigInt, "123456789012345678901234567890") + 1
8. 123456789012345678901234567891

10. julia> string(big"2"^200, base=16)
11. "100000000000000000000000000000000000000000000000000"

13. julia> 0x100000000000000000000000000000000-1 == typemax(UInt128)
14. true

16. julia> 0x000000000000000000000000000000000
17. 0

19. julia> typeof(ans)
20. BigInt

22. julia> big"1.23456789012345678901"
23. 1.234567890123456789010000000000000000000000000000000000000000000000000000000004

25. julia> parse(BigFloat, "1.23456789012345678901")
26. 1.234567890123456789010000000000000000000000000000000000000000000000000000000004

28. julia> BigFloat(2.0^66) / 3
29. 2.459565876494606882133333333333333333333333333333333333333333333333333333333344e+19

31. julia> factorial(BigInt(40))
32. 815915283247897734345611269596115894272000000000

然而,上面的原始类型与 BigInt/BigFloat 之间的类型提升并不是自动的,需要明确地指定:

1. julia> x = typemin(Int64)
2. -9223372036854775808

4. julia> x = x - 1
5. 9223372036854775807

7. julia> typeof(x)
8. Int64

10. julia> y = BigInt(typemin(Int64))
11. -9223372036854775808

13. julia> y = y - 1
14. -9223372036854775809

16. julia> typeof(y)
17. BigInt

BigFloat 的默认精度(有效数字的位数)和舍入模式可以通过调用 setprecision 和 setrounding 来全局地改变,所有之后的计算都会根据这些改变进行。还有一种方法,可以使用同样的函数以及 do-block 来只在运行一个特定代码块时改变精度和舍入模式:

1. julia> setrounding(BigFloat, RoundUp) do
2. BigFloat(1) + parse(BigFloat, "0.1")
3. end
4. 1.100000000000000000000000000000000000000000000000000000000000000000000000000003

6. julia> setrounding(BigFloat, RoundDown) do
7. BigFloat(1) + parse(BigFloat, "0.1")
8. end
9. 1.099999999999999999999999999999999999999999999999999999999999999999999999999986

11. julia> setprecision(40) do
12. BigFloat(1) + parse(BigFloat, "0.1")
13. end
14. 1.1000000000004

数值字面量系数

为了让常见的数值公式和表达式更清楚,Julia 允许变量直接跟在一个数值字面量后,暗指乘法。这可以让写多项式变得很清楚:

1. julia> x = 3
2. 3

4. julia> 2x^2 - 3x + 1
5. 10

7. julia> 1.5x^2 - .5x + 1
8. 13.0

也会让写指数函数变得更加优雅:

1. julia> 2^2x
2. 64

数值字面量系数的优先级跟一元运算符相同,比如说取相反数。所以 2^3x 会被解析成 2^(3x),而 2x^3 会被解析成 2*(x^3)。

数值字面量也能作为被括号表达式的系数:

1. julia> 2(x-1)^2 - 3(x-1) + 1
2. 3

Note

用于隐式乘法的数值字面量系数的优先级高于其它的二元运算符,例如乘法(*)和除法(/、\ 以及 //)。这意味着,比如说,1 / 2im 等于 -0.5im 以及 6 // 2(2+1) 等于 1 // 1。

此外,括号表达式可以被用作变量的系数,暗指表达式与变量相乘:

1. julia> (x-1)x
2. 6

但是,无论是把两个括号表达式并列,还是把变量放在括号表达式之前,都不会被用作暗指乘法:

1. julia> (x-1)(x+1)
2. ERROR: MethodError: objects of type Int64 are not callable

4. julia> x(x+1)
5. ERROR: MethodError: objects of type Int64 are not callable

这两种表达式都会被解释成函数调用:所有不是数值字面量的表达式,后面紧跟一个括号,就会被解释成使用括号内的值来调用函数(更多关于函数的信息请参见函数)。因此,在这两种情况中,都会因为左边的值并不是函数而产生错误。

上述的语法糖显著地降低了在写普通数学公式时的视觉干扰。注意数值字面量系数和后面用来相乘的标识符或括号表达式之间不能有空格。

语法冲突

并列的字面量系数语法可能和两种数值字面量语法产生冲突:十六进制、八进制、二进制整数字面量以及浮点字面量的工程表示法。下面是几种会产生语法冲突的情况:

  • 十六进制整数字面量 0xff 可能被解释成数值字面量 0 乘以变量 xff。类似的,像 0o777 或0b01001010 使用八进制或二进制表示法也会形成冲突。
  • 浮点字面量表达式 1e10 可以被解释成数值字面量 1 乘以变量 e10,与之等价的 E-表示法也存在类似的情况。
  • 32-bit 的浮点数字面量 1.5f22 被解释成数值字面量 1.5 乘以变量 f22。

在这些所有的情况中,歧义都优先解释为数值字面量:

  • 0x /0o/0b 开头的表达式总是十六进制/八进制/二进制字面量。
  • 数值开头跟着 e 和 E 的表达式总是浮点字面量。
  • 数值开头跟着 f 的表达式总是 32-bit 浮点字面量。

由于历史原因 E 和 e 在数值字面量上是等价的,与之不同的是,F 只是一个行为和 f 不同的字母。因此开头为 F 的表达式将会被 解析为一个数值字面量乘以一个变量,例如 1.5F22等价于 1.5 * F22。

零和一的字面量

Julia 提供了 0 和 1 的字面量函数,可以返回特定类型或所给变量的类型。

函数 描述
zero(x) x 类型或变量 x 的类型的零字面量
one(x) x 类型或变量 x 的类型的一字面量

这些函数在数值比较中可以用来避免不必要的类型转换带来的开销。

例如:

1. julia> zero(Float32)
2. 0.0f0

4. julia> zero(1.0)
5. 0.0

7. julia> one(Int32)
8. 1

10. julia> one(BigFloat)
11. 1.0