为什么Rust的u64.pow函数需要传入u32类型的参数?

14
为什么Rust中的原始类型需要一个指数?
error[E0308]: mismatched types
  --> src/protagonists.rs:13:25
   |
13 |         return root.pow(self.secret) % prime;
   |                         ^^^^^^^^^^^ expected u32, found u64
help: you can convert an `u64` to `u32` and panic if the converted value wouldn't fit

https://doc.rust-lang.org/std/primitive.u64.html#pow.v


3
对于非0和1的值,最大可能的不溢出指数为63。 - zneak
谢谢,我也是这么想的,只是这样会让代码变得很混乱(但显然这比出现运行时错误要好)。 - joedborg
2个回答

14

为什么大多数操作需要相同类型的操作数?

对于我们来说,2i32 + 2i64 应该是 4i64 很容易理解,但对于 CPU 来说,2i322i64 是完全不同且毫无关联的。CPU 内部的 + 实际上只是一块硬件,通常支持两个 32 位输入或两个 64 位输入,但不支持一个 32 位输入和一个 64 位输入。因此,在将 i32 加到 i64 时,较短的数字必须先进行符号扩展到 64 位,然后才能将两个值插入 ALU 进行计算。

大多数整数和浮点运算操作通常也是如此:必须进行转换以执行不匹配类型的运算。在 C 中,编译器通常将两个操作数提升为可以表示两个值的最小类型;这些隐式转换根据上下文称为 "integer promotions" 或 "usual arithmetic conversions"。然而,在 Rust 中,编译器主要只知道相同类型的操作,因此您必须通过决定如何转换操作数来选择想要的操作类型。喜欢 Rust 的人通常认为这是一件好事。¹

为什么这不适用于 u64::pow

并非所有算术运算(即使是硬件实现的)都接受相同类型的参数。在硬件中(尽管不在LLVM中),移位指令经常忽略移位参数的高位(这就是为什么在C中移位超过整数大小会引发未定义行为)。LLVM提供powi指令,将浮点数提高到整数幂次方。
这些操作是不同的,因为输入是不对称的,并且设计者通常利用这些不对称性使硬件更快、更小。然而,在u64::pow的情况下,它没有使用硬件指令:它只是用普通的Rust编写。考虑到这一点,要求指数为u64是相当不必要的:正如Schwern的答案所指出的那样,u32足以包含所有可能的幂,可以提高到u64希望获得准确度的水平,因此额外的32位将是毫无意义的。
好的,为什么是u32
最后一句话也同样适用于甚至 -- 一个不能包含,因此使用似乎几乎是浪费的。但是,还有实际考虑因素。许多调用约定在寄存器中传递函数参数,因此在32位(或更大)平台上,缩小到比这更小不会带来任何优势。许多CPU也不支持本机8位或16位算术,因此参数必须被符号扩展以实现我之前链接的平方指数算法。简而言之,我不知道具体为什么选择了,但像这样的事情可能已经纳入决策。

C语言的规则受历史和支持各种历史硬件的影响。Rust只针对LLVM,因此编译器不需要担心底层硬件是否具有原始的8位add指令;它只是发出add,并让LLVM担心它是否将被编译为原始指令或使用32位指令进行模拟。这就是为什么在C中char+charint,但在Rust中i8+i8i8的原因。


8

这只是一个有根据的猜测。

考虑到将一个u64提高到一个u32已经足以溢出任何u64的范围。例如,如果我们将2(最小可能的有意义的u64)仅仅提高到2^10或1024,我们得到的结果比u64还大。

179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216

没有必要允许更大的指数,这只会导致计算更加费时且更容易溢出。

如果一个u64不能安全地降级为u32,那么它绝对不能作为指数安全地使用。同样的原因,u128.pow也仅接受u32的指数。

注意:还有overflowing_pow可以用于检测可能发生的溢出。


如果这是原因,为什么不使用u8而是使用u32呢? - Peter Hall
1
@PeterHall trentcl的回答有一种理论。我会补充我的想法。在像 Rust 这样的低级语言中,整数溢出有时是一件可取的事情,比如哈希函数。将指数幂限制为 u8 会削弱这一点。Rust 的关于溢出的设计随着时间的推移而演变,他们似乎已经决定使用显式的“包裹”操作符 - Schwern

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接