整数和浮点数
整数和浮点数是算术和计算的基本组成部分。这些值的内置表示称为数字基元,而整数和浮点数在代码中作为立即值的表示称为数字字面量。例如,1
是一个整数字面量,而 1.0
是一个浮点字面量;它们在内存中的二进制表示形式作为对象是数字基元。
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 | half | 16 |
Float32 | single | 32 |
Float64 | double | 64 |
此外,对 复数和有理数 的全面支持建立在这些基元数字类型之上。由于灵活且用户可扩展的 类型提升系统,所有数字类型都可以自然地进行交互,无需显式转换。
整数
整数字面量以标准方式表示
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 还定义了类型 Int
和 UInt
,它们分别是系统有符号和无符号本机整数类型的别名
# 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
前缀和十六进制 (16 进制) 数字 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
和0x12
是UInt8
字面量,0x123
和0x1234
是UInt16
字面量,0x12345
和0x12345678
是UInt32
字面量,0x123456789
和0x1234567890adcdef
是UInt64
字面量,等等。
即使存在不影响值的先行零位数字,它们也会被计入确定字面量存储大小的范围。因此 0x01
是一个 UInt8
,而 0x0001
是一个 UInt16
。
这允许用户控制大小。
编码大于 UInt128
值所能表示的整数的无符号字面量 (以 0x
开头) 将改为构造 BigInt
值。这不是无符号类型,但它是唯一能够表示如此大的整数值的内置类型。
二进制、八进制和十六进制字面量可以通过直接位于无符号字面量之前的 -
来签名。它们会生成与无符号字面量相同大小的无符号整数,其值为该值的二进制补码
julia> -0x2
0xfe
julia> -0x0002
0xfffe
基元数字类型(如整数)的最小值和最大值由 typemin
和 typemax
函数给出
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]
typemin
和 typemax
返回的值始终是给定参数类型的。 (上面的表达式使用了一些尚未介绍的功能,包括 for 循环、字符串 和 插值,但对于具有一定编程经验的用户来说应该很容易理解。)
溢出行为
在 Julia 中,超过给定类型最大可表示值的范围会导致循环行为
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
函数) 有两个异常情况:除以零以及将最小负数 (typemin
) 除以 -1。这两种情况都会抛出 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
值。字面量 Float32
值可以通过在 e
的位置写入 f
来输入
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
半精度浮点数也受支持(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"
特殊浮点值
有三个指定的标准浮点值,它们不对应于实数线上的任何点。
Float16 | Float32 | Float64 | 名称 | 描述 |
---|---|---|---|---|
Inf16 | Inf32 | Inf | 正无穷 | 大于所有有限浮点值的值 |
-Inf16 | -Inf32 | -Inf | 负无穷 | 小于所有有限浮点值的值 |
NaN16 | NaN32 | NaN | 非数字 | 一个不等于任何浮点值(包括它自身)的值 |
有关这些非有限浮点值如何相对于彼此和其他浮点数排序的进一步讨论,请参阅 数值比较。根据 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
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> eps(Float32)
1.1920929f-7
julia> eps(Float64)
2.220446049250313e-16
julia> eps() # same as eps(Float64)
2.220446049250313e-16
这些值分别为 2.0^-23
和 2.0^-52
,作为 Float32
和 Float64
值。该 eps
函数还可以采用浮点值作为参数,并给出该值与下一个可表示浮点值之间的绝对差值。也就是说,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 还提供了 nextfloat
和 prevfloat
函数,它们分别返回分别为参数的下一个最大或最小的可表示浮点数。
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 标准 中提供的舍入模式更改此舍入的方式。
始终使用的默认模式是 RoundNearest
,它舍入到最接近的可表示值,其中平局舍入到最接近的具有偶数最低有效位的数值。
背景和参考资料
浮点运算涉及许多微妙之处,这对于不熟悉底层实现细节的用户来说可能会很令人惊讶。但是,这些微妙之处在大多数关于科学计算的书籍以及以下参考文献中都有详细描述。
- 浮点运算的权威指南是 IEEE 754-2008 标准;但是,它不能在线免费获得。
- 有关浮点数表示方式的简短但清晰的说明,请参阅 John D. Cook 关于该主题的 文章 以及他对 介绍 该表示方式与实数理想化抽象在行为方面的差异。
- 还推荐 Bruce Dawson 关于浮点数的 一系列博客文章。
- 有关浮点数和使用它们进行计算时遇到的数值精度问题的出色、深入的讨论,请参阅 David Goldberg 的论文 每个计算机科学家应该了解的关于浮点运算的知识。
- 有关浮点数的历史、原理和问题的更广泛的文档,以及对数值计算中的许多其他主题的讨论,请参阅 收集的著作 的 William Kahan,通常被称为“浮点之父”。可能特别有趣的是 与浮点老人的访谈。
任意精度算术
为了允许使用任意精度的整数和浮点数进行计算,Julia 分别包装了 GNU 多精度算术库 (GMP) 和 GNU MPFR 库。该 BigInt
和 BigFloat
类型在 Julia 中分别用于任意精度的整数和浮点数。
构造函数存在于从原始数值类型创建这些类型,并且该 字符串文字 @big_str
或 parse
可用于从 AbstractString
中构造它们。BigInt
还可以在它们太大而无法用于其他内置整数类型时作为整数字面量输入。请注意,由于 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
操作的默认精度(以有效数字的位数表示)和舍入模式可以通过调用 setprecision
和 setrounding
来全局更改,所有后续计算都将考虑这些更改。或者,可以使用具有 do
块的相同函数,仅在特定代码块的执行范围内更改精度或舍入。
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
数值字面量系数的优先级略低于一元运算符(如否定)。因此,-2x
被解析为 (-2) * x
,而 √2x
被解析为 (√2) * x
。但是,当与求幂结合使用时,数值字面量系数的解析类似于一元运算符。例如,2^3x
被解析为 2^(3x)
,而 2x^3
被解析为 2*(x^3)
。
数值字面量也可以作为括号表达式系数使用,意味着用变量乘以表达式。
julia> 2(x-1)^2 - 3(x-1) + 1
3
用于隐式乘法的数值字面量系数的优先级高于其他二元运算符,如乘法 (*
) 和除法 (/
、\
和 //
)。这意味着,例如,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
。类似的歧义出现在八进制和二进制字面量中,如0o777
或0b01001010
。 - 浮点数字面量表达式
1e10
可以解释为数值字面量1
乘以变量e10
,类似于等效的E
形式。 - 32 位浮点数字面量表达式
1.5f22
可以解释为数值字面量1.5
乘以变量f22
。
在所有情况下,都会以支持将其解释为数值字面量的形式解决歧义。
- 以
0x
/0o
/0b
开头的表达式始终是十六进制/八进制/二进制字面量。 - 以数字字面量后跟
e
或E
开头的表达式始终是浮点字面量。 - 以数字字面量后跟
f
开头的表达式始终是 32 位浮点字面量。
与E
(由于历史原因,在数字字面量中等效于e
)不同,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