【问题标题】:Wrong results multiplying two 32 bit numbers in C在 C 中将两个 32 位数字相乘的错误结果
【发布时间】:2015-03-08 21:02:13
【问题描述】:

我正在 C 中尝试两个乘法矩阵,但我不明白为什么会得到这些结果...

我想做:Btranspose * B

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>                        
#include <math.h>     

#define LOW_WORD(x)  (((x) << 16) >> 16) 
#define HIGH_WORD(x) ((x) >> 16)
#define ABS(x) (((x) >= 0) ? (x) : -(x))
#define SIGN(x) (((x) >= 0) ? 1 : -1)

#define UNSIGNED_MULT(a, b) \
    (((LOW_WORD(a)  * LOW_WORD(b))  <<  0) + \
     (((int64_t)((LOW_WORD((a)) * HIGH_WORD((b))) + (HIGH_WORD((a)) * LOW_WORD((b))))) << 16) + \
     ((int64_t)(HIGH_WORD((a)) * HIGH_WORD((b))) << 32))

#define MULT(a, b)  (UNSIGNED_MULT(ABS((a)), ABS((b))) * SIGN((a)) * SIGN((b)))


int main()
{
    int c,d,k;
    int64_t multmatrix[3][3];
    int64_t sum64 = 0;
    int32_t Btranspose[3][3] = {{15643, 24466, 58751},
                               {54056, 26823, -25563},
                               {-33591, 54561, -13777}};
    int32_t B[3][3] = {{15643, 54056, -33591},
                      {24466, 26823, 54561},
                      {58751, -25563, -13777}};

    for ( c = 0 ; c < 3 ; c++ ){
        for ( d = 0 ; d < 3 ; d++ ){
            for ( k = 0 ; k < 3 ; k++ ){
                sum64 = sum64 + MULT(Btranspose[c][k], B[k][d]);
                printf("\n the MULT for k = %d is: %ld \n", k, MULT(Btranspose[c][k], B[k][d]));
                printf("\n the sum for k = %d is: %ld \n", k, sum64);
            }
            multmatrix[c][d] = sum64;
            sum64 = 0;
        }
    }       

    printf("\n\n multmatrix \n");
    for( c = 0 ; c < 3; c++ ){
        printf("\n");
        for( d = 0 ; d < 3 ; d++ ){
            printf(" %ld  ", multmatrix[c][d]);
        }
    }
    return 0;
}

我的输出低于 put 那是错误的,我注意到错误是在将第三个元素 (58751 * 58751) 乘以 k=2 时出现错误。 我认为不会溢出,因为 58751^2 需要 32 位。

k = 0 的 MULT 是:244703449 k = 0 的总和是:244703449 k = 1 的 MULT 是:598585156 k = 1 的总和是:843288605 k = 2 的 MULT 是:46036225 // 这是错误的!!! k = 2 的总和是:889324830 . . . . k = 2 的 MULT 是:189805729 k = 2 的总和是:1330739379 多矩阵 889324830 650114833 324678230 650114833 1504730698 -308929574 324678230 -308929574 1330739379

正确的结果应该是

   multmatrix - correct

   4.2950e+09  -2.2870e+03   1.2886e+04
  -2.2870e+03   4.2950e+09  -1.2394e+05
   1.2886e+04  -1.2394e+05   4.2951e+09

为什么矩阵的乘法错了?? 我应该如何更改上面的代码,以使两个矩阵的乘法不会溢出??

(我正在尝试编写一个程序,将两个 32 位数字相乘以导入到只有 32 位寄存器的系统上)


所以根据下面的答案,这实际上有效

#define LOW_WORD(x)  ((uint32_t)(x) & 0xffff)
#define HIGH_WORD(x) ((uint32_t)(x) >> 16)
#define ABS(x) (((x) >= 0) ? (x) : -(x))
#define SIGN(x) (((x) >= 0) ? 1 : -1)

#define UNSIGNED_MULT(a, b) \
    (((LOW_WORD(a)  * LOW_WORD(b))  <<  0) + \
     ((int64_t)(LOW_WORD(a) * HIGH_WORD(b) + HIGH_WORD(a) * LOW_WORD(b)) << 16) + \
     ((int64_t)(HIGH_WORD((a)) * HIGH_WORD((b))) << 32))

#define MULT(a, b)  (UNSIGNED_MULT(ABS((a)), ABS((b))) * SIGN((a)) * SIGN((b)))

感谢您帮助我理解一些事情!我会尝试将整个事情变成函数并将其发布回来。

【问题讨论】:

  • 我懒得正确解析它,但是高 x 低
  • 您的 shift 宏会导致未定义的行为。一个好的开始是将LOW_WORD 更改为#define LOW_WORD(x) ((x) % 0x10000u) 尽管还有其他问题
  • 哦,虽然我可以同情不相信某些编译器不会正确内联,但至少在尝试调试之前尝试将宏混乱展开为直线 C(或至少在之前让我们服从)
  • @Franx LOW_WORDHI_WORD 只有在 uint32_t 上调用时才能正常工作。对于其他类型,它们会做错事和/或导致未定义的行为。例如左移到符号位会导致未定义的行为;并且右移负值可能无法达到您的预期。
  • @Franx:我的意思是通过扩展宏来摆脱宏,最好用函数和/或子表达式替换它们。这应该使代码更清晰,更容易在调试器中跟踪。就目前而言,您必须在心理上扩展代码才能弄清楚中间类型和表达式中发生了什么。

标签: c matrix arm fixed-point cortex-m


【解决方案1】:

这个

(((x) << 16) >> 16)

如您所料,不会产生无符号 16 位数。这个表达式的类型和x的类型一样,都是int32_t(有符号整数)。事实上,如果使用任何合理的(二进制补码)C 实现,对于x=58751

x                   = 00000000000000001110010101111111
(x) << 16           = 11100101011111110000000000000000 (negative number)
(((x) << 16) >> 16) = 11111111111111111110010101111111 (negative number)

要正确提取低 16 位,请使用无符号算术:

((uint32_t)(x) & 0xffff)

或者(保持你的风格)

((uint32_t)(x) << 16 >> 16)

要获得高位字,您也必须使用无符号算术:

((uint32_t)(x) >> 16)

此外,编译器可能需要帮助确定此表达式的范围(进行优化):

(uint16_t)((uint32_t)(x) & 0xffff)

一些(全部?)编译器足够聪明,可以自己完成。


另外,正如 doynax 所指出的,低位字和高位字的乘积是一个 32 位数字(或 31 位,但没关系)。要将其左移 16 位,您必须将其转换为 64 位类型,就像使用高位字一样:

((int64_t)(LOW_WORD(a) * HIGH_WORD(b) + HIGH_WORD(a) * LOW_WORD(b)) << 16)

【讨论】:

  • 为什么不直接转换为 int16_t?
  • @user3528438 是的,这将是最好的选择。但也许在语言律师的意义上不便携?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-13
  • 2014-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多