Julia 是一门通用编程语言,可以用于大多数编程任务。 不过在实践当中,它的主要应用场景往往是工程和科学领域。 快速、灵活且精密的数值计算,是这门语言设计的核心。
整数是不带小数点的“整”数。
在基础概念中,我们看到可以把一个整数值赋给变量,而无需指定类型。
为了提高可读性,可以用下划线作为数字分隔符。 编译器会忽略它们。
julia> x = 3
3
julia> typeof(x)
Int64
julia> large_number = 1_234_567_890
1234567890
在内部,编译器会使用最适合你 CPU 的有符号整数类型。
在现代 PC 上,这通常是 Int64,对大多数任务来说完全够用。
类型会在以后的概念中更详细地讨论。 现在,_数字_类型应该足够直观,能让本文读得通。
我们会在后面的概念中看到,Julia 原生支持非常大的多维数组。 这些数组会挑战可用内存的大小,以及数据传输的带宽。
为了给程序员更精细的控制,指定所需的有符号或无符号整数类型既可行,也相当常见。 许多之前用过 Python 的 Julia 学习者,可能会把这一点和 NumPy 相比,而不是和 Python 本身相比。
julia> y = Int8(42)
42
julia> typeof(y)
Int8
julia> z = UInt32(1024)
0x00000400
注意,Julia 默认以十六进制格式显示无符号整数,因为无符号类型常用于底层位运算。
整数也可以用二进制、八进制或十六进制输入,前缀分别是 0b、0o 和 0x。
julia> a = 0x10
0x10
julia> Int(a)
16
julia> b = 0b1010 # displays as hexadecimal
0x0a
BigInt
每种整数类型都有它能存储的最大值和最小值:
julia> typemax(Int8)
127
julia> typemin(Int8)
-128
超出这个有效范围会导致整数溢出,结果看起来会非常奇怪。
julia> c = Int8(126)
126
julia> c * c
4
我们大概都会同意,126 * 126 不应该等于 4!
详细的解释需要了解 CPU 寄存器如何存储整数,这超出了本概念的范围。
使用“更宽”的类型可以把问题降到最低,最大可到 Int128 或 UInt128(“更宽”的含义会在下面讨论)。 不过,所有类型在遇到溢出问题之前都有硬性上限。
一个解决办法是使用 BigInt 类型,它只受你计算机内存的限制。
它是 Julia 对任意精度算术的实现。
julia> 2 ^ 70
0
julia> big = BigInt(2) ^ 70
1180591620717411303424
julia> UInt128(big)
0x00000000000000400000000000000000
因为 2 的 70 次方放不进 Int64,上面例子中的第一次计算会失败,不过要注意,并不会出现任何错误消息。
程序员当心!
把 2 转换成 BigInt 就能得到正确答案,而用十六进制显示它,则能更清楚地看出这是一个 2 的幂。
浮点数可以有小数点,以及小数点后的小数部分,这一点应该不会让人意外。
julia> f = 3.45
3.45
julia> typeof(f)
Float64
当然,也支持科学计数法。
julia> avogadro = 6.02e23
6.02e23
和整数一样,默认类型对大多数用途来说就够用,但也可以使用其他有符号类型。 不存在_无符号_浮点类型。
作为快捷方式,可以用 f0 后缀创建 Float32 值。
julia> f32 = 4.56f0
4.56f0
julia> typeof(f32)
Float32
其他大小的浮点数需要显式转换,和整数一样。
最大值和最小值可能会让你意外:
julia> typemax(Float64)
Inf
julia> typemin(Float64)
-Inf
无穷大是一个有效值!
不过,浮点数的有效范围是有限的:非常大的数会被直接赋为 Inf,非常小的数会被舍入为 0.0。
我们可以用另一对函数来查看这些限制:大约是 ± 10 ^ 308。
julia> floatmax(Float64)
1.7976931348623157e308
julia> floatmin(Float64)
2.2250738585072014e-308
此外,精度也是有限的,能表示的有效数字数量很大但有限(对 Float64 来说大约是 15 位)。
正如基础概念中讨论的,算术运算符的用法大多和人们从小学习的那种标准算术一样。
注意,幂运算使用 ^,而_不是_ **(两者在其他语言中都很常见)。
2 + 3 # 5 (addition)
2 - 3 # -1 (subtraction)
2 * 3 # 6 (multiplication)
8 / 2 # 4.0 (division)
8 % 3 # 2 (remainder)
2 ^ 3 # 8 (exponentiation)
不过,有几个 Julia 特有的细节值得讨论。
julia> x = 4.2
4.2
julia> 2 * x
8.4
julia> 2x
8.4
julia> 2.4x
10.08
这可能会让人意外。
和大多数其他计算机语言一样,你始终可以把 * 用作中缀运算符。
不过,Julia 的设计者相信,代码应该尽可能地看起来像数学方程式。
由于变量名必须以字母开头,在名称前面加一个数字(整数或浮点数)会被当作隐式乘法。
例如,如果我们要计算一个球的表面积,可以不用 4 * pi * r * r,而是这样写:
julia> surface(r) = 4π * r^2
surface (generic function with 1 method)
julia> surface(3)
113.09733552923255
尽管 π 是一个内置常量,它同时也是一个(希腊)字母。
因此解析器仍然需要一个显式的 *,把 π 和 r 分开。
把 / 用作中缀运算符时,即使输入是整数,结果也始终是浮点数。
对于整数除法,有更多选择:
julia> 10 / 3
3.3333333333333335
julia> div(10, 3)
3
julia> 10 ÷ 3
3
julia> 10 // 3
10//3
div() 函数用于整数除法,结果会向零截断:正数向下取整,负数向上取整。
作为它的同义词,我们可以使用中缀运算符 ÷,同样是为了让它看起来更像数学。
如果你用的是能识别 Julia 的编辑器,输入 \div 然后按 <Tab> 键。
// 运算符将在后面的教学大纲中单独用一个概念来讲解。
现在,我们只要说 // 的结果是一个“有理”数,也就是大多数人所说的分数的正式名称。
公因数会从分子和分母中约去,得到两个整数的比,也就是所谓的“最简形式”。
julia> rationalnum = 22 // 6
11//3
julia> typeof(rationalnum)
Rational{Int64}
我们有了有理数。 那“无理”数呢?
julia> π
π = 3.1415926535897...
julia> typeof(π)
Irrational{:π}
无理数是无法化简为两个整数之比的数。
常见的例子包括 π、e(欧拉数),以及许多根,比如 √2(2 的平方根)。
Julia 努力_正确地_做数学。
这通常可以自动发生:
julia> x = 2 + 3.5
5.5
julia> typeof(x)
Float64
我们把一个 Int64 和一个 Float64 相加,得到了 Float64 结果。
事实上,在执行加法之前,那个整数已经被静默转换成了 Float64。
Julia 有数字类型“宽度”的概念。
Int64 比 Int16 更宽。如果在一个表达式中混合使用了多种类型,每种类型都会按需被“提升”到所用类型中最宽的那一种。
要强制转换,我们可以把整数转换为特定类型,比如 Float64(5)。
或者,直接用 float(5),让编译器选择合适的类型。
浮点数转整数的转换不可避免地更复杂。 小数点后的部分你想怎么处理?
round() 函数会转换为最接近的整数;对于像 4.5 这样的平局,会舍入到最接近的_偶数_。floor() 向下舍入,ceil() 向上舍入,trunc() 向零舍入。Int32(),会失败并抛出 InexactError。不过,这些函数默认返回的并不一定是你想要的整数类型。 可以指定想要的输出类型。
julia> round(4.5)
4.0
julia> round(Int64, 4.5)
4
julia> round(Int, 4.5) # => default integer type
4
julia> ceil(Int16, 4.3)
5
也可以用 digits 关键字,舍入到小数点后的指定位数。
julia> round(π, digits=10)
3.1415926536
更多细节请参阅手册。
这难道不应该直接抛出一个错误吗? 事实上,情况并没有那么简单。
用 ÷ 或 // 做整数除法会像你预期的那样产生错误。
用 / 做浮点数除法,采取的可能是工程方法,而不是标准的计算机科学方法:
julia> 2 / 0
Inf
julia> 0 / 0
NaN
如前文所述,无穷大在 Julia 中是一个有效的浮点数,用 Inf 表示。
当分子也是零时,结果在数学上是未定义的。
Julia 便把它当作“非数”,用 NaN 表示。
如果这看起来很奇怪,不妨把它放到处理大型真实世界(因此往往相当杂乱)数据数组的场景中想想。 为了继续推进,最好只是标记出有问题的值,然后继续处理。
无休止地手动检查每个值,编程起来会很繁琐,而且肯定会拖累运行时性能。
每遇到一点小故障就停下并抛出错误消息,会让你的程序_非常_不受用户欢迎!
正如 Conditionals 概念中所述,相等性测试通常用 == 运算符完成。
这对整数、字符、字符串等都很好用。
不过,浮点数值的精度有限,用不同方式计算同一个结果,可能会得到略有差异的数值。
对 Float64 来说,差异通常出现在第 15 位有效数字左右:差别很小,但并不“相等”。
传统上,给程序员的建议是:_永远不要_对浮点数值使用 ==,因为结果不可预测。
一种广泛使用的替代做法,是把差值的绝对值与某个允许的容差(通常称为 epsilon 或 ϵ)比较。
所以不要用 a == b,而要用 abs(a - b) < epsilon。
Julia 通过 isapprox() 函数提供了更整洁的替代方案。
要使用绝对容差,语法是 isapprox(a, b, atol=epsilon)。
这种情况下必须提供 atol 关键字。
相对容差往往更有用。
它是默认设置,所以 isapprox(a, b) 会尝试为 rtol 选择一个合理的值(相关规则相当复杂)。
也可以指定相对容差,即被比较数值的某个比例。
因此 isapprox(a, b, rtol=0.01 测试的是两个数值的差异在 1% 以内。
按照 Julia 惯常的风格,有一个数学运算符可以作为默认情况的同义词:a ≈ b(运算符的输入方式是 \approx,然后按 <tab>)。
除了有理数之外,教学大纲后面的部分还会讨论: