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

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

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).


person Julien Dhondt    schedule 13.04.2019    source источник
comment
Кроме того, самые простые способы получить максимальное значение для unsigned long long — это (-1ULL) или (~0ULL).   -  person Deduplicator    schedule 13.04.2019


Ответы (2)


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

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

unsigned long long veryBigIntOneLine{ ((255ull*256ull+255ull)*256ull+255ull)*256ull+255ull}; // 4294967295
person user7860670    schedule 13.04.2019
comment
Хорошо, спасибо, unsigned long long veryBigIntOneLine = (unsigned long long)((255*256+255)*256+255)*256+255; работает нормально. - person Julien Dhondt; 13.04.2019
comment
И большинство компиляторов предупредят об этом, если потребуется. Хотя добавление constexpr изменяет это на ошибку, которая всегда будет диагностироваться: a/5cc7d4a84abd756a Кроме того, было бы достаточно сделать одну из внутренних констант unsigned long long, а остальные зависеть от неявного продвижения. - person Deduplicator; 13.04.2019
comment
@JulienDhondt Вы можете просто работать везде с unsigned long long, используя соответствующий литеральный суффикс. Также обратите внимание на использование инициализации списка — она запрещает сужающее преобразование. - person user7860670; 13.04.2019
comment
@Deduplicator Итак, в принципе, этот unsigned long long veryBigIntOneLine = ((255ull*256+255)*256+255)*256+255; должен работать? - person Julien Dhondt; 13.04.2019
comment
@JulienDhondt Это бы сработало, но все равно будет много неявных преобразований 255 в unsigned long long. - person user7860670; 13.04.2019
comment
@VTT Еще раз спасибо, я никогда не осознавал, что существуют буквальные суффиксы и префиксы вне L ... для wchar_t []. - person Julien Dhondt; 13.04.2019
comment
@JulienDhondt Вы также можете определить свои собственные суффиксы. См. пользовательский литерал - person user7860670; 13.04.2019

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

255ULL + 256ULL * 255ULL + ...

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

person Alexis Wilke    schedule 13.04.2019
comment
Спасибо, единственный литеральный спецификатор, который я знал, был L для строк wchar_t[]. Я не знал, что литеральные выражения другого типа печатаются, обрабатываются/вычисляются и только после преобразования при определении/присваивании. - person Julien Dhondt; 13.04.2019
comment
Примечание: int не обязательно 32-битный (и существует довольно много архитектур с 16-битным int!). - person Aconcagua; 13.04.2019