防止算术溢出
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 的幂。
我想使用 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 的幂。