Несоответствующие результаты при вычислении одного и того же целого числа с разными выражениями

#c #integer-arithmetic #unsigned-long-long-int

#c #целое число-арифметика #unsigned-long-long-int

Вопрос:

Арифметический результат для одного и того же выражения приводит к разным результатам в зависимости от того, определяю ли я целое число в одной строке или использую несколько шагов:

 int main() {

    unsigned long long veryBigIntOneLine = ((255*256 255)*256 255)*256 255;

    unsigned long long veryBigInt = 255;
    veryBigInt *= 256;
    veryBigInt  = 255;
    veryBigInt *= 256;
    veryBigInt  = 255;
    veryBigInt *= 256;
    veryBigInt  = 255;

    unsigned long long veryBigIntParanthesis = (((((255*256) 255)*256) 255)*256) 255;

    unsigned long long fourthInt = 256;
    fourthInt *= 256;
    fourthInt *= 256;
    fourthInt *= 256;
    --fourthInt;

    cout << "veryBigIntOneLine: " << veryBigIntOneLine << endl;
    cout << "veryBigInt: " << veryBigInt << endl;
    cout << "veryBigIntParanthesis: " << veryBigIntParanthesis << endl;
    cout << "fourthInt: " << fourthInt << endl;

    return 0;

}
  

все они должны описывать одно и то же число, 256 ^ 4-1 (или 2 ^ 32-1), но результат отличается.

 veryBigIntOneLine: 18446744073709551615
veryBigInt: 4294967295
veryBigIntParanthesis: 18446744073709551615
fourthInt: 4294967295
  

4294967295 — ожидаемый ответ (поскольку он дается для всех четырех выражений калькулятором Google).

Также 18446744073709551615, вероятно, не является точным результатом того, что вычисляется, поскольку я получаю предупреждение о переполнении во время компиляции для обоих однострочных выражений (даже когда я пробовал с типом __int128). На самом деле это 2 ^ 64-1, что является максимальным значением для unsigned long long с моим компилятором (veryBigIntOneLine 1 дает 0).

Комментарии:

1. Кроме того, самыми простыми способами получения максимального значения для unsigned long long было бы (-1ULL) или (~0ULL) .

Ответ №1:

Код инициализации ((255*256 255)*256 255)*256 255 страдает от переполнения целого числа со знаком, которое является неопределенным поведением, а также от неявного преобразования signed int в unsigned . В то время как пошаговые вычисления позволяют избежать этих проблем, поскольку правый операнд неявно преобразуется в беззнаковый long long .

Простое использование соответствующих литералов устранит эти проблемы:

 unsigned long long veryBigIntOneLine{ ((255ull*256ull 255ull)*256ull 255ull)*256ull 255ull}; // 4294967295
  

Комментарии:

1. Хорошо, спасибо, unsigned long long veryBigIntOneLine = (unsigned long long)((255*256 255)*256 255)*256 255; работает нормально.

2. И большинство компиляторов предупредят об этом, если будет предложено. Хотя добавление constexpr изменяет это на ошибку, которая всегда будет диагностироваться: coliru.stacked-crooked.com/a/5cc7d4a84abd756a Кроме того, было бы достаточно сделать одну из внутренних констант an unsigned long long , а для остальных — зависеть от неявных поощрений.

3. @JulienDhondt Вы можете просто работать везде с unsigned long long, используя соответствующий буквенный суффикс. Также обратите внимание на использование инициализации списка — это запрещает сужающее преобразование.

4. @Deduplicator Итак, в принципе, это unsigned long long veryBigIntOneLine = ((255ull*256 255)*256 255)*256 255; должно работать?

5. @JulienDhondt Вы также можете определить свои собственные суффиксы. См. Определяемый пользователем литерал

Ответ №2:

Это потому, что вы не используете unsigned long long литералы. Если вы хотите, чтобы литералы соответствовали вашим определениям, вам необходимо использовать:

 255ULL   256ULL * 255ULL   ...
  

Это ULL очень важно, если вы создаете числа, состоящие из 64 бит. В C без суффикса число может быть 64, 32 или даже всего 16 бит (даже байты на некоторых CRAY, где 64 бита. Это также означает, что ваш код работал бы просто find в одной из этих систем CRAY.)

Комментарии:

1. Спасибо, единственным известным мне буквальным спецификатором был L» для строк wchar_t[]. Я не знал, что другие литеральные выражения были напечатаны, обработаны / вычислены и только после преобразования при определении / присвоении.

2. Примечание: int не обязательно 32-разрядный (и существует довольно много архитектур, имеющих 16-разрядный int!).