Целочисленная ошибка приведения?

Рассмотрим следующий код:

#include <iostream>
using namespace std;

int main() {
// the following is expected to not print 4000000000
// because the result of an expression with two `int`
// returns another `int` and the actual result
// doesn't fit into an `int`
cout << 2 * 2000000000 << endl; // prints -294967296

// as such the following produces the correct result
cout << 2 * 2000000000U << endl; // prints 4000000000
}

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

#include <iostream>
using namespace std;

int main() {
// unexpectedly this does print the correct result
cout << (unsigned int)(2 * 2000000000) << endl; // prints 4000000000

// this produces the same wrong result as the original statement
cout << (long long)(2 * 2000000000) << endl; // prints -294967296
}

Я ожидал, что оба из следующих утверждений не дадут правильного результата, почему один из них преуспел, а другой нет?

2

Решение

Слишком много путаницы происходит у людей, пытающихся ответить на этот вопрос.

Давайте рассмотрим:

2 * 2000000000

Это int умножается на int, § 5/4 говорит нам:

Если во время вычисления выражения результат не определен математически или не находится в диапазоне представимых значений для его типа, поведение не определено.

Этот результат определен математически, но находится ли он в диапазоне представимых значений для int?

Это зависит от. На многих распространенных архитектурах int имеет 32 бита для представления значений, что дает ему максимальное значение 2 147 483 647. Поскольку математический результат этого равен 4 000 000 000, такая архитектура не сможет представлять значение, и поведение не определено. (Это в значительной степени убивает вопрос, потому что теперь поведение всей программы не определено.)

Но это зависит только от платформы. Если int вместо этого был шириной 64 бита (примечание: long long гарантированно имеет как минимум 64 бита для представления значений), результат будет соответствовать просто отлично.

Давайте просто исправим проблему и перейдем прямо к этому:

int x = -294967296; // -294,967,296

И давайте далее скажем, что это вписывается в диапазон int (который для 32 бит int оно делает).

Теперь давайте приведем это к unsigned int:

unsigned int y = static_cast<unsigned int>(x);

Какова стоимость y? Это не имеет ничего общего с битовым представлением x.

Не существует «приведения битов», когда компилятор просто обрабатывает биты как количество без знака. Конверсии работают с ценности. значение из signed int преобразован в unsigned int определено в §4.7 / 2:

Если тип назначения является беззнаковым, полученное значение является наименьшим целым числом без знака, соответствующим исходному целому числу (по модулю 2N где n — количество битов, используемых для представления типа без знака). [Примечание: в представлении дополнения до двух это преобразование является концептуальным, и в битовой комбинации нет изменений (если нет усечения). —Конечная записка]

Для нас на нашем 32-битном (unsigned) int система, это означает 4000000000. Это работает независимо от битов: комплимент двоим, комплимент одному, магический комплимент и т. д. Это не имеет значения.

причина вы видите значение, которое вы хотели в первой части (игнорируя UB), это то, что на компьютере с комплиментами двоих различие между знаковыми и беззнаковыми целыми числами действительно заключается в различном просмотре битов. Итак, когда вы умножили эти два intвы действительно умножали два целых числа без знака, игнорировали переполнение и рассматривали результат как целое число со знаком. Затем актерский состав снова меняет ваш взгляд.

Но кастинг работает независимо от битов!

4

Другие решения

В int значение 4,000,000,000 написано как 1110 1110 0110 1011 0010 1000 0000 0000

В неподписанном int значение 4,000,000,000 написано как 1110 1110 0110 1011 0010 1000 0000 0000

Глядя на эти два, вы можете видеть, что они одинаковы.

Разница заключается в том, как биты читаются в int а также unsigned int, в обычном int самый важный бит используется, чтобы сказать, является ли число отрицательным или нет.

3

В C ++ тип выражения не зависит от среды кода (обычно).

Поэтому подвыражение 2 * 2000000000 имеет одинаковый тип и значение в одной и той же системе, независимо от контекста содержащего выражения, оно int (так как оба операнда оператора * intс). И это будет 4000000000, но в вашей архитектуре оно изменилось и изменилось на -294967296 из-за переполнения.

Приведение к long long не будет менять значение, потому что long long может представлять -294967296 просто отлично.

На самом деле это гораздо интереснее, что cout << (unsigned int)(2 * 2000000000) << endl; работает. Как unsinged int не может содержать -294967296, переполнение происходит снова. -294967296 и 4000000000 являются конгруэнтными по модулю 2 ^ 32, так что это будет новое значение. (Обновлено с лучшего ответа GManNickG).

Чтобы проиллюстрировать более глубокую проблему, вы можете попробовать

cout << (unsigned int)(2 * 2000000000 / 2) << endl;

Деление будет выполнено -294967296, а двоичное представление -147483648 будет преобразовано в беззнаковое, которое равно 4147483648.

1

В третьем (странном) случае работающая программа делает это:

2 * 2000000000       = binary number (11101110011010110010100000000000)
print it as unsigned = 4000000000
(interprets the first bit (1) as part of the unsigned number)

Четвертый случай:

2 * 2000000000       = binary number (11101110011010110010100000000000, same as above)
print it as signed   = -294967296
(interprets the first bit (1) as negative number)

Важно понять, что выражение 2 * 2000000000 приводит к последовательности байтов, а затем интерпретируется как говорит операция приведения.

0

Обратите внимание, что целочисленное переполнение со знаком является неопределенным поведением. В заключение, все может случиться. В том числе и невинно правильные результаты.


Оба целочисленных литерала 2 а также 2000000000 шириной 32 бита. Результат будет переполнен, как говорит ваш компилятор:

warning: integer overflow in expression [-Woverflow]

Результатом умножения остается 32-разрядное целое число со знаком. И, в этом случае, результат переполнения, к счастью, является правильным, если рассматривать его как 32-разрядное целое число без знака. Вы можете наблюдать это при приведении битового шаблона к 32-битному unsigned int,

Однако если вы приведете значение к целочисленному типу большей ширины (например, 64 бита), начальные байты будут дополнены ff (расширение знака) и, таким образом, дает ложные результаты.

#include <iostream>

int main() {
long long x = 2 * 2000000000;     // 8 byte width
unsigned int y = 2 * 2000000000;  // 4 byte width
unsigned long z = 2 * 2000000000; // 8 byte width
std::cout << std::hex << x << " " << std::dec << x << std::endl;
// output is: ffffffffee6b2800 -294967296
std::cout << std::hex << y << " " << std::dec << y << std::endl;
// output is: ee6b2800 4000000000
std::cout << std::hex << z << " " << std::dec << z << std::endl;
// output is: ffffffffee6b2800 18446744073414584320

}
0
По вопросам рекламы [email protected]