Julia 学习笔记——2. 基本数据类型与运算符
整数与浮点数
整数
Julia 支持固定长度的整数类型,包括有符号和无符号的版本,例如
Int8,Int32,UInt32
等。对于十进制整数字面量,在64位系统中默认使用 Int64
类型整数。可以使用 typeof() 查看字面量的类型(Python
对应的函数为 type()) 1
typeof(1) # Int64
类型也是可以作为参数进行运算的,例如使用 typemin() 和
typemax() 直接查看类型的最大最小值 1
2
3typemin(Int32) # -2147483648
typemin(Int64) # -9223372036854775808
typemax(Int64) # 9223372036854775807
注:
- Julia 支持使用
_作为数字的分隔符,可以提高可读性,例如10_000。 - 由于整数的位数是固定的,在运算中自然也存在溢出问题,与其它语言中的处理类似,不再赘述。
- Python 的整数是无限精度的,Julia 也提供了
BigInt类型以支持无限精度。
对于非十进制的整数字面量,Julia
采用了不同的处理:默认将其视作无类型整数,并自动选择合适的位数,例如
1
2typeof(0x11) # UInt8
typeof(0x1111111) # UInt32
除此之外,对于非十进制的字面量还有很多细节处理的差异,谨慎使用。
布尔值
提供布尔类型的 true 和 false。
在底层实现时,Julia 采用 8 位整数存储的 0 和 1 分别代表
true 和 false。相应的类型转换为
1
2Bool(1) # true
Bool(0) # false
需要注意的是,Julia 不会把非零整数全部转换为
true,下面的语句报错 1
Bool(2) # error
在类型系统中,Bool 属于 Integer 的子类型
1
2julia> Bool <: Integer
true
浮点数
Julia 支持基本的浮点数类型,包括默认使用的双精度浮点数
Float64,单精度浮点数 Float32 和半精度浮点数
Float16。
有一点奇怪的是,Julia 在科学记数法的表示中,使用 e
会对应 Float64 的字面量,使用 f 则会对于
Float32 的字面量。 1
2typeof(1.1e-3) # Float64
typeof(1.1f-3) # Float32
可以使用类型名将整数转换为浮点数,例如 1
2
3Float64(1) # 1.0
typeof(Float64(1)) # Float64
有意思的是,Julia 给不同的浮点数类型分别设置了 nan 和
inf:
- Float64:
NaN和Inf - Float32:
NaN32和Inf32 - Float16:
NaN16和Inf16
关于这些特殊值的运算都符合 IEEE 标准,因此不同语言之间并没有什么区别。
Julia 的 eps 和 MATLAB
类似,作为一个函数,可以传入一个浮点数或者浮点数类型:
- 如果传入的是浮点数,那么返回的就是它附近的浮点数间隙;
- 如果传入的是浮点数类型,返回的是
1.0附近的浮点数间隙; - 缺省时相当于
eps(Float64)。
1 | eps(1000.0) # 1.1368683772161603e-13 |
Julia 提供了 zero 和 one
函数来创建指定类型的 0 和
1,可以传入类型或者一个数,在某些情况下可以避免不必要的类型转换。 例如
1
2
3
4zero(1.0) # 0.0 # Float64
zero(Int) # 0 # Int64
one(Int32) # 1 # Int32
在整数和浮点数混合运算时,Julia 会自动转换为浮点数进行处理,并没有
MATLAB 那样的奇葩语法。 1
1 + 0.2 # 1.2
复数与有理数
复数
Julia 支持复数,使用全局常量 im 表示虚数单位,例如
1
1 + 2im
有意思的是 1
2typeof(1+im) # Complex{Int64}
typeof(im) # Complex{Bool}
这表明复数并不是简单的一个类型,实际类型与复数的实部虚部所属的类型有关。
在数学中,实数是复数的一个子类,但是在编程实现中很难如此实现,通常实数(包括浮点数和整数)是单独的一套类型体系,复数则是一个几乎与此无关的类型。
涉及到 im 的字面量需要特别注意运算优先级的差异,例如
1
23/4*im # 0.0 + 0.75im
3/4im # 0.0 - 0.75im
更建议的做法是使用 complex 函数来创建复数,例如
1
complex(1,2) # 1 + 2im
注意:Julia 严格区分字面量是否是复数类型,例如 -1 和
-1+0im 是完全不同的 1
2-1 == -1+0im # true
-1 === -1+0im # false
在使用 sqrt() 时的差异非常明显 1
2sqrt(-1) # error
sqrt(-1+0im) # 0.0 + 1.0im
有理数
Julia 支持有理数类型,可以通过 // 运算符创建,例如
1
22//3
typeof(2//3) # Rational{Int64}
注:
- Julia 会自动对分数进行化简,并且保证分母非负。
- Julia 允许分母为0,例如
5//0,但是不允许分子分母同时为 0。 - Julia 不支持涉及浮点数的
//运算
字符与字符串
与 Python 不同,Julia 使用单引号 ' 表示字符,使用双引号
" 来表示字符串,两者是不同的类型,不可以混用。例如
1
2typeof('a') # Char
typeof("a") # String
字符的底层当然是基于 ASCII 存储的,因此也支持与数值的运算,例如
1
'a' + 1 # 'b'
Julia 支持多行字符串和 raw 标记的原始字符串(禁用
\ 转义,适合用于 Windows 路径),例如 1
2
3
4
5
6text = """
这是
多行字符串
"""
rawstr = raw"C:\Users\Alice"
Julia 有如下两种方式拼接字符串,注意使用 *
而不是 + 进行拼接! 1
2
3"Hello, " * "world!" # "Hello, world!"
join(["a", "b", "c"], ", ") # "a, b, c"
使用 ^ 表示字符串的重复 1
"abc"^4 # "abcabcabcabc"
与 Python 的 f-string 类似,在字符串中可以使用 $
插入变量或表达式,有歧义时可以加括号,例如 1
2
3
4
5name = "Alice"
greeting = "Hello, $(name)!" # "Hello, Alice!"
age = 25
info = "Age next year: $(age + 1)" # "Age next year: 26"
Julia 的字符串支持基于字典序的大小比较。
Julia 甚至为版本号提供了一个专门的字符串字面量:版本号字面量,使用
v 作为前缀,例如 VERSION
是一个特殊的常数,代表 Julia
当前版本,可以用下面的语句进行特定版本的处理 1
2
3if v"0.2" <= VERSION < v"0.3-"
# 针对 0.2 系列版本
end
有很多关于字符串的操作,包括子串的搜索和提取,正则匹配等,但是目前不太需要,因此略去。
运算符
基本运算符
基本运算符和其它语言都差不多,只有几个值得注意的:
a*b:乘法a/b:除法a\b:反向除法,a\b等价于b/aa^b:幂运算a%b:取余a ÷ b:整除==:相等,这和isequal函数不太一样!=: 不等===:严格比较二进制数据相等
注:
- 涉及到
NaN和Inf的等号和不等号判断可能违反直觉,例如NaN != NaN,但是这也是遵循浮点数标准的。 - 部分运算符对应的 Unicode(LaTeX)
字符也可以正常使用,例如不等号也支持
\leq ===、==和isequal的语义存在细微区别,尤其在某些边缘例子中,一般来说,===最严格,isequal最弱。
与 Python 一样,Julia 支持链式比较,例如 1
1 < 2 <= 2 < 3 == 3 > 2 >= 1 == 1 < 3 != 5 # true
Julia 支持 += 等复合赋值运算符,例如 a += b
等价于 a = a + b,这个过程中可能改变 a
的类型,例如 1
2
3a=0; typeof(a) # Int64
a += 0.1; typeof(a) # Float64
和 Python 一样,这类运算符的优先级很低。
Numpy 的
+=等运算符是就地更新的,而 Julia 并不存在这样的区别,a += b始终等价于a = a + b。
! 是逻辑非,&&
是逻辑与,|| 是逻辑或,并且 && 和
|| 都是短路运算,例如 1
2
3true && false # false
true || false # true
!true # false
~,& 和 |
是按位运算符,例如 1
2
3~0 # -1
123 & 234 # 106
1 | 2 | 4 # 7
点运算符
Julia 为每一个二元运算符提供了点运算符的变体,例如 1
[1, 2, 3] ^ 2 # error
但是加上 . 就可以正常执行 1
[1, 2, 3] .^ 2 # [1, 4, 9]
. 的含义为对逐个元素进行运算。
实际上,a .^ b 会被处理为
(^).(a,b),首先对两个输入进行广播操作,使得不同的尺寸相匹配,然后进行逐个元素运算。
除了二元运算符,函数调用也可以变成点运算版本,例如
f.(A)。
在一个复杂的表达式中,. 运算会自动融合,例如
1
2 .* A.^2 .+ sin.(A)
这里涉及到的点运算会合并到一起,实际只会执行一次对 A
的循环遍历。
如果需要对一个复杂表达式的各个运算都改成点运算版本,也可以直接使用
@. 这个宏,例如 1
@. 2A^2 + sin(A))
在函数的复合调用中,点运算也会自动融合,例如 1
f.(g.(A))
复合赋值运算符也支持点运算的版本,例如 1
A .+= B
将点运算符用于数值字面量可能会导致歧义。例如 1.+x
既可以表示 1. + x,也可以表示 1 .+ x?
遇到这种存在歧义的情况时,Julia 会直接报错,可以用空格消除歧义。
1
2
3
4
5x = [1,2,3]
1.+x # error
1 .+ x # ok
补充
类型转换
可以直接使用 T(x) 把 x 转换为类型
T,例如 1
Int32(10)
对于基本数据类型,这通常是调用 convert()
函数实现的,例如 1
convert(Int32, 10)
类型提升
在不同类型的数据进行混合数值运算时,通常会将其提升为统一的数据类型,例如
1
1 + 0.3 # 1.3
可以用 promote() 函数探究类型提升的细节,例如
1
promote(1,0.3) # (1.0, 0.3)
对于自定义的类型,也支持给 Julia 提供对应的类型提升规则,例如
1
promote_rule(::Type{Float64}, ::Type{Float32}) = Float64
数值字面量系数
这是一个非常糟糕的,为了所谓的公式简洁优雅而提供的语法糖,实际上给代码可读性和语法解析都带来了额外的问题。
Julia 允许变量直接跟在一个数值字面量后,暗指乘法,例如
1
2
3x = 3
2x^2 + 4x + 5 # 21
6.0x^3-x # 46.0
数值字面量系数的优先级跟一元运算符相同,比如取相反数,例如:
2^3x会被解析成2^(3x)2x^3会被解析成2*(x^3)
括号表达式在某些情况下可以被用作变量的系数,暗指表达式与变量相乘,例如
1 | x = 3 |
但是这玩意其实很鸡肋,因为下面的表达式都是语法错误:
1 | x = 3 |
而且这里还可能和其它语法产生冲突,例如函数调用,十六进制的前缀0x,用于浮点数表示的f和e,以及虚数单位im等。
总的来说,字面量系数这种奇葩语法糖还是了解即可,实践中不要使用。
