предотвратить арифметическое переполнение
Я хочу рассчитать напряжение с помощью периферийного АЦП PIC18F14K50. Результат находится в диапазоне от 0 до 1023 (10 бит). Итак, я использовал этот простой расчет:
uint16_t voltage = ADC_Result * 5000 / 1023;
Однако результаты неверны. Я предполагаю, что произошло арифметическое переполнение. Я пробовал много сочетаний скобок, изменения порядка элементов и т
. Д. Лучший результат был 4088, когдаADC_Result
было 1023 с использованием кода ниже; что действительно далеко от 5000.
uint16_t voltage = ADC_Result * (5000 / 1023);
Что мне делать, чтобы получить лучшие результаты в приведенном выше расчете? Пожалуйста, не предлагайте числа с плавающей запятой, так как они вызывают сбой в MCU! Они используют много ресурсов без какой-либо реальной выгоды.
3 ответа
Вы можете использовать более широкий тип для промежуточного умножения, например:
uint16_t voltage = (uint32_t)ADC_Result * 5000 / 1023;
РЕДАКТИРОВАТЬ
Если деление на 1023 слишком медленное, вы можете получить примерно равное преобразование, изменив 5000 / 1023 на 5005 / 1024, что может использовать быстрый битовый сдвиг для деления:
uint16_t voltage = (uint32_t)ADC_Result * 5005 >> 10;
NB 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;
}
Что мне делать, чтобы получить лучший результат в приведенном выше расчете?
Переполнение кода ОП 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;
В L
в 5000LU
предоставляет как минимум 32-битную математику.
Использовать U
для потенциально более простой / быстрой математики и более простого округления ADC_Result
не отрицательный.
+ 1024/2
выполняет округление до ближайшего, а не усечение.
Используйте 1024 вместо 1023 для правильного масштабирования с учетом обычных характеристик аналого-цифровых преобразователей. Дополнительное преимущество: более быстрое деление, поскольку 1024 - это степень двойки.