我正在对两个定点整数进行 32 位无符号乘法,如下所示:
888.88 x 805.00 = 7,155,484,000
(大于 32 位)
但我需要如下结果:
888.88 x 805.00 = 715,548.4
我用 PIC16 汇编语言执行此操作,由于程序内存限制,我不想使用浮点例程。 有没有其他方法或技巧可以在 32 位内获得所需的结果?
我在这个问题上需要帮助! 如果可以的话请回答。
问题没有指定定点格式。我将假设采用二进制定点格式,而不是基于 BCD 数字的十进制格式。由于示例显示的是带有两位小数的数字,并且问题指定了无符号操作数,因此我选择映射到
uint32_t
的 UQ24.8 格式,其中 24 位分配给整数部分,8 位分配给小数部分。
每个定点数都包含一个基于分数位数的隐式比例因子。对于 UQ24.8 操作数,该比例因子为 28 = 256。当我们使用整数乘法来影响定点乘法时,比例因子被包含两次,因此我们需要除以比例因子以获得所需的 UQ24 .8 应用一次比例因子的结果。我们还可以通过在除法之前添加比例因子的一半来对乘法结果进行四舍五入。
根据我的经验,在大多数平台上,舍入定点乘法的开销足够低,因此通过防止截断引起的偏差误差而导致数值漂移是值得的。
我不熟悉PIC汇编语言,但下面的ISO-C99代码演示了原理。这个小程序的输出应该如下所示:
factor a = 000378e1 888.879
factor b = 00032503 805.012
truncated product: p = 0aeb25ef 715557.934
rounded product: p = 0aeb25f0 715557.938
对于十进制定点算术,定点乘法的机制是相同的,只是比例因子是 10 的幂,需要相对昂贵的除法而不是简单的扩展宽度移位。然而,这种除法是由一个常数进行的,因此可以用倒数乘法来代替,这是任何优化编译器都应该知道如何做的事情,使用 20 世纪 90 年代以来已知的“通用算法”。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <math.h>
#define UQ24P8_FRACT_BITS (8)
#define UQ24P8_INTGR_BITS (24)
#define UQ24P8_SCALE_FACT (1L << UQ24P8_FRACT_BITS)
#define UQ24P8_OVFL_LIMIT (1L << UQ24P8_INTGR_BITS)
typedef uint32_t uq24p8;
uq24p8 mul_uq24p8 (uq24p8 a, uq24p8 b, int round)
{
uint64_t p = ((uint64_t)a) * b;
if (!round) {
return (uq24p8)(p >> UQ24P8_FRACT_BITS);
} else {
return (uq24p8)((p + (UQ24P8_SCALE_FACT / 2)) >> UQ24P8_FRACT_BITS);
}
}
uq24p8 float_to_uq24p8 (float a)
{
float t = roundf (a * UQ24P8_SCALE_FACT);
if ((t < 0) || (t >= UQ24P8_OVFL_LIMIT)) {
fprintf (stderr, "float_to_uq24p8: conversion error\n");
}
return ((uq24p8)t);
}
double uq24p8_to_double (uq24p8 a)
{
return ((double)a) / UQ24P8_SCALE_FACT;
}
int main (void)
{
uq24p8 a = float_to_uq24p8 (888.88f);
uq24p8 b = float_to_uq24p8 (805.01f);
uq24p8 p;
printf ("factor a = %08" PRIx32 " %10.3f\n", a, uq24p8_to_double(a));
printf ("factor b = %08" PRIx32 " %10.3f\n", b, uq24p8_to_double(b));
p = mul_uq24p8 (a, b, 0);
printf ("truncated product: p = %08" PRIx32 " %10.3f\n", p, uq24p8_to_double(p));
p = mul_uq24p8 (a, b, 1);
printf ("rounded product: p = %08" PRIx32 " %10.3f\n", p, uq24p8_to_double(p));
return EXIT_SUCCESS;
}