为了练习,我在rust中实现了qoi specification.其中有一个小的散列函数来存储最近使用的像素:

指数位置=(r*3+g*5+b*7+a*11)%64

其中r、g、b和a分别为红色、绿色、蓝色和阿尔法通道.

我假设这是一个散列,因为它用mod为数字创建了一个唯一的素数分解,以限制字节数.总之,我在代码中天真地实现了它.

在查看其他实现时,我遇到了一个优化哈希计算的bit hack:

fn hash(rgba:[u8:4]) -> u8 {
        let v = u32::from_ne_bytes(rgba);
        let s = (((v as u64) << 32) | (v as u64)) & 0xFF00FF0000FF00FF;

        s.wrapping_mul(0x030007000005000Bu64.to_le()).swap_bytes() as u8 & 63
    }

我想我了解大部分情况,但我对幻数(被乘数)感到困惑.据我所知,它应该被翻转.作为一个逐步的例子:

  1. let rgba = [0x12, 0x34, 0x56, 0x78].
  2. 在我的机器(little endian)上,v的值为0x78563412.
  3. 位移位使值扩散,得到s = 0x7800340000560012.
  4. 这就是我困惑的地方.幻数的值应在64位字段(3、5、7、11)中对齐相乘,间距与原始值相同.然而,它们似乎与值的顺序相反:
0x7800340000560012
0x030007000005000B

相乘时,最高值alpha通道(0x78)似乎被乘以3,而最低值红色通道(0x12)被乘以11.我也不完全确定,为什么这个乘法在乘以不同的2次幂后仍然有效.

我知道字节会被交换到big-endian并被修剪,但直到乘法步骤之后,我才明白这一点.

我知道代码生成了正确的散列,但我不明白为什么会这样.有人能解释一下我错过了什么吗?

推荐答案

如果你想一想数学运算的方式,你可以按照这个翻转的顺序来计算,因为它意味着same字节中每个"逻辑"乘法集群的所有结果.第一个值中的最高字节乘以第二个值中的最低字节,得到最高字节的结果.第一个值的乘积中的最低字节与第二个值中的最高字节生成结果in the same highest byte,中间字节也是如此.

是的,0x78...0x03...也会相乘,但它们会溢出way超过值的顶部,并丢失."向后"的顺序意味着我们关心的乘法的结果都会在最高字节中求和(我们想要的结果的总移位量总是56位,因为第56位偏移量值乘以第0位、第40位乘以第16位、第16位乘以第40位、第0位乘以第56位),对于其余的乘法,我们希望它们的结果要么溢出(并丢失),要么以较低的字节出现(我们忽略).如果翻转第二个值中的字节,0x78 * 0x0B(alpha值和乘法器)组件将因溢出而丢失,而0x12 * 0x03(red值和乘法器)组件将无法到达目标字节(我们关心的每个组件最终都不是最上面的字节).

举一个可能更直观的例子,想象一下做同样的工作,但一个输入的所有字节(除了单个组件)都是零.如果乘以:

0x7800000000000000 * 0x030007000005000B

合乎逻辑的结果是:

0x1680348000258052800000000000000

但清除溢出会将其减少到:

0x2800000000000000
//^^ result we care about (actual product of 0x78 and 0x0B is 0x528, but only keeping low byte)

同样地,

0x0000340000000000 * 0x030007000005000B

生产:

0x9c016c000104023c0000000000

涌向:

0x04023c0000000000
//^^ result we care about (actual product of 0x34 and 0x5 was 0x104, but only 04 kept) 

在这种情况下,其他乘法确实会在结果中留下数据(并非全部溢出),但由于我们只查看高字节,其余的被忽略.

如果你继续一步一步地做这个数学运算,并把结果相加,你会发现高字节最终是你期望的四个单独乘法的正确答案(mod 256);颠倒顺序,结果就不会是这样.

将所有结果放在高字节中的好处是,它允许您使用swap_bytes将其廉价地移动到低字节,并直接读取值(甚至不需要在许多体系 struct 上屏蔽它).

Rust相关问答推荐

文档示例需要导入相关的 struct ,但仅在运行测试时.这是故意的行为吗?

亚性状上位性状上的 rust 病伴生型界限

如何在Tauri中将变量从后端传递到前端

获取字符串切片(&;str)上的切片([ia..ib])返回字符串

在UdpSocket上使用sendto时的隐式套接字绑定

不能在Rust中使用OpenGL绘制三角形

.在 Rust 模块标识符中

使用占位符获取用户输入

为什么是&mut发送?线程如何在安全的 Rust 中捕获 &mut?

如何将 &[T] 或 Vec<T> 转换为 Arc<Mutex<[T]>>?

Rust 中 Mutex<> 的深拷贝?

如何在 Rust 中将 bson::Bson 转换为 Vec

如何获取函数中borrow 的切片的第一部分?

仅当满足外部条件时如何添加到 actix web 的路由

预期类型参数,发现不透明类型

为什么这个闭包没有比 var 长寿?

带有库+多个二进制文件的Cargo 项目,二进制文件由多个文件组成?

Rust 中的运行时插件

如何在不设置精度的情况下打印浮点数时保持尾随零?

类型参数不受 impl 特征、自身类型或谓词的约束