Julia 语言中的整数与浮点数:数值类型体系、字面量语法与任意精度算术完全指南
Julia 语言中的整数与浮点数数值类型体系、字面量语法与任意精度算术完全指南【免费下载链接】juliaThe Julia Programming Language项目地址: https://gitcode.com/gh_mirrors/ju/julia整数与浮点数是 Julia 计算体系的基本构件。本文以官方手册 integers-and-floating-point-numbers.md 为主体结合 Julia 仓库中的基础库源码base/int.jl、base/float.jl、base/checked.jl、base/mpfr.jl 等系统讲解 Julia 的原生数值类型、字面量书写规则、溢出与除零语义、IEEE 754 浮点细节、任意精度算术BigInt/BigFloat以及数字字面量系数的语法糖。读完本文你将掌握 Julia 数值编程的完整地图能够准确预测每个数值表达式在底层产生的类型与行为。数值类型总览原生数值原语Numeric Primitives在 Julia 中数值在代码中的直接书写形式称为数字字面量numeric literal例如1是整数字面量、1.0是浮点字面量而它们在内存中的二进制表示对象则称为数值原语numeric primitive。Julia 提供了一整套直接映射到现代 CPU 原生指令的数值类型因此能够充分利用底层计算资源同时对于原生硬件无法有效表示的大数值Julia 还通过 任意精度算术 提供了软件层面的支持。整数类型Integer TypesJulia 的整数类型覆盖 8 到 128 位有符号与无符号对称齐全类型有符号位数最小值最大值Int8✓8-2^72^7 - 1UInt8802^8 - 1Int16✓16-2^152^15 - 1UInt161602^16 - 1Int32✓32-2^312^31 - 1UInt323202^32 - 1Int64✓64-2^632^63 - 1UInt646402^64 - 1Int128✓128-2^1272^127 - 1UInt12812802^128 - 1BoolN/A8false(0)true(1)值得注意Bool在 Julia 中也是整数类型家族的一员它占用 8 位其值false/true即 0/1这使其可以直接参与整数运算。浮点类型Floating-point Types浮点类型完全对齐 IEEE 754 标准类型精度位数Float16半精度half16Float32单精度single32Float64双精度double64在这些原生类型之上Julia 还构建了完整的复数与有理数支持。所有数值类型都可以在类型提升系统的协调下自然互操作无需显式强制转换。整数字面量类型推断规则与进制表示十进制整数字面量与系统字长整数字面量按标准方式书写julia 1 1 julia 1234 1234整数字面量的默认类型取决于目标系统的字长32 位还是 64 位架构# 32-bit system: julia typeof(1) Int32 # 64-bit system: julia typeof(1) Int64Julia 内部变量Sys.WORD_SIZE用于指示目标系统的字长。在 base/sysinfo.jl 中可以看到它的定义Sys.WORD_SIZE::Int const WORD_SIZE Core.sizeof(Int) * 8即Sys.WORD_SIZE由Int类型的字节数乘以 8 计算而来因此运行时会稳定地给出 32 或 64# 32-bit system: julia Sys.WORD_SIZE 32 # 64-bit system: julia Sys.WORD_SIZE 64同时Julia 定义了Int与UInt两个类型别名分别指向系统原生有符号与无符号整数类型32 位系统上即Int32/UInt3264 位系统上即Int64/UInt64。正如 base/docs/basedocs.jl 所述Int就是Sys.WORD_SIZE位的有符号整数类型也是size、数组索引等绝大多数默认场景返回的类型。超大字面量自动提升为 64 位无法用 32 位表示、但能放进 64 位的整数字面量无论系统字长如何总是产生Int64# 32-bit or 64-bit system: julia typeof(3000000000) Int64十六进制、二进制与八进制字面量无符号整数使用0x前缀和十六进制数字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的字面量所需的最小尺寸计算。因此0x1与0x12是UInt8字面量0x123与0x1234是UInt16字面量0x12345与0x12345678是UInt32字面量0x123456789与0x1234567890adcdef是UInt64字面量依此类推。即便前导零不贡献数值它们仍参与存储尺寸的判定所以0x01是UInt8而0x0001是UInt16。这给了用户精确控制字面量存储大小的能力。超大无符号字面量回退为 BigInt以0x开头、但编码值超出UInt128表示范围的无符号字面量会构造BigInt值。BigInt并非无符号类型但它是内建类型中唯一大到足以容纳此类数值的类型。例如julia 0x000000000000000000000000000000000 0 julia typeof(ans) BigInt负的无符号字面量二进制补码二进制、八进制、十六进制字面量可在前面紧接-号形成带符号形式。它们产生与无符号字面量同尺寸的无符号整数值为原数的二进制补码julia -0x2 0xfe julia -0x0002 0xfffe用 typemin / typemax 查询边界原生数值类型的最小值与最大值由typemin与typemax函数给出。在 base/int.jl 中可以看到这些函数为每个具体类型定义的具体实现typemin(::Type{Int8 }) Int8(-128) typemax(::Type{Int8 }) Int8(127) typemin(::Type{UInt8 }) UInt8(0) typemax(::Type{UInt8 }) UInt8(255) # ... Int16/UInt16/Int32/UInt32/Int64/UInt64 依此类推 eval typemin(::Type{UInt128}) $(convert(UInt128, 0)) eval typemax(::Type{UInt128}) $(bitcast(UInt128, convert(Int128, -1))) eval typemin(::Type{Int128} ) $(convert(Int128, 1) 127) eval typemax(::Type{Int128} ) $(bitcast(Int128, typemax(UInt128) 1))值得注意Int128/UInt128的边界是通过位运算移位与bitcast在编译期构造的这体现了 Julia 将类型边界常量化内联的设计。这两个函数返回的值总是参数类型的实例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]溢出行为与模运算在 Julia 中超过某类型最大可表示值会产生**回绕wraparound**行为julia x typemax(Int64) 9223372036854775807 julia x 1 -9223372036854775808 julia x 1 typemin(Int64) trueJulia 整数类型的算术运算本质上执行模运算modular arithmetic这与现代计算机硬件上的整数算术特性一致。在可能溢出的场景中必须显式检查回绕效应。Base.Checked模块实现于 base/checked.jl提供了一套带溢出检查的算术运算一旦溢出即抛出错误module Checked export checked_neg, checked_abs, checked_add, checked_sub, checked_mul, ...该模块导出checked_neg、checked_abs、checked_add、checked_sub、checked_mul等运算并且从源码可以看到它们会先对不同类型的参数做promote统一类型checked_add(x::Integer, y::Integer) checked_add(promote(x,y)...)保证检查语义的类型一致性。对于任何情况下都不能容忍溢出的场景应改用下一节介绍的BigInt。一个典型的溢出案例与解决方案julia 10^19 -8446744073709551616 julia big(10)^19 10000000000000000000整数除法的异常情况整数除法div函数有两个特殊异常场景除以零以及用最低负数typemin除以 -1。两者都会抛出DivideError。rem与mod函数在第二个参数为零时同样抛出DivideError。这些语义在 base/int.jl 的Int128相关实现中有清晰的体现function div(x::Int128, y::Int128) (x typemin(Int128)) (y -1) throw(DivideError()) return Int128(div(BigInt(x), BigInt(y)))::Int128 end function divrem(x::UInt128, y::UInt128) iszero(y) throw(DivideError()) ... end function rem(x::UInt128, y::UInt128) iszero(y) throw(DivideError()) ... end从源码可见typemin ÷ -1被显式拦截并抛出DivideError原因是该结果2^127恰好超出Int128的表示范围——这正是回绕会掩盖错误的典型反例因此 Julia 选择直接报错而非静默回绕。浮点数字面量格式、后缀与十六进制写法标准浮点字面量浮点字面量使用标准格式书写必要时采用 E 记数法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字面量用f代替ejulia x 0.5f0 0.5f0 julia typeof(x) Float32 julia 2.5f-4 0.00025f0也可以轻松地将值转换为Float32julia x Float32(-1.5) -1.5f0 julia typeof(x) Float32十六进制浮点字面量十六进制浮点字面量同样合法但只能是Float64值且用p引导以 2 为底的指数julia 0x1p0 1.0 julia 0x1.8p3 12.0 julia x 0x.4p-1 0.125 julia typeof(x) Float64例如0x1.8p3即(1 8/16) × 2³ 1.5 × 8 12.0。这种表示能精确表达二进制尾数在需要严格按位理解浮点值时非常有用。半精度 Float16所有平台都支持半精度浮点数Float16在硬件支持该格式的平台上使用原生指令否则以软件方式实现并用Float32做中间计算。作为内部实现细节Julia 在底层借助 LLVM 的half类型来实现其行为类似于 GCC 的-fexcess-precision16标志对 C/C 代码的效果。julia sizeof(Float16(4.)) 2 julia 2*Float16(4.) Float16(8.0)从sizeof结果可见Float16恰好占用 2 字节16 位与 base/float.jl 中floatmin(::Type{Float16}) $(bitcast(Float16, 0x0400))这类位级定义方式相互印证——半精度浮点完全由 IEEE 754 位模式驱动。下划线数字分隔符下划线_可用作数字分隔符提升长数字的可读性julia 10_000, 0.000_000_005, 0xdead_beef, 0b1011_0010 (10000, 5.0e-9, 0xdeadbeef, 0xb2)浮点零与特殊值两个零正零与负零浮点数存在两个零——正零与负零。它们彼此相等但二进制表示不同可用bitstring观察julia 0.0 -0.0 true julia bitstring(0.0) 0000000000000000000000000000000000000000000000000000000000000000 julia bitstring(-0.0) 1000000000000000000000000000000000000000000000000000000000000000二者唯一的差别是符号位最左一位——这正是 IEEE 754 带符号零的设计。三个特殊值Inf 与 NaN有三个标准浮点值不对应实数轴上的任何点Float16Float32Float64名称描述Inf16Inf32Inf正无穷大于所有有限浮点值的值-Inf16-Inf32-Inf负无穷小于所有有限浮点值的值NaN16NaN32NaN非数不任何浮点值包括自身的值按照 IEEE 754 标准这些值由特定算术运算产生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特别强调NaN与自身都不相等NaN NaN为false任何与NaN的有序比较均为false。在代码中判断 NaN 时应使用isnan而非。此外typemin/typemax也适用于浮点类型且返回无穷值julia (typemin(Float16),typemax(Float16)) (-Inf16, Inf16) julia (typemin(Float32),typemax(Float32)) (-Inf32, Inf32) julia (typemin(Float64),typemax(Float64)) (-Inf, Inf)若要获得最大的有限浮点值应使用 base/float.jl 中定义的floatmax最小的正有限值则用floatmin例如floatmin(Float32)为1.1754944f-38。机器精度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^-23Float32和2.0^-52Float64。eps也可以接收一个浮点值作为参数返回该值与下一个可表示浮点值的绝对差即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 还提供nextfloat与prevfloat分别返回参数的下一个更大或更小的可表示浮点数其多参数形式nextfloat(x, n)/prevfloat(x, n)支持一次跨过 n 个值实现见 base/float.jljulia 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这个例子凸显了一个重要原理相邻的可表示浮点数也具有相邻的二进制整数表示尾数位模式只差 1。舍入模式Rounding Modes如果某个数没有精确的浮点表示它必须被舍入到适当的可表示值。根据 IEEE 754 标准舍入方式可以按需改变。默认模式始终是RoundNearest——舍入到最近的可表示值若恰好在中间则向具有偶数最低有效位的最近值舍入即银行家舍入。任意精度算术BigInt 与 BigFloatGMP / MPFR 封装与构造方式为支持任意精度整数与浮点运算Julia 分别封装了 GNU 多精度算术库GMP与 GNU MPFR 库——这一事实在 base/Base.jl 的using .GMP/using .MPFR中可以得到确认。BigInt与BigFloat分别对应任意精度整数与浮点数。构造这些类型的方式包括从原生数值类型调用构造函数使用字符串字面量宏big_str即big...或parse从AbstractString构造BigInt还可以通过超大的整数字面量直接输入——当整数字面量对其它内建整数类型都太大时即是BigInt。注意Base中没有无符号的任意精度整数类型BigInt在大多数场景下已足够因此十六进制、八进制、二进制字面量除十进制外也都可以使用。一旦创建BigInt/BigFloat通过类型提升与转换机制参与所有数值类型的运算julia BigInt(typemax(Int64)) 1 9223372036854775808 julia big123456789012345678901234567890 1 123456789012345678901234567891 julia parse(BigInt, 123456789012345678901234567890) 1 123456789012345678901234567891 julia string(big2^200, base16) 100000000000000000000000000000000000000000000000000 julia 0x100000000000000000000000000000000-1 typemax(UInt128) true julia 0x000000000000000000000000000000000 0 julia typeof(ans) BigInt julia big1.23456789012345678901 1.234567890123456789010000000000000000000000000000000000000000000000000000000004 julia parse(BigFloat, 1.23456789012345678901) 1.234567890123456789010000000000000000000000000000000000000000000000000000000004 julia BigFloat(2.0^66) / 3 2.459565876494606882133333333333333333333333333333333333333333333333333333333344e19 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第一个序列中typemin(Int64) - 1回绕成了typemax(Int64)仍是Int64而显式转换为BigInt后则得到精确的-9223372036854775809。这正是需要防溢出时必须显式使用BigInt的最佳佐证。全局与局部精度 / 舍入控制BigFloat运算的默认精度有效数字的位数与舍入模式可以通过setprecision与setrounding全局修改后续所有计算都会受影响。也可以只用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可以看到同一条BigFloat(1) 0.1计算在RoundUp与RoundDown下得到截然不同但都按各自方向精确的结果而将精度降到 40 位后结果被截短——这直观展示了精度与舍入对任意精度浮点结果的共同影响。在 base/mpfr.jl 中可以找到setprecision的完整实现支持base关键字参数指定进制。警告setprecision/setrounding与big_str宏big0.3这类写法之间的关系可能不那么直观因为big_str是宏而非函数。宏展开时精度/舍入可能已被固化。具体细节请查阅big_str的文档。数字字面量系数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注意用于隐含乘法的数字字面量系数其优先级高于乘法*与除法/、\、//等二元运算符。例如1 / 2im等于-0.5im6 // 2(2 1)等于1 // 1。另外括号表达式也可以作为变量的系数表示表达式与变量相乘julia (x-1)x 6但两个括号表达式并列、以及变量放在括号表达式之前都不能用来表示乘法——它们被解释为函数调用julia (x-1)(x1) ERROR: MethodError: objects of type Int64 are not callable julia x(x1) ERROR: MethodError: objects of type Int64 are not callable规则是任何非数字字面量的表达式紧跟括号时都被解释为函数作用于括号内的值。上述两个例子中左侧值不是函数因此报错。同时注意数字字面量系数与被乘的标识符或括号表达式之间不能有空白。语法冲突及消解规则并列的字面量系数语法可能与某些数字字面量语法冲突十六进制整数字面量0xff可能被误解为数字0乘以变量xff0o777、0b01001010类似。浮点字面量1e10可能被误解为数字1乘以变量e10E形式同理。32 位浮点字面量1.5f22可能被误解为1.5乘以变量f22。所有情况下歧义都消解为数字字面量以0x/0o/0b开头的表达式始终是十六/八/二进制字面量以数字字面量后接e或E的表达式始终是浮点字面量以数字字面量后接f的表达式始终是 32 位浮点字面量。与E历史原因等价于e不同F只是普通字母在数字字面量中不像f那样特殊。因此以数字字面量后接F的表达式被解释为数字乘以变量——例如1.5F22等于1.5 * F22。字面量零与一zero / oneJulia 提供返回字面量 0 和 1的函数对应指定类型或给定变量的类型函数描述zero(x)类型x或变量x类型的字面量零one(x)类型x或变量x类型的字面量一这些函数在数值比较等场景中用于避免不必要的类型转换开销——直接构造目标类型的 0/1而不是先写出默认类型的常量再转换。例如julia zero(Float32) 0.0f0 julia zero(1.0) 0.0 julia one(Int32) 1 julia one(BigFloat) 1.0补充参考与延伸阅读浮点算术包含许多对底层实现细节不熟悉的用户容易惊讶的微妙之处。官方手册推荐的深入资料包括IEEE 754-2008 标准本身浮点算术的权威定义John D. Cook 关于浮点数表示与数值是漏水的抽象的系列文章Bruce Dawson 关于浮点数的系列博客包括非常规浮点数的性能问题David Goldberg 的经典论文《What Every Computer Scientist Should Know About Floating-Point Arithmetic》William Kahan被誉为浮点数之父的文集与访谈。在本仓库中你还可以通过以下源码路径继续深入验证本文涉及的机制整数边界与除法语义base/int.jl浮点定义、eps/nextfloat/prevfloat/floatmin/floatmaxbase/float.jl溢出检查算术Base.Checkedbase/checked.jl任意精度算术与setprecision/setroundingbase/mpfr.jlSys.WORD_SIZE定义base/sysinfo.jl小结本文沿官方手册的脉络完整覆盖了 Julia 数值编程的核心知识从 8–128 位整数与 16–64 位浮点类型的边界与字面量规则到溢出回绕、DivideError异常语义、IEEE 754 特殊值正负零、Inf、NaN、机器精度与舍入模式再到BigInt/BigFloat任意精度算术的构造、提升规则与精度/舍入控制最后讲解了数字字面量系数这一极其实用的语法糖及其与进制字面量的消歧规则。掌握这些细节你就能在编写高性能数值代码时准确预判类型与行为避免溢出、除零与浮点精度陷阱。【免费下载链接】juliaThe Julia Programming Language项目地址: https://gitcode.com/gh_mirrors/ju/julia创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考