Неверный результат при работе с беззнаковыми целыми числами

У меня проблема с арифметической операцией с целочисленными переменными без знака.

Все переменные определены как uint32_t. Это арифметическая операция:

batt += (uint32_t) ((((charg - discharg) * (time_now - time_old)) / 1000) + 0.5);

Значения до операции:

batt = 8999824
charg = 21
discharg = 1500
time_now = 181
time_old = 132

Проблема в том, что результат после операции

batt = 13294718

вместо

batt = 8999752

В чем причина?

Заранее спасибо.


person aliants    schedule 22.04.2013    source источник
comment
charg - discharg вызвать переполнение беззнакового целого числа.   -  person MYMNeo    schedule 22.04.2013
comment
charg - discharg отрицательно относится к вашим данным.   -  person Roger Rowland    schedule 22.04.2013
comment
charg - discharg ниже нуля и поэтому переполняется. Это намеренно?   -  person CodesInChaos    schedule 22.04.2013
comment
@CodesInChaos это значения, считанные из txt-файла, иногда бывает, что charg больше, чем discharg, иногда наоборот. Как я могу сделать общий случай? Спасибо.   -  person aliants    schedule 22.04.2013
comment
@ Я бы использовал 64-битную подпись для вычислений   -  person CodesInChaos    schedule 22.04.2013
comment
возьмите величину разницы. Используйте abs(). Поскольку вы читаете из файла в discharg, вы можете также прочитать abs() (при условии, что в файле могут быть значения -ve)   -  person Koushik Shetty    schedule 22.04.2013
comment
Я решил с помощью: batt += (uint32_t) (((int) ((charg - discharg) * (time_now - time_old)) / 1000) + 0,5); Большое спасибо!   -  person aliants    schedule 22.04.2013
comment
@aliants: Ваш + 0.5 по-прежнему ничего не делает, поскольку / 1000 уже отбросил любую дробную часть.   -  person aschepler    schedule 03.07.2013


Ответы (2)


У тебя 2 проблемы.

  1. charg < discharg создает циклический ответ 4294965817 для charg - discharg. Ниже показано, почему вы получили 13294718.

  2. Сделайте смещение (+ 0,5) перед /1000, иначе целочисленное деление будет полностью готово, отбросив дробную часть.

Рекомендуемое исправление 1: обеспечить заряд >= разряд.

OR

Рекомендуемое исправление 1: изменить charg, discharg, time_now, time_old и, возможно, batt на int32_t.

Рекомендуемое исправление 2: измените округление на batt += (uint32_t) ((Product / 1000.0) + 0.5);.

OR

Рекомендуемое исправление 2: измените округление на batt += (Product + 500*sign(Product))/1000;.


Очевидный ошибочный код - шаг за шагом.

uint32_t batt = 8999824;
uint32_t charg = 21;
uint32_t discharg = 1500;
uint32_t time_now = 181;
uint32_t time_old = 132;
// batt += (uint32_t) ((((charg - discharg) * (time_now - time_old)) / 1000) + 0.5);

// The big problem occurs right away.
// Since charg is less than discharg, and unsigned arithmetic "wrap around", 
// you get (21 - 1500) + 2**32 = (21 - 1500) + 4294967296 = 4294965817
uint32_t d1 = charg - discharg;
uint32_t d2 = time_now - time_old; // 49
// The product of d1 and d2 will overflow and the result is mod 4294967296
// (49 * 4294965817) = 210453325033
// 210453325033 mod 4294967296 = 4294894825
uint32_t p1 = d1 * d2;
uint32_t q1 = p1/1000;  // 4294894825/1000 = 4294894.825.  round to 0 --> 4294894
double s1 = q1 + 0.5;  //  4294894 + 0.5 --> 4294894.5;
uint32_t u1 = (uint32_t) s1; // 4294894.5 round to 0 --> 4294894
batt += u1; // 8999824 + 4294894 --> 13294718
person chux - Reinstate Monica    schedule 03.07.2013

Результат charg - discharg отрицательный, поэтому все выражения являются отрицательными, то есть довольно большими unsigned.

person Alex    schedule 22.04.2013
comment
Разница вряд ли будет отрицательной, поскольку оба значения имеют один и тот же тип без знака (может быть отрицательным, если этот тип меньше int). Таким образом, разница потенциально является большой положительной величиной. Другие возможные проблемы включают переполнение при умножении и потерю точности при делении. - person Alexey Frunze; 22.04.2013