Julia 整型,在数学理论及科学计算中,整数是最基本的数值类型,而整型(Integers)则是计算机中表达整数的基本语法类型。
众所周知,任何数据在计算机内部都是二进制的,而一个数值类型的表达能力与该类型的二进制位(比特,Bit)的数量有关。在Julia中,为了兼容不同的运行系统、节约存储空间或便于代码移植,按位数的不同对整型做了非常详细的划分,分别为8位、16位、32位、64位甚至128位的整数定义了相应的整型,如表所示。
除此之外,为了更精确地进行整数表达,与一些强类型语言相似,Julia中的整型又分成有符号及无符号两类,其中的有符号型能够表达负数而无符号型仅用于表达正数。而且,它们能够表述的整数范围不同,在实践中也有着不同的作用。
表中的最后两列分别给出了各类型能够表达的整数数值范围。实际上,在Julia中,对于任意数值类型,我们都可以随时使用内置的函数typemin()
与typemax()
来获得该类型能够表达的数值范围。例如:
julia> typemin(Int64) # 查看Int64类型能够表达的最小值
-9223372036854775808
julia> typemax(UInt32) # 查看UInt32类型能够表达的最大值
0xffffffff # 无符号整型以十六进制方式展示
其中的无符号整型会以十六进制的方式展示,并不影响值的实际内容。
表达方式
在开发中,经常会以字面值(Literal)的方式提供数据(即直接将具体数字写在代码中),例如:
julia> x1 = 20 # 20即为字面值
20
其中定义了x1变量,并将字面值20赋值给它。在赋值语句执行后,REPL打印了执行的结果,即显示x1的结果值为20。
但在实践中,不仅会有最为常用的十进制,还会有十六进制、八进制等。尤其是在整数表达中,多种数制的表达与转换是经常遇到的问题,甚至有时候需要直接使用二进制方式。
在Julia中以不同的数制输入字面值是非常方便的,只需在数值的前面设置对应的前缀即可:0x表示十六进制数,0b表示二进制数,而0o则表示八进制数(第二个圈不是零而是字母O的小写)。需要注意的是,这些前缀中的字母标识x、b及o只能是小写,不能是大写。
下面我们分别给出数值20的各种进制表达方法,如下所示:
julia> 0x14 # 十六进制
0x14
julia> 0b10100 # 二进制
0x14
julia> 0o24 # 八进制
0x14
这三种表达方式在REPL展示时都被显示为十六进制,可见它们确实表达了同样的值。但它们的类型也是一样的吗?与前面的十进制20又有什么不同?
事实上,我们可以使用typeof()
函数在任何时候查看某个值或变量等任意对象的类型,例如:
我们会发现,这四个值虽然都是整数20,但类型却不都是一样的。
整数类型的确定需要考虑两个方面的问题,一是有符号还是无符号,二是位数。在Julia自动确定字面值类型的过程中,会将十进制的字面值确定为有符号整型,而将其他进制的数均处理为无符号整型;而在位数选择方面,十进制字面值会默认与操作系统的位数保持一致,除非字面值过大,需要选择更大的位数,对于非十进制的其他数制字面值会依据值的大小选择恰当的位数。
仍以数值20的四种进制表达方式为例,0x14、0b10100及0o24均被确定为无符号整型,而十进制被确定为有符号整型;虽然大小都是20,但十进制选择了64位(笔者运行示例的系统为64位,后文未说明均为64位操作系统),其他的进制则仅选择8位。我们可以借助内置的bitstring()
查看这些值在内部的二进制结构:
julia> bitstring(20)
"0000000000000000000000000000000000000000000000000000000000010100" # 64位
julia> bitstring(0x14)
"00010100" # 8位
julia> bitstring(0b10100)
"00010100" # 8位
julia> bitstring(0o24)
"00010100" # 8位
可见十进制的字面值20在存储结构中确实采用了64位,而其他的三个采用了8位。
我们再看看一个比20大得多的例子:
上例将变量bign绑定到了一个非常大的整数,因为是十进制,所以确定为有符号类型,但却超出了Int64所能表达的范围,所以最终将类型放大到128位有符号整型。
对于其他进制这种情况也类似,例如:
julia> typeof(0x14F) # 对应十进制数335
UInt16
julia> typeof(0x14FFF) # 对应十进制数86015
UInt32
julia> typeof(0x14FFFFFFF) # 对应十进制数5637144575
UInt64
julia> typeof(0x14FFFFFFFFFFFFFFF) # 对应十进制数24211351596743786495
UInt128
可见数值越大,Julia选择类型的位数越高。此时,类型位数的选择不会再局限于运行系统的位数。
类型强制限定
当然,在提供数据时,我们可以不让Julia自行确定类型,或者将其转换为指定的类型,做法为:
类型名称(输入值或变量)
这实际是以类似函数的形式对目标类型进行调用。例如:
julia> x2 = Int32(20) # 字面值20以Int32类型提供
julia> typeof(x2)
Int32
julia> x3 = UInt8(20) # 字面值20以UInt8类型提供
julia> typeof(x3)
UInt8
julia> y1 = Int64(0x14) # 字面值0x14以Int64类型提供
julia> typeof(y1)
Int64
julia> y2 = Int8(0b10100) # 字面值0b10100以Int8类型提供
julia> typeof(y2)
Int8
或者
julia> UInt32(y2) # 原来的类型为Int8
julia> typeof(ans)
UInt32
julia> Int8(x3) # 原来的类型为UInt8
julia> typeof(ans)
Int8
但如果限定的类型无法表达原始数值,Julia会报异常,例如:
所以,类型的确定需要综合考虑各类型的表达能力以及内存管理方面的情况,确保在资源节约的情况下,不会导致数据的表达出现类似上述的截断性错误。
有无符号转换
显而易见,位数相同的整型中,与有符号相比,无符号整型能够表达更大的正数。必要时,我们可以使用unsigned()
函数将有符号数值转为无符号类型,例如:
julia> unsigned(20) # 20为Int64类型
julia> typeof(ans)
UInt64 # 转为原值相同位数的有符号类型
julia> unsigned(Int16(20))
0x0014
julia> typeof(ans)
UInt16 # 转为原值相同位数的有符号类型
反之,可以使用signed()
函数将无符号数值转为有符号类型,即:
julia> signed(UInt32(20))
20
julia> typeof(ans)
Int32 # 转为原值相同位数的有符号类型
julia> signed(0x14)
20
julia> typeof(ans)
Int8 # 转为原值相同位数的有符号类型
可以看出,上述的unsigned signed()函数在转换时会将原值变成位数相同的无符号或有符号类型。但需要注意的是,在这种转换中,前者则会忽略负值的检查;而后者会忽略表达范围越界(Overflow,溢出)的情况。例如:
julia> a1 = Int8(-20) # a1为8比特的整型,负数
-20
julia> a2 = unsigned(a1) # a2为将负数的a1转为无符号整型
julia> Int64(a2) # a2的实际值变成了236
julia> bitstring(a1)
"11101100"
julia> bitstring(a2)
"11101100"
其中的a2在将a1转换为无符号后,值发生了变化,不再是-20而是236。两者在存储结构中,位序列上仍是一致的,但类型已经不再相同,一个是负数,而另一个变成了正数。不过,再次对a2进行转换时,仍可以获得其原始的负值,即:
再例如:
julia> b1 = typemax(UInt8)
julia> signed(b1)
-1 # 转换后溢出,导致结果并非是2^32-1,而变成了-1
julia> typeof(ans)
Int8
该例中尝试将UInt8的最大值转为有符号类型,但显然相同位数的有符号型Int8无法表达这个值,出现了溢出现象,变成了-1值。
酷客网相关文章:
评论前必须登录!
注册