整数类型的名称大都很直观,并且它们的宽度也都已经体现在名称中了。详见下表。

    表 5-1 整数类型及其取值

    我们最好记住该表中各个类型的最小值和最大值。这并不困难,因为它们是有规律可循的。不过,实在记不住也没有关系。通过调用typemin函数和typemax函数,我们可以分别获得某一个整数类型能够表示的最小值和最大值,例如:

    严格来说,Bool类型也属于整数类型。因为它与SignedUnsigned一样,也是Integer类型的直接子类型。Bool类型的宽度(也就是其值占用的比特数)是8,最小值是0(即false),最大值是1(即true)。

    此外,Julia 还定义了IntUIntInt代表了有符号整数的默认类型。在 32 位的计算机系统中,它们分别是Int32UInt32的别名。而在 64 位的计算机系统中,它们分别是Int64UInt64的别名。如此一来,我们在 REPL 环境中随便输入一个整数字面量,就能猜出它的类型:

    1. julia> typeof(2020) # 在 32 位的计算机系统中
    2. Int32
    3. julia>
    1. julia> typeof(2020) # 在 64 位的计算机系统中
    2. Int64
    3. julia>

    这完全取决于我们的计算机系统的位数(或者说字宽)。顺便说一句,我们可以通过访问常量Sys.WORD_SIZE来获知自己的计算机系统的字宽。

    不过,对于较大的整数,Julia 会自动使用宽度更大的整数类型,例如:

    1. julia> typeof(1234567890123456789)
    2. Int64
    3. julia> typeof(12345678901234567890)
    4. Int128
    5. julia>

    注意,在这个例子中,整数字面量的类型是否为Int128,依据的不是字面量的长度,而是字面量表示的整数是否大于Int64类型的最大值。

    与前面的有符号整数不同,无符号整数会使用以0x为前缀的十六进制形式来表示。比如:

    我们都知道,在这些十六进制整数中,字母af分别代表了十进制整数1015,并且大写这些字母也是可以的。注意,无符号整数值的类型会由字面量本身决定:

    1. julia> typeof(0x01)
    2. UInt8
    3. julia> typeof(0x001)
    4. UInt16
    5. julia> typeof(0x00001)
    6. UInt32
    7. julia> typeof(0x00000000000000001)
    8. UInt128
    9. julia>

    除了十六进制之外,我们还可以使用二进制或八进制的形式来表示无符号整数值。比如:

    1. julia> 0b00000001
    2. 0x01
    3. julia> 0o001
    4. 0x01
    5. julia>

    0b为前缀的整数字面量就是以二进制形式表示的整数,而以0o为前缀的整数字面量则是以八进制形式表示的整数。在这里,数字1至少需要 8 位的二进制数或 3 位的八进制数或 2 位的十六进制数来表示。即使我们输入的位数不够也没有关系,Julia 会自动帮我们在高位补0以填满至相应类型的位数(这里是 8 个比特):

    1. julia> 0b001
    2. 0x01
    3. julia> 0o01
    4. 0x01
    5. julia> 0x1
    6. 0x01
    7. julia>

    对于更大的无符号整数值的字面量来说也是类似的。

    注意,二进制、八进制和十六进制的字面量可以表示无符号的整数值,但不能表示有符号的整数值。虽然我们可以在这些字面量的前面添加负号-,但是它们表示的依然是无符号的整数值。例如:

    不要被字面量-0x01中的负号迷惑,它表示的值的类型仍然是UInt80xff实际上是负的0x01(也就是-1)的补码。但由于十六进制字面量表示的整数值只能是无符号的,所以 Julia 会把它视为一个无符号整数值的原码。如此一来,字面量-0x01代表的整数值就是255

    顺便说一下,我们可以使用下划线_作为数值字面量中的数字分隔符。至于划分的具体间隔,Julia 并没有做硬性的规定。例如:

    1. julia> 2_020, 0x000_01, 0b000_000_01, -0x0_1
    2. (2020, 0x00000001, 0x01, 0xff)
    3. julia>

    我们已知每个整数类型的最小值和最大值。当一个整数值超出了其类型的取值范围时,我们就说这个值溢出(overflow)了。

    以 64 位的计算机系统为例,Julia 对整数值溢出有两种处理措施,具体如下:

    • 对于其类型的宽度小于64的整数值,值不变,其类型会被提升到Int64

    也就是说,对于Int8Int16Int32‌、UInt8UInt16UInt32这 6 个类型,Julia 会把溢出值的类型自动地转换为。这样的话,这些值就不再是溢出的了。

    对于宽度更大的整数类型,Julia 会采取不同的应对措施——环绕式(wraparound)处理。这是什么意思呢?比如,当一个Int64类型的整数值比这个类型的最大值还要大1的时候,该值就会变成这个类型的最小值。相对应的,当这个类型的整数值比其最小值还要小1的时候,该值就会变成这个类型的最大值。示例如下:

    1. julia> int1 = typemax(Int64)
    2. 9223372036854775807
    3. julia> int2 = int1 + 1
    4. -9223372036854775808
    5. julia> int2 == typemin(Int64)
    6. true
    7. julia> int3 = int2 - 1
    8. 9223372036854775807
    9. julia> int3 == typemax(Int64)
    10. true
    11. julia>

    但对于像Int64这样的整数类型来说,其所有可取值共同形成的就不再是一根棍子了,而是一个圆环。这就好像把原来的棍子掰弯并首尾相接了一样。当该类型的值从它的最大值变更为最大值再加1时,就好似从圆环接缝的右侧移动一格,到了接缝左侧。相对应的,当该类型的值从它的最小值变更为最小值再减1时,就好像从圆环接缝的左侧移动一格,到了接缝右侧。这样的处理方式就叫做对整数溢出的环绕式处理。

    图 5-1 对整数溢出的环绕式处理

    总之,对于Int64Int128UInt64UInt128这 4 个类型,Julia 会对溢出值做环绕式处理。

    如果你需要的是不会溢出的整数类型,那么可以使用BigInt。它的值的大小只受限于当前计算机的内存空间。

    BigInt类型属于有符号的整数类型。它表示的数值可以是非常大的正整数,也可以是非常小的负整数。由此,我们可以说,它的值可以是任意精度的。

    与其他的整数类型一样,其实例的构造函数与类型拥有相同的名称。并且,我们还可以使用一种非常规的字符串来构造它的值。例如:

    1. julia> BigInt(1234567890123456789012345678901234567890)
    2. 1234567890123456789012345678901234567890
    3. julia> typeof(ans)
    4. BigInt
    5. julia> big"1234567890123456789012345678901234567890"
    6. 1234567890123456789012345678901234567890
    7. julia> typeof(ans)
    8. BigInt

    可以看到,我们把一串很长的数字传给了BigInt函数,并由此构造了一个BigInt类型的值。实际上,BigInt函数接受的唯一参数可以是任意长度的整数字面量,也可以是任何其他整数类型的值。

    甚至,这个构造函数的参数值还可以是像big"1234"这样的非常规字符串。不过,我们没有必要这么做。因为big"1234"本身就能够表示一个BigInt类型的实例。更宽泛地讲,在一个内容形似整数的字符串前添加big这 3 个字母就可以把它变成一个BigInt类型的值。

    另外,任何溢出的整数值的类型都不会被自动地转换成BigInt。如有需要,我们只能手动地进行类型转换。

    最后,你需要了解的是,虽然BigInt直接继承自Signed类型,但它是一个比较特殊的整数类型。它被定义在了Base.GMP包中,而其他的整数类型的定义都在Core包中。GMP 指的是 GNU Multiple Precision Arithmetic Library,可以翻译为多精度算术库。Julia 中的包实际上只是对这个库的再次封装而已。虽然如此,这样一个类型的值却可以直接与其他类型的数值一起做数学运算。这主要得益于 Julia 中数值类型的层次结构,以及它的类型提升和转换机制。