Julia 数值类型的提升

                     

贡献者: xzllxls; addis

   本文授权转载自郝林的 《Julia 编程基础》.原文链接:第 5 章 数值与运算

1. 5.6 数值类型的提升

   Julia 中有一个辅助系统,叫做类型提升系统.它可以将数学运算符操作的多个值统一地转换为某个公共类型的值,以便运算的顺利进行.我们下面就简要地说明一下这个辅助系统的应用和作用.关于公共类型的解释也会在其中.

   在 Julia 中,数学运算符其实也是用函数实现的.就拿用于二元加的运算符 + 来说,它的一个衍生方法的定义是这样的:

+(x::Float64, y::Float64) = add_float(x, y)

   这个定义向我们揭示了两个细节.第一个细节就是我刚刚说的,数学运算符是由函数实现的.不仅如此,针对每一类可操作的数值,Julia 都定义了相应的衍生方法.第二个细节是,数学运算符操作的多个值必须是同一个类型的.你可能会有疑问,那为什么我们编写的像 1 + 2.0 这样的运算依然可以顺利进行呢?实际上,这恰恰得益于 Julia 的类型提升系统.我们来看该系统中的一个定义:

+(x::Number, y::Number) = +(promote(x,y)...)

   这个衍生方法的两个参数的类型都是 Number.这就意味着,只要参与二元加的操作数都是数值且它们的类型不同,该运算就会被分派到这个方法上.如果两个数值的类型相同,那么二元加运算就会被分派到像前一个定义那样的方法上.

   请注意,这个衍生方法的定义中有一个对 promote 函数的调用.这个函数其实就代表了类型提升系统的核心算法.我们可以在 REPL 环境中输入表达式 promote(1, 2.0) 并回车.其结果如下:

julia> promote(1, 2.0)
(1.0, 2.0)

julia> typeof(ans)
Tuple{Float64,Float64}

julia>

   我们都知道,在 64 位的计算机系统中,字面量 1 的类型一定是 Int64,而字面量 2.0 的类型肯定是 Float64.由此,在那个调用 promote 函数后得到的元组中,包含了转换自参数值 1 的、Float64 类型的数值 1.0,以及保持原样的、Float64 类型的数值 2.0.这正是类型提升系统所起到的作用.它一般会先找到能够无损地表示输入值的某个公共类型,然后把这些值都转换为此公共类型的值(通常通过调用 convert 函数实现),最后输出这些类型统一的值.

   在一般情况下,如果参数值列表中只包含了整数和有理数,那么 promote 函数就会把这些参数值都转换为有理数.倘若参数值列表中存在浮点数(但不存在复数),那么这个函数就会把这些参数值都转换为适当类型的浮点数.一旦参数值列表中有复数,那该函数就一定会返回适当类型的复数的元组.另一方面,如果这些参数值的类型只是在宽度上所有不同(如 Int64Int8Float16Float32 等等),那么 promote 函数就会把它们都转换为宽度较大的那个类型的值.

   我们倒是不用死记硬背这些规则.因为有一个名叫 promote_type 的函数,它可以接受若干个类型字面量并返回它们的公共类型.例如:

julia> promote_type(Int64, Float64)
Float64

julia> promote_type(Int64, Int8)
Int64

julia> promote_type(Float16, Float32)
Float32

julia>

   请注意,我们一直在说的是多个类型的公共类型,而不是多个类型的共同超类型.这两者之间并没有任何关联.如果你确实想得到两个类型的共同超类型,那么可以调用 typejoin 函数.例如,调用表达式 typejoin(Int, Float64) 的求值结果会是 Real

   好了,不论细节如何,经过前文所述的处理之后,这些数值就可以交给普通的运算符实现方法进行操作了.就像这样:

julia> +(promote(1, 2.0)...)
3.0

julia>

   这里对 + 数的调用会被分派到我们在前面展示的那个针对 Float64 类型的衍生方法上.

   解释一下,符号 ... 的作用是,把紧挨在它左边的那个值中的所有元素值(如元组 (1.0, 2.0) 中的 1.02.0)都平铺开来,并让这些元素值都成为传入外层函数(如 + 函数)的独立参数值.所以,调用表达式 +((1.0, 2.0)...) 就相当于 +(1.0, 2.0)

   至于什么是元组,你现在可以简单地把它理解为由圆括号包裹的、可承载若干值的容器.函数在同时返回多个值的时候通常就会用这种数据结构呈现.在后面讲参数化类型的那一章里有对元组的详细说明.

   除了以上讲的这些,Julia 的类型提升系统还有一个很重要的作用,那就是:让我们可以编写自己的类型提升规则,以自定义数学运算的部分行为,尤其是在操作数的类型不同的时候.例如,若我们想让整数和浮点数的运算结果变成 BigFloat 类型的值,则可以这样做:

julia> import Base.promote_rule

julia> promote_rule(::Type{Int64}, ::Type{Float64}) = BigFloat
promote_rule (generic function with 137 methods)

julia>

   第一行代码是一条导入语句.简单来说,我们在编写某个函数的衍生方法的时候必须先导入这个函数.第二行代码就是我编写的衍生方法.由于与之相关的一些背景知识我们还没有讲到,所以你看不太懂也没有关系.在这里,你只要关注这行代码中的 Int64Float64BigFloat 就可以了.前两个都代表了操作数的类型,而后一个则代表了它们的公共类型.这正是在定义操作数类型和公共类型的对应关系.

   现在,我们再次执行之前的代码:

julia> promote(1, 2.0)
(1.0, 2.0)

julia> typeof(ans)
Tuple{BigFloat,BigFloat}

julia>

   可以看到,这次调用 promote 函数后得到的元组包含了两个 BigFloat 类型的值.这就说明我们刚刚编写的类型提升规则已经生效了.当然,修改 Julia 内置的类型提升规则是比较危险的.因为这可能会改变已有代码的基本行为,并且会明显地降低程序的稳定性,所以还是要谨慎为之.但对于我们自己搭建的数值类型体系来讲,这一特性的潜力是非常可观的.

   总之,Julia 的类型提升系统辅助维护着数学运算的具体实现.其中有着大量的默认规则,并确保着常规运算的有效性.但同时,它也允许我们自定义类型提升的规则,以满足自己的特殊需要.


致读者: 小时百科一直以来坚持所有内容免费,这导致我们处于严重的亏损状态。 长此以往很可能会最终导致我们不得不选择大量广告以及内容付费等。 因此,我们请求广大读者热心打赏 ,使网站得以健康发展。 如果看到这条信息的每位读者能慷慨打赏 10 元,我们一个星期内就能脱离亏损, 并保证在接下来的一整年里向所有读者继续免费提供优质内容。 但遗憾的是只有不到 1% 的读者愿意捐款, 他们的付出帮助了 99% 的读者免费获取知识, 我们在此表示感谢。

                     

友情链接: 超理论坛 | ©小时科技 保留一切权利