Странные результаты по модулю арифметики с отрицательными числами и беззначными знаменателями
У меня есть массив в 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;
}
Ответы
Ответ 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.