【问题标题】:Why does Rust's u64.pow expect a u32?为什么 Rust 的 u64.pow 期望 u32?
【发布时间】:2019-11-28 20:49:34
【问题描述】:

为什么 Rust 的 u64 原语需要 u32 指数?

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

【问题讨论】:

  • 考虑对于非 0 或 1 的值,最大可能的非溢出指数是 63。
  • 谢谢,这也是我的假设,它只是让代码变得混乱(但这显然比出现运行时错误要好。

标签: types rust integer integer-arithmetic


【解决方案1】:

这是有根据的猜测。

考虑将 u64 提升到 u32 已经足以溢出任何 u64。例如,如果我们取 2(可能的最小有效 u64)并将其提高到仅 2^10 或 1024,我们会得到比 u64 更大的东西。

179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216 P>

允许更大的指数是没有意义的,它只是意味着更昂贵的计算更有可能溢出。

如果 u64 不能安全地降级为 u32,它肯定不能安全地用作指数。出于同样的原因,u128.pow 也只采用 u32 指数。

注意:有overflowing_pow 来检测可能的溢出。

【讨论】:

【解决方案2】:

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

我们很容易看出2i32 + 2i64 应该是4i64,但对于CPU,2i322i64 是完全不同且完全不相关的东西。 CPU 内部的+ 实际上只是一个硬件,通常支持两个 32 位输入或两个 64 位输入,但不支持一个 32 位输入和一个 64 位输入。因此,为了将 i32 添加到 i64,必须将较短的数字符号扩展为 64 位,然后才能将两个值插入 ALU。

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

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

并不是所有的算术运算,即使是那些在硬件中实现的,都接受相同类型的参数。在硬件中(虽然不是在 LLVM 中),移位指令通常会忽略移位参数的高位(这就是为什么在 C 中移位超过整数的大小会调用未定义的行为)。 LLVM 提供 powi instructions 将浮点数提高到整数幂。

这些操作是不同的,因为输入是不对称的,设计人员经常利用这些不对称性来使硬件更快、更小。但是,对于u64::pow,它没有使用硬件指令实现:it's just written in plain Rust。考虑到这一点,很明显,要求指数为 u64 是完全没有必要的:正如 Schwern's answer 指出的那样,u32 完全能够包含 u64 可以提升到的所有可能的权力有任何准确性的希望,所以额外的 32 位将毫无意义。

好的,为什么是u32

最后一句话同样适用于u16 甚至u8——u64 不能包含pow(2, 255),因此使用u32 似乎几乎一样浪费。但是,也有实际的考虑。许多调用约定在寄存器中传递函数参数,因此在 32 位(或更大)平台上,您不会看到比这更小的任何优势。许多 CPU 也不支持本机 8 位或 16 位算术,因此无论如何都必须对参数进行符号扩展,以实现我之前链接的乘方求幂算法。简而言之,我不知道具体为什么选择u32,但这样的事情可能是决定的因素。


¹ C 的规则在某种程度上受到历史的阻碍,并支持广泛的历史硬件。 Rust 只针对 LLVM,因此编译器无需担心底层硬件是否有原始的 8 位 add 指令;它只是发出 add 并让 LLVM 担心它是否会被编译为原始指令或用 32 位指令模拟。这就是为什么 char + char 在 C 中是 int,但 i8 + i8 在 Rust 中是 i8

【讨论】:

    猜你喜欢
    • 2022-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-02
    • 2020-02-05
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多