Использование unsigned в C с отрицательными временными результатами
Из того, что я понимаю, в C unsigned
тип ведет себя как арифметика (при условии unsigned
имеет 32-разрядную длину) в целочисленном кольце по модулю 4294967296.
Таким образом, из базовой теории колец следует, что если у нас есть целочисленное (теперь я имею в виду ℤ!) Вычисление, включающее *
+
-
, тогда независимо от того, какие временные переполнения и переполнения происходят временно, конечный результат будет правильным, если конечный результат находится в диапазоне [0, 2^32]?
Например, в следующем расчете:
#include <stdio.h>
int main(void){
unsigned v = 50000;
unsigned w = 100000;
unsigned x = 300000;
unsigned y = 20000;
unsigned z = 4000000000;
unsigned r = v*w - x*y + z;
printf("v*w - x*y + z = %u \n", r);
return 0;
}
у нас есть временные результаты, которые отличаются от результатов, которые мы получили бы, если бы рассчитали в ℤ, но мы все равно получаем правильный ответ.
Это правильно или что-то может пойти не так?
1 ответ
Согласно C11 6.2.5 Types /9
(мой акцент):
Вычисления с участием беззнаковых операндов никогда не могут переполниться, потому что результат, который не может быть представлен результирующим целочисленным типом без знака, уменьшается по модулю на число, которое на единицу больше наибольшего значения, которое может быть представлено результирующим типом.
Другими словами, при условии, что типы являются беззнаковыми, и значение модуля, которое вы хотите использовать, UINT_MAX + 1
и тип является правильной шириной (все это верно в данном конкретном случае), это будет работать как положено.
Однако вы должны правильно указать константы как беззнаковые, чтобы гарантировать отсутствие проблем с переполнением самой константы (в этом случае C будет использовать более широкий тип со знаком, так что, если вы можете назначить этот более широкий тип unsigned int
, не должно быть никаких проблем - могут быть проблемы, если, например, вы не используете реализацию дополнения до двух, что вряд ли так).
Вы должны также использовать тип, гарантированно достаточно большой, так как unsigned int
на некоторых платформах может иметь ширину менее 32 бит.
Другими словами, ваши заявления должны быть в форме:
unsigned long z = 4000000000U;
Это, конечно, означает, что вам может потребоваться выполнить свои собственные операции модуля, так как unsigned long
может иметь ширину более 32 бит.
Конечно, если ваша платформа предоставляет их, гораздо предпочтительнее использовать типы фиксированной ширины, uint32_t
в твоем случае. Таким образом, вы получаете тип Goldilocks (ни слишком маленький, ни слишком большой, и, конечно, два дополнения)), и вы можете позволить самому C обрабатывать операции по модулю:
uint32_t z = 4000000000U;
Это решение, которое я бы выбрал.