为什么Rust的u64.pow期望使用u32? [英] Why does Rust's u64.pow expect a u32?

查看:125
本文介绍了为什么Rust的u64.pow期望使用u32?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么Rust的 u64 原语期望 u32 指数?

 错误[E0308]:类型不匹配->src/protagonists.rs:13:25|13 |返回root.pow(self.secret)%素数;|预期的u32,找到的u64 ^^^^^^^^^^^^^帮助:您可以将u64转换为u32,如果转换后的值不合适,则可以恐慌 

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

解决方案

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

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

大多数整数和浮点算术运算通常也是如此:必须进行转换才能对不匹配的类型进行数学运算.在C语言中,编译器通常将两个操作数提升为可以表示两个值的最小类型;根据上下文,这些隐式转换称为整数促销"或常规算术转换" .但是,在Rust中,编译器通常只知道相同类型的操作,因此您必须通过确定如何转换操作数来选择所需的操作类型.喜欢Rust的人通常认为这是一件好事.¹

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

并非所有算术运算(甚至是在硬件中实现的运算运算)都接受相同类型的参数.在硬件中(尽管不是在LLVM中),移位指令通常会忽略shift参数的高位(这就是为什么在C语言中,移位超过整数的大小会引起未定义的行为).LLVM提供了 powi 指令点数到整数幂.

这些操作是不同的,因为输入是不对称的,设计人员经常利用这些不对称性来使硬件更快,更小.但是,在 u64 :: pow 的情况下,不是是通过硬件指令实现的: Schwern的答案指出, 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 .

Why is it that Rust's u64 primitive expects a u32 exponent?

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

解决方案

Why do most operations require operands of the same type?

It's easy for us to see that 2i32 + 2i64 should be 4i64, but to a CPU, 2i32 and 2i64 are completely different and totally unrelated things. + inside a CPU is really just a piece of hardware that typically supports two 32-bit inputs or two 64-bit inputs, but not one 32-bit input and one 64-bit input. So in order to add an i32 to an i64, the shorter number has to be sign-extended to 64 bits before both values can be plugged in to the ALU.

The same is generally true for most integer and floating-point arithmetic operations: A conversion has to be done to do math on mismatched types. In C, the compiler usually promotes both operands to the smallest type that can represent both values; these implicit conversions are called the "integer promotions" or "usual arithmetic conversions" depending on context. In Rust, however, the compiler mostly only knows about same-type operations, so you have to choose what kind of operation you want by deciding how to convert the operands. People who like Rust generally consider this to be a good thing.¹

Why doesn't this apply to u64::pow?

Not all arithmetic operations, even those implemented in hardware, accept arguments of the same type. In hardware (although not in LLVM), shift instructions often ignore the higher bits of the shift argument (this is why in C shifting by more than the size of the integer invokes undefined behavior). LLVM offers powi instructions that raise a floating-point number to an integer power.

These operations are different because the inputs are asymmetric, and designers often take advantage of these asymmetries to make hardware faster and smaller. In the case of u64::pow, though, it's not implemented with a hardware instruction: it's just written in plain Rust. Bearing that in mind, it's clear that requiring the exponent to be a u64 is quite unnecessary: As Schwern's answer points out, u32 is more than capable of containing all the possible powers that a u64 could be raised to with any hope of accuracy, so the extra 32 bits would be just pointless.

OK, why u32?

The last sentence is equally true of u16 or even u8 -- a u64 can't contain pow(2, 255), so using u32 seems nearly as wasteful. However, there are also practical considerations. Many calling conventions pass function arguments in registers, so on a 32-bit (or larger) platform you will not see any advantage from going smaller than that. Many CPUs also do not support native 8-bit or 16-bit arithmetic, so the argument would have to be sign-extended anyway to implement the exponentiation-by-squaring algorithm I linked to earlier. In short, I don't know specifically why u32 was chosen, but things like this may have factored into the decision.


¹ C's rules are somewhat encumbered by history and supporting a wide range of historical hardware. Rust only targets LLVM, so the compiler doesn't need to worry about whether the underlying hardware has a primitive 8-bit add instruction or not; it just emits add and lets LLVM worry about whether it will be compiled to a primitive instruction or emulated with 32-bit ones. This is why char + char is int in C, but i8 + i8 is i8 in Rust.

这篇关于为什么Rust的u64.pow期望使用u32?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