Integers and Floating-Point Numbers

整数和浮点值是算术和计算的基本构建块。这些值的内置表示称为数值原始类型,而整数和浮点数在代码中作为立即值的表示被称为数值字面量。例如,1 是一个整数字面量,而 1.0 是一个浮点字面量;它们在内存中的二进制表示作为对象是数值原始类型。

Julia 提供了广泛的原始数值类型,并且定义了一整套算术和位运算符以及标准数学函数。这些直接映射到现代计算机本地支持的数值类型和操作,从而使 Julia 能够充分利用计算资源。此外,Julia 还提供了对 Arbitrary Precision Arithmetic 的软件支持,可以处理在本地硬件表示中无法有效表示的数值操作,但代价是相对较慢的性能。

以下是Julia的基本数值类型:

  • 整数类型:
TypeSigned?Number of bitsSmallest valueLargest value
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)
  • 浮点类型:
TypePrecisionNumber of bits
Float16half16
Float32single32
Float64double64

此外,Complex and Rational Numbers 的全面支持建立在这些原始数值类型之上。所有数值类型自然地相互操作,无需显式转换,这得益于灵活的、用户可扩展的 type promotion system

Integers

字面整数以标准方式表示:

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 前缀和十六进制(基数 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 的字面量所需的最小大小决定。这意味着:

  • 0x10x12UInt8 字面量,
  • 0x1230x1234UInt16 字面量,
  • 0x123450x12345678UInt32 字面量,
  • 0x1234567890x1234567890adcdefUInt64 字面量,等等。

即使存在不影响数值的前导零数字,它们也会计入字面量的存储大小。因此,0x01UInt8,而 0x0001UInt16

这允许用户控制大小。

无符号字面量(以 0x 开头)编码的整数如果太大而无法表示为 UInt128 值,将构造 BigInt 值。虽然这不是一种无符号类型,但它是唯一足够大的内置类型,可以表示如此大的整数值。

二进制、八进制和十六进制字面量可以通过在无符号字面量前面立即加上 - 来表示为负数。它们产生的无符号整数与无符号字面量的大小相同,值为其二的补数:

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 loopsStringsInterpolation,但对于一些已有编程经验的用户来说应该足够容易理解。)

Overflow behavior

在Julia中,超过给定类型的最大可表示值会导致环绕行为:

julia> x = typemax(Int64)
9223372036854775807

julia> x + 1
-9223372036854775808

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

算术运算与 Julia 的整数类型本质上执行 modular arithmetic,反映了现代计算机硬件上整数运算的特性。在溢出可能发生的情况下,明确检查可能由此类溢出引起的环绕效应至关重要。Base.Checked 模块提供了一套配备溢出检查的算术运算,如果发生溢出则会触发错误。对于在任何情况下都无法容忍溢出的用例,建议使用 BigInt 类型,如 Arbitrary Precision Arithmetic 中详细说明的那样。

溢出行为的一个示例及其潜在解决方案如下:

julia> 10^19
-8446744073709551616

julia> big(10)^19
10000000000000000000

Division errors

整数除法(div 函数)有两个特殊情况:除以零,以及将最低负数 (typemin) 除以 -1。这两种情况都会抛出一个 DivideError。当余数和模数函数(remmod)的第二个参数为零时,也会抛出一个 4d61726b646f776e2e436f64652822222c20224469766964654572726f722229_40726566

Floating-Point Numbers

字面浮点数以标准格式表示,使用 E-notation 在必要时:

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 的二进制指数:

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)

Floating-point zero

浮点数有 two zeros,正零和负零。它们彼此相等,但具有不同的二进制表示,可以使用 bitstring 函数看到这一点:

julia> 0.0 == -0.0
true

julia> bitstring(0.0)
"0000000000000000000000000000000000000000000000000000000000000000"

julia> bitstring(-0.0)
"1000000000000000000000000000000000000000000000000000000000000000"

Special floating-point values

有三个指定的标准浮点值,它们不对应于实数线上的任何点:

Float16Float32Float64NameDescription
Inf16Inf32Infpositive infinitya value greater than all finite floating-point values
-Inf16-Inf32-Infnegative infinitya value less than all finite floating-point values
NaN16NaN32NaNnot a numbera value not == to any floating-point value (including itself)

有关这些非有限浮点值如何相互排序以及与其他浮点值的关系的进一步讨论,请参见 Numeric Comparisons。根据 IEEE 754 standard,这些浮点值是某些算术运算的结果:

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)

