整数和浮点数

整数和浮点值是算术和计算的基础。这些数值的内置表示被称作原始数值类型(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)
  • 浮点类型:
类型精度比特数
Float16半精度16
Float32单精度32
Float64双精度64

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

整数

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

julia> 1
1

julia> 1234
1234

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

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

# 64 位系统:
julia> typeof(1)
Int64

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

# 32 位系统:
julia> Sys.WORD_SIZE
32

# 64 位系统:
julia> Sys.WORD_SIZE
64

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

# 32 位系统:
julia> Int
Int32
julia> UInt
UInt32

# 64 位系统:
julia> Int
Int64
julia> UInt
UInt64

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

# 32 位或 64 位系统:
julia> typeof(3000000000)
Int64

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

julia> x = 0x1
0x01

julia> typeof(x)
UInt8

julia> x = 0x123
0x0123

julia> typeof(x)
UInt16

julia> x = 0x1234567
0x01234567

julia> typeof(x)
UInt32

julia> x = 0x123456789abcdef
0x0123456789abcdef

julia> typeof(x)
UInt64

julia> x = 0x11112222333344445555666677778888
0x11112222333344445555666677778888

julia> typeof(x)
UInt128

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

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

julia> x = 0b10
0x02

julia> typeof(x)
UInt8

julia> x = 0o010
0x08

julia> typeof(x)
UInt8

julia> x = 0x00000000000000001111222233334444
0x00000000000000001111222233334444

julia> typeof(x)
UInt128

与十六进制字面量一样,二进制、八进制和十六进制的字面量都会产生无符号的整数类型。 当字面量不是开头全是 0 时,它们二进制数据项的位数会是最少需要的位数。 在前导为零的情况下,大小由具有相同长度但前导数字为 1 的字面量所需的最小大小决定。 这意味着:

  • 0x1 and 0x12 are UInt8 literals,
  • 0x123 and 0x1234 are UInt16 literals,
  • 0x12345 and 0x12345678 are UInt32 literals,
  • 0x123456789 and 0x1234567890adcdef are UInt64 literals, etc.

Even if there are leading zero digits which don’t contribute to the value, they count for determining storage size of a literal. So 0x01 is a UInt8 while 0x0001 is a UInt16.

That allows the user to control the size.

Unsigned literals (starting with 0x) that encode integers too large to be represented as UInt128 values will construct BigInt values instead. This is not an unsigned type but it is the only built-in type big enough to represent such large integer values.

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

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 里,超出一个类型可表示的最大值会导致环绕 (wraparound) 行为:

julia> x = typemax(Int64)
9223372036854775807

julia> x + 1
-9223372036854775808

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

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

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

julia> 10^19
-8446744073709551616

julia> big(10)^19
10000000000000000000

除法错误

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> x = 0.5f0
0.5f0

julia> typeof(x)
Float32

julia> 2.5f-4
0.00025f0

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

julia> x = Float32(-1.5)
-1.5f0

julia> typeof(x)
Float32

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

julia> 0x1p0
1.0

julia> 0x1.8p3
12.0

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

julia> typeof(x)
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

julia> NaN == NaN
false

julia> NaN != NaN
true

julia> NaN < NaN
false

julia> NaN > NaN
false

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() # 与 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 两种类型分别提供了任意精度的整数和浮点数。

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

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

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

julia> big"123456789012345678901234567890" + 1
123456789012345678901234567891

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

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

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

julia> 0x000000000000000000000000000000000
0

julia> typeof(ans)
BigInt

julia> big"1.23456789012345678901"
1.234567890123456789010000000000000000000000000000000000000000000000000000000004

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

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

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

语法冲突

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

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

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

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

由于历史原因 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