Подтвердить что ты не робот

Странные результаты по модулю арифметики с отрицательными числами и беззначными знаменателями

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

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

-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), поэтому оно не является ошибкой как таковой, но почему это стандарт, определенный таким образом?! Конечно, в этом должна быть какая-то логика...

Как мне написать 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;
}
4b9b3361

Ответ 1

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

(-1) % 4000U

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

0xffffffffu % 4000u

что дает 3295.

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

Ответ 2

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

(-1) % 4000U

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

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