防止算术溢出

prevent arithmetic overflow

我想使用 PIC18F14K50 的 ADC 外设计算电压。结果范围在 0-1023(10 位)之间。所以我使用了这个简单的计算:

uint16_t voltage = ADC_Result * 5000 / 1023;

但是,结果不正确。我猜发生了算术溢出。我尝试了很多括号组合、改变元素顺序等
使用以下代码,当 ADC_Result 为 1023 时,最佳结果为 4088;这离5000真的很远

uint16_t voltage = ADC_Result * (5000 / 1023);

在上面的计算中,我应该怎么做才能得到更好的结果?请不要建议浮点数,因为它们会在 MCU 中造成灾难!他们使用了大量资源,却没有任何实际好处。

中间乘法可以使用更宽的类型,例如:

uint16_t voltage = (uint32_t)ADC_Result * 5000 / 1023;

编辑

如果除以 1023 太慢,您可以通过将 5000 / 1023 更改为 5005 / 1024 来获得近似相等的转换,这可以使用快速位移来进行除法:

uint16_t voltage = (uint32_t)ADC_Result * 5005 >> 10;

N.B。 1023 * 5005 / 1024 ≃ 5000.1123

你应该为这个计算使用更广泛的整数类型,例如 uint32_t

在你的情况下,1023 * 5000 == 3192(因为实际结果 5115000 不适合),所以这是不正确的。 5000 / 1023 == 4,这是整数除法的预期结果。将 ADC_Result 除以 1023 将导致相同的行为。

您可以将其计算为 uint32_t,然后检查它是否适合 uint16_t:

uint32_t result_tmp = ADC_Result * (5000 / 1023);
uint16_t result;

if (result > 0xffff) {
    // this won't fit
} else {
    result = (uint16_t) result_tmp;
}

What should I do to get better results in above calculation?

OP 的代码溢出 1`6 位数学。


要获得正确的 舍入 结果,请使用更广泛的数学和偏移量。

// uint16_t voltage = ADC_Result * 5000 / 1023;
uint16_t voltage = (ADC_Result * 5000LU + 1024u/2) / 1024u;
// or
#include <stdint.h>
...
uint16_t voltage = (ADC_Result * UINT32_C(5000) + 1024u/2) / 1024u;

5000LU 中的 L 至少提供了 32 位数学。

使用 U 可能 simpler/faster 数学和更简单的四舍五入给定 ADC_Result 不是负数。

+ 1024/2 影响最接近的回合而不是截断。

鉴于 A/D 转换器的通常特性,使用 1024 而不是 1023 进行正确缩放。附带好处:除法速度更快,因为 1024 是 2 的幂。