You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何基于MMX SIMD的C数组乘法函数返回补码形式的int值?

问题根源分析

你的问题本质是数据类型不匹配加上MMX指令的特性导致的符号扩展错误,具体拆解如下:

1. 函数设计目标与你的使用方式不匹配

mmx_mul函数是为**16位整数数组(int16_t)**设计的,但你传递的是32位int数组:

  • 函数内部用movq加载64位数据(即4个16位元素),用pmullw指令对每对16位带符号整数执行乘法,结果保留低16位补码。
  • 当你把32位int数组传入时,每个32位int会被拆成两个16位元素(小端字节序下,低字节在前)。比如你的A[0] = -1(32位补码0xFFFFFFFF)会被拆成两个16位的0xFFFF(即-1),B[0] = -1同理。
  • 执行pmullw后,每对16位的-1 * -1得到1(16位补码0x0001),函数会把4个这样的16位结果打包成64位,然后写入内存——对应两个32位int:0x00010001(即65537),这就是你看到的第一个异常值。

2. 16位补码到32位int的符号扩展缺失

当函数把16位乘法结果写入内存后,你用32位int读取时,默认会把16位值当成无符号数扩展到32位(高16位补0):

  • 比如16位的-4补码是0xFFFC,当作为32位int读取时,会变成0x0000FFFC(即65532),而不是正确的带符号扩展0xFFFFFFFC(即-4)。
  • 只有当结果是正数时,16位补码和32位补码的低16位一致,所以你看到最后一个值7是正确的。

3. 额外的最高位1来自哪里?

你提到两个负数相乘时结果多一位最高位1,比如65537(0x10001),这其实是两个16位乘法结果被打包进了同一个32位int:这个int的低16位是第一个乘法结果1,高16位是第二个乘法结果1,合起来就是0x00010001,看起来像多了一位,但实际是两个独立的16位结果拼在了一起。


解决方案

方案1:使用正确的16位整数数组

把你的数组类型改成int16_t(需要包含<stdint.h>),并调整调用逻辑:

#include <stdint.h>

int16_t A[8] = {-1, 4, 1, -1, 1, -2, -3, 7};
int16_t B[8] = {-1, -1, -1, -1, -1, -1, -1, 1};
int16_t C[8];
// 总元素数是8,不是16!因为每个元素是16位,8个元素就是8个16位值
mmx_mul(A, B, C, 8);

之后如果需要把int16_t转成int,编译器会自动处理16位到32位的带符号扩展:

int result = (int)C[0];

方案2:修复结果读取时的符号扩展

如果不能修改数组类型,在读取C数组时,手动将低16位的16位补码符号扩展到32位:

// 对每个C[i],提取低16位并符号扩展
int corrected = (int)(int16_t)(C[i] & 0xFFFF);

比如C[1] = 65532(0x0000FFFC),C[i] & 0xFFFF得到0xFFFC,转成int16_t就是-4,再转成int就是正确的-4。

方案3:修改函数适配32位int数组

如果你坚持用32位int数组,需要调整函数逻辑:

  • 改用支持32位带符号乘法的SIMD指令(比如SSE2的pmulld),因为MMX没有原生的32位带符号乘法指令。
  • 调整循环计数和内存操作步长(每个32位int占4字节,而非2字节)。

不过从你的需求来看,方案1是最贴合原函数设计的最优解。


额外注意点
  • 函数末尾的EMMS指令是正确的,用来清空MMX状态,避免和x87浮点指令冲突。
  • L3循环里的代码有小问题:mov eax, [edi]读取的是4字节,但add edi,2,应该改成mov ax, [edi]来读取16位元素,避免读取多余的字节:
    mov ax, [edi]; 读取16位元素
    imul ax, [esi]; 16位乘法
    mov [ebx], ax; 存储16位结果
    

内容的提问来源于stack exchange,提问作者Jake

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:53:10