Machine epsilon

大多数实数无法用浮点数精确表示,因此在许多情况下,了解两个相邻可表示浮点数之间的距离是很重要的,这通常被称为 machine epsilon

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^-232.0^-52,对应的值为 Float32Float64eps 函数也可以接受一个浮点值作为参数,并给出该值与下一个可表示的浮点值之间的绝对差。也就是说,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"

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

Rounding modes

如果一个数字没有精确的浮点表示,它必须被四舍五入到一个合适的可表示值。然而,如果需要,可以根据在 IEEE 754 standard 中提出的舍入模式来改变这种舍入方式。

默认使用的模式始终是 RoundNearest,它会四舍五入到最接近的可表示值,平局时向最近的具有偶数最低有效位的值四舍五入。

Background and References

浮点运算包含许多微妙之处,这对不熟悉底层实现细节的用户来说可能会令人惊讶。然而,这些微妙之处在大多数科学计算书籍中都有详细描述,并且在以下参考文献中也有说明:

Arbitrary Precision Arithmetic

为了允许对任意精度整数和浮点数进行计算,Julia 包装了 GNU Multiple Precision Arithmetic Library (GMP)GNU MPFR LibraryBigIntBigFloat 类型在 Julia 中分别用于任意精度整数和浮点数。

构造函数用于从原始数值类型创建这些类型,string literal@big_strparse 可以用于从 AbstractString 构造它们。当 BigInt 太大而无法适应其他内置整数类型时,也可以将其作为整数字面量输入。请注意,由于 Base 中没有无符号任意精度整数类型(在大多数情况下 BigInt 足够),可以使用十六进制、八进制和二进制字面量(除了十进制字面量)。

一旦创建,它们就可以与所有其他数值类型进行算术运算,这要归功于 Julia 的 type promotion and conversion mechanism

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 块:

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
Warning

setprecisionsetrounding@big_str 之间的关系,可能并不直观,因为 @big_str 是一个宏。有关详细信息,请参阅 4d61726b646f776e2e436f64652822222c2022406269675f7374722229_40726566 文档。

Numeric Literal Coefficients

为了使常见的数字公式和表达式更清晰,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
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

这两个表达式都被解释为函数应用:任何不是数字字面量的表达式,当紧接着一个括号时,都被解释为一个函数应用于括号中的值(有关函数的更多信息,请参见 Functions)。因此,在这两种情况下都会发生错误,因为左侧的值不是一个函数。

上述语法增强显著减少了在书写常见数学公式时产生的视觉噪音。请注意,数字字面量系数与其所乘的标识符或括号表达式之间不得有空格。

Syntax Conflicts

并排的字面系数语法可能与某些数字字面语法发生冲突:十六进制、八进制和二进制整数字面以及浮点字面的工程记数法。以下是一些出现语法冲突的情况:

  • 十六进制整数字面量表达式 0xff 可以被解释为数字字面量 0 乘以变量 xff。类似的歧义也出现在八进制和二进制字面量中,例如 0o7770b01001010
  • 浮点字面量表达式 1e10 可以被解释为数字字面量 1 乘以变量 e10,同样适用于等效的 E 形式。
  • 32位浮点字面量表达式 1.5f22 可以被解释为数字字面量 1.5 乘以变量 f22

在所有情况下,歧义都以将其解释为数字字面量的方式得到解决:

  • 0x/0o/0b 开头的表达式始终是十六进制/八进制/二进制字面量。
  • 以数字字面量开头,后跟 eE 的表达式始终是浮点字面量。
  • 以数字字面量开头并后跟 f 的表达式始终是 32 位浮点字面量。

E 不同,E 在数字字面量中出于历史原因等同于 e,而 F 只是另一个字母,并且在数字字面量中不表现得像 f。因此,以数字字面量开头后跟 F 的表达式被解释为数字字面量乘以一个变量,这意味着,例如,1.5F22 等于 1.5 * F22

Literal zero and one

Julia 提供了返回与指定类型或给定变量类型相对应的字面量 0 和 1 的函数。

FunctionDescription
zero(x)Literal zero of type x or type of variable x
one(x)Literal one of type x or type of variable x

这些函数在 Numeric Comparisons 中非常有用,以避免不必要的 type conversion 带来的开销。

示例:

julia> zero(Float32)
0.0f0

julia> zero(1.0)
0.0

julia> one(Int32)
1

julia> one(BigFloat)
1.0