Странные результаты арифметики по модулю с отрицательными числами и знаменателями без знака - PullRequest
12 голосов
/ 29 января 2012

У меня есть массив в C, к которому я хочу обратиться аналогично циклическому буферу, например: a[-1] вернул бы мне последний элемент массива.

Для этого я попыталсяесли использовать арифметику по модулю (очевидно), проблема в том, что я получаю довольно странные результаты, когда задействованы отрицательные числа:

-1 % 4 = -1
-1 % 4U = 3

Пока все хорошо.

-1 % 4000 = -1
(-1+4000U) % 4000U = 3999
(-1) % 4000U = 3295

Вопрос: Значение (3295) действительно для (a/b)*b + a%b shall equal a, truncated towards zero (для a=-1, b=4000) из стандарта C (6.5.5 # 6), так что это не ошибка per se , но почему стандарт определен таким образом ?!Конечно, в этом должна быть какая-то логика ...

Как мне написать a%b, чтобы получить ощутимые результаты для отрицательных значений a (так как (a+b)%b перестает работать, когда abs(a)>b)?

Тестовое приложение:

#include <stdio.h>
int main(int argc, char **argv) {
  int i=0;
#define MAX_NUM 4000U
  int weird = (i-1)%MAX_NUM;
  printf("%i\n", weird);
  printf("%i\n", (i-1+MAX_NUM))%MAX_NUM);
  printf("a: %i, b: %i, a from equation: %i\n", i-1, MAX_NUM,
    ((i-1)/MAX_NUM)*MAX_NUM + weird);
  return 0;
}

Ответы [ 2 ]

9 голосов
/ 29 января 2012

Арифметика в Си всегда (за исключением некоторых странностей с операторами сдвига битов) переводит все операнды в общий тип перед выполнением операции.Таким образом:

(-1) % 4000U

продвигается как (при условии 32-битных целых):

0xffffffffu % 4000u

, что дает 3295.

Если вы хотите использовать модульную арифметику для смещений массиваэто может быть отрицательным, вы должны сначала отказаться от использования арифметики без знака на смещениях.Таким образом, ваши результаты теперь будут в диапазоне от -MAX_NUM+1 до MAX_NUM-1, из-за уродливого определения С целого числа со знаком и остатка.Если код не критичен к производительности, просто добавьте if (result<0) result+=MAX_NUM; и покончите с этим.Если вам действительно нужно избегать ветвления (и вы измерили , чтобы определить, что вам нужно его избегать), то снова спросите, как оптимизировать это вычисление, и я или кто-то более яркий, чем я, на SO, несомненно, сможетпомогать.: -)

5 голосов
/ 29 января 2012

Как говорится в 6.5.3, «обычные арифметические преобразования выполняются над операндами». В случае вашего примера:

(-1) % 4000U

, что означает преобразование -1 в unsigned int. Таким образом, ваш -1 действительно интерпретируется как 4294967295 ... для которого остаток именно то, что вы видите: 3295.

«Обычные арифметические преобразования» описаны в 6.3.1.8.

...