Странности с беззнаковым целым числом, типами данных с плавающей запятой и умножением

Я не очень хорошо разбираюсь в языке C и только что столкнулся с проблемой, которую не понимаю. Код:

int main()
{
    unsigned int a = 100;
    unsigned int b = 200;
    float c = 2;

    int result_i;
    unsigned int result_u;
    float result_f;

    result_i = (a - b)*2;
    result_u = (a - b);
    result_f = (a-b)*c;

    printf("%d\n", result_i);
    printf("%d\n", result_u);
    printf("%f\n", result_f);
    return 0;
}

И вывод:

-200
-100
8589934592.000000
Program ended with exit code: 0

Поскольку (a-b) является отрицательным, а a,b имеют тип unsigned int, (a-b) является тривиальным. И после умножения числа с плавающей запятой результат будет 8589934592.000000. У меня есть два вопроса:

Во-первых, почему результат нетривиален после умножения числа типа int на 2 и присвоения числа типа int?

Во-вторых, почему result_u нетривиален, хотя (a-b) отрицателен, а result_u имеет тип unsigned int?

Я использую Xcode для тестирования этого кода, а компилятором по умолчанию является APPLE LLVM 6.0.

Спасибо!


person facebook-1536745818    schedule 09.07.2015    source источник
comment
Не могли бы вы описать, что вы подразумеваете под нетривиальным?   -  person Sean Bright    schedule 10.07.2015
comment
Всем спасибо. Я думаю, что тривиальное равнозначно бессмысленному, нетривиальное — наоборот.   -  person facebook-1536745818    schedule 10.07.2015
comment
Да, это должно быть %u вместо %d. Но мне было интересно, почему %d может иметь правильный результат? Спасибо.   -  person facebook-1536745818    schedule 10.07.2015
comment
Также вы можете изменить result_f = ((float) a-b)* c; и вы можете сохранить printf(%f\n, result_f);   -  person Mihai8    schedule 10.07.2015
comment
тривиальное не означает бессмысленное, а нетривиальное не противоположно бессмысленному. «нетривиальный» обычно означает «не простой и не очевидный».   -  person pvg    schedule 10.07.2015


Ответы (2)


Ваше предположение, что a - b отрицательное, совершенно неверно.

Так как a и b имеют тип unsigned int, все арифметические операции с этими двумя переменными выполняются в домене типа unsigned int. То же самое относится и к смешанной арифметике «unsigned int с int». Такие операции реализуют арифметику по модулю, при этом модуль равен UINT_MAX + 1.

Это означает, что выражение a - b дает результат типа unsigned int. Это большое положительное значение, равное UINT_MAX + 1 - 100. На типичной платформе с 32-битной int это 4294967296 - 100 = 4294967196.

Выражение (a - b) * 2 также дает результат типа unsigned int. Это также большое положительное значение (UINT_MAX + 1 - 100 умножается на 2 и берется по модулю UINT_MAX + 1). На типичной платформе это 4294967096.

Последнее значение слишком велико для типа int. Это означает, что когда вы принудительно вставляете его в переменную result_i, происходит переполнение целого числа со знаком. Результатом переполнения целого числа со знаком при назначении является определенная реализация. В вашем случае result_i оказалось -200. Это выглядит "правильным", но язык не гарантирует этого. (Хотя это может быть гарантировано вашей реализацией.)

Переменная result_u получает правильный беззнаковый результат - положительное значение UINT_MAX + 1 - 100. Но вы печатаете этот результат, используя спецификатор формата %d в printf вместо правильного %u. Недопустимо печатать значения unsigned int, которые не вписываются в диапазон int, используя спецификатор %d. По этой причине поведение вашего кода не определено. Значение -100, которое вы видите в выводе, является просто проявлением этого неопределенного поведения. Этот вывод формально бессмысленен, хотя и кажется "правильным" на первый взгляд.

Наконец, переменная result_f получает «правильный» результат выражения (a-b)*c, вычисленный без переполнения, так как умножение выполняется в домене float. То, что вы видите, это большое положительное значение, о котором я упоминал выше, умноженное на 2. Однако он, вероятно, округляется до точности типа float, которая определяется реализацией. Точное значение будет 4294967196 * 2 = 8589934392.

Можно возразить, что последнее напечатанное вами значение — единственное, которое правильно отражает свойства беззнаковой арифметики, т. е. оно «естественным образом» получено из фактического результата a - b.

person AnT    schedule 09.07.2015
comment
Большое спасибо! В выражении (a - b) * 2 число 2 автоматически считается беззнаковым целым числом? Это потому, что a, b беззнаковые целые? - person facebook-1536745818; 10.07.2015
comment
@facebook-1536745818; да. - person haccks; 10.07.2015
comment
@facebook-1536745818: Формально 2 — это int. Но когда вы смешиваете подписанные и неподписанные типы одинакового размера, выигрывает неподписанный тип, и оценка выполняется в домене беззнакового типа. Итак, да, 2 интерпретируется как беззнаковое. - person AnT; 10.07.2015

Вы получаете отрицательные числа в printf, потому что вы попросили его напечатать целое число со знаком с %d. Используйте %u, если вы хотите увидеть фактическое значение, которое вы получили. Это также покажет вам, как вы получили вывод для умножения с плавающей запятой.

person pvg    schedule 09.07.2015
comment
Как это покажет, как он оказался с выходом с плавающей запятой? Он печатает число с плавающей запятой, используя %f, что уже правильно. - person Beta Carotin; 10.07.2015
comment
Он покажет правильное значение результата unsigned int, что делает вывод %f printf очевидным. - person pvg; 10.07.2015