整数和浮点数

整数和浮点数

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

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

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

类型带符号?比特数最小值最大值
Int88-2^72^7 - 1
UInt8802^8 - 1
Int1616-2^152^15 - 1
UInt161602^16 - 1
Int3232-2^312^31 - 1
UInt323202^32 - 1
Int6464-2^632^63 - 1
UInt646402^64 - 1
Int128128-2^1272^127 - 1
UInt12812802^128 - 1
BoolN/A8false (0)true (1)
类型精度比特数
Float16half16
Float32single32
Float64double64

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

整数

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

julia> 1
1

julia> 1234
1234

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

# 32-bit system:
julia> typeof(1)
Int32

# 64-bit system:
julia> typeof(1)
Int64

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

# 32-bit system:
julia> Sys.WORD_SIZE
32

# 64-bit system:
julia> Sys.WORD_SIZE
64

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

# 32-bit system:
julia> Int
Int32
julia> UInt
UInt32

# 64-bit system:
julia> Int
Int64
julia> UInt
UInt64

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

# 32-bit or 64-bit system:
julia> typeof(3000000000)
Int64

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

julia> 0x1
0x01

julia> typeof(ans)
UInt8

julia> 0x123
0x0123

julia> typeof(ans)
UInt16

julia> 0x1234567
0x01234567

julia> typeof(ans)
UInt32

julia> 0x123456789abcdef
0x0123456789abcdef

julia> typeof(ans)
UInt64

julia> 0x11112222333344445555666677778888
0x11112222333344445555666677778888

julia> typeof(ans)
UInt128

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

还记得这个 ans 变量吗?它存着交互式会话中上一个表达式的运算结果,但以其他方式运行的 Julia 代码中没有这个变量。

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

julia> 0b10
0x02

julia> typeof(ans)
UInt8

julia> 0o010
0x08

julia> typeof(ans)
UInt8

julia> 0x00000000000000001111222233334444
0x00000000000000001111222233334444

julia> typeof(ans)
UInt128

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

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

julia> -0x2
0xfe

julia> -0x0002
0xfffe

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

julia> (typemin(Int32), typemax(Int32))
(-2147483648, 2147483647)

julia> for T in [Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128]
           println("$(lpad(T,7)): [$(typemin(T)),$(typemax(T))]")
       end
   Int8: [-128,127]
  Int16: [-32768,32767]
  Int32: [-2147483648,2147483647]
  Int64: [-9223372036854775808,9223372036854775807]
 Int128: [-170141183460469231731687303715884105728,170141183460469231731687303715884105727]
  UInt8: [0,255]
 UInt16: [0,65535]
 UInt32: [0,4294967295]
 UInt64: [0,18446744073709551615]
UInt128: [0,340282366920938463463374607431768211455]

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

溢出行为

Julia 中,超出一个类型可表示的最大值会导致循环行为:

julia> x = typemax(Int64)
9223372036854775807

julia> x + 1
-9223372036854775808

julia> x + 1 == typemin(Int64)
true

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

除法错误

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

浮点数

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

julia> 1.0
1.0

julia> 1.
1.0

julia> 0.5
0.5

julia> .5
0.5

julia> -1.23
-1.23

julia> 1e10
1.0e10

julia> 2.5e-4
0.00025

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

julia> 0.5f0
0.5f0

julia> typeof(ans)
Float32

julia> 2.5f-4
0.00025f0

数值容易就能转换成 Float32

julia> Float32(-1.5)
-1.5f0

julia> typeof(ans)
Float32

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

julia> 0x1p0
1.0

julia> 0x1.8p3
12.0

julia> 0x.4p-1
0.125

julia> typeof(ans)
Float64

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

julia> sizeof(Float16(4.))
2

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

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

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

浮点数中的零

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

julia> 0.0 == -0.0
true

julia> bitstring(0.0)
"0000000000000000000000000000000000000000000000000000000000000000"

julia> bitstring(-0.0)
"1000000000000000000000000000000000000000000000000000000000000000"

特殊的浮点值

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

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

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

julia> 1/Inf
0.0

julia> 1/0
Inf

julia> -5/0
-Inf

julia> 0.000001/0
Inf

julia> 0/0
NaN

julia> 500 + Inf
Inf

julia> 500 - Inf
-Inf

julia> Inf + Inf
Inf

julia> Inf - Inf
NaN

julia> Inf * Inf
Inf

julia> Inf / Inf
NaN

julia> 0 * Inf
NaN

typemintypemax 函数同样适用于浮点类型:

julia> (typemin(Float16),typemax(Float16))
(-Inf16, Inf16)

julia> (typemin(Float32),typemax(Float32))
(-Inf32, Inf32)

julia> (typemin(Float64),typemax(Float64))
(-Inf, Inf)

机器精度

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

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

julia> eps(Float32)
1.1920929f-7

julia> eps(Float64)
2.220446049250313e-16

julia> eps() # same as eps(Float64)
2.220446049250313e-16

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

julia> eps(1.0)
2.220446049250313e-16

julia> eps(1000.)
1.1368683772161603e-13

julia> eps(1e-27)
1.793662034335766e-43

julia> eps(0.0)
5.0e-324

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

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

julia> x = 1.25f0
1.25f0

julia> nextfloat(x)
1.2500001f0

julia> prevfloat(x)
1.2499999f0

julia> bitstring(prevfloat(x))
"00111111100111111111111111111111"

julia> bitstring(x)
"00111111101000000000000000000000"

julia> bitstring(nextfloat(x))
"00111111101000000000000000000001"

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

舍入模式

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

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

基础知识与参考文献

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

任意精度算术

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

存在从原始数字类型创建它们的构造器,也可以使用 parseAbstractString 来构造它们。一旦被创建,它们就可以像所有其它数值类型一样参与算术(也是多亏了 Julia 的类型提升和转换机制)。

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

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

julia> parse(BigFloat, "1.23456789012345678901")
1.234567890123456789010000000000000000000000000000000000000000000000000000000004

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

julia> factorial(BigInt(40))
815915283247897734345611269596115894272000000000

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

julia> x = typemin(Int64)
-9223372036854775808

julia> x = x - 1
9223372036854775807

julia> typeof(x)
Int64

julia> y = BigInt(typemin(Int64))
-9223372036854775808

julia> y = y - 1
-9223372036854775809

julia> typeof(y)
BigInt

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

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

julia> setrounding(BigFloat, RoundDown) do
           BigFloat(1) + parse(BigFloat, "0.1")
       end
1.099999999999999999999999999999999999999999999999999999999999999999999999999986

julia> setprecision(40) do
           BigFloat(1) + parse(BigFloat, "0.1")
       end
1.1000000000004

数值字面量系数

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

julia> x = 3
3

julia> 2x^2 - 3x + 1
10

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

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

julia> 2^2x
64

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

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

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

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

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

julia> (x-1)x
6

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

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

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

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

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

语法冲突

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

在这些所有的情况中,都使用这样的解释方法来解决二义性:

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

零和一的字面量

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

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

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

例如:

julia> zero(Float32)
0.0f0

julia> zero(1.0)
0.0

julia> one(Int32)
1

julia> one(BigFloat)
1.0