Как безопасно static_cast между unsigned int и int?

У меня есть 8-символьный string, представляющий шестнадцатеричное число, и мне нужно преобразовать его в int. Это преобразование должно сохранять битовый шаблон для строк "80000000" и выше, т.е. Эти числа должны быть отрицательными. К сожалению, наивное решение:

int hex_str_to_int(const string hexStr)
{    
    stringstream strm;
    strm << hex << hexStr;
    unsigned int val = 0;
    strm >> val;
    return static_cast<int>(val);
}

не работает для моего компилятора, если val > MAX_INT (возвращаемое значение равно 0). Изменение типа val до int также приводит к 0 для больших чисел. Я попробовал несколько различных решений из различных ответов здесь, на SO и еще не был успешным.

Вот что я знаю:

  • Я использую компилятор HP С++ на OpenVMS (используя, я полагаю, процессор Itanium).
  • sizeof(int) будет по крайней мере 4 на каждой архитектуре, на которой будет работать мой код.
  • Выделение из числa > INT_MAX в int определяется реализацией. На моей машине это обычно приводит к 0, но интересное кастинг от long до int приводит к INT_MAX, когда значение слишком велико.

Это удивительно сложно сделать правильно, или, по крайней мере, это было для меня. Кто-нибудь знает о переносном решении для этого?

Update:

Изменение static_cast до reinterpret_cast приводит к ошибке компилятора. Комментарий заставил меня попробовать C-стиль: return (int)val в коде выше, и он сработал. На этой машине. Будет ли это безопасно на других архитектурах?

Ответ 1

В то время как есть способы сделать это с помощью приемов и преобразований, большинство полагается на поведение undefined, которое имеет хорошо определенные поведения на некоторых машинах/с некоторыми компиляторами. Вместо того, чтобы полагаться на поведение undefined, скопируйте данные:

int signed_val;
std::memcpy (signed_val, val, sizeof(int));
return signed_val;

Ответ 2

Цитирование стандарта С++ 03, §4.7/3 (интегральные преобразования):

Если тип назначения подписан, значение не изменяется, если оно может быть представлено в типе назначения (и ширине битового поля); в противном случае значение определяется реализацией.

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

Ответ 3

Вы можете свести на нет номер двоеточия без знака, взяв дополнение и добавив его. Так что сделайте это для негативов:

if (val < 0x80000000) // positive values need no conversion
  return val;
if (val == 0x80000000) // Complement-and-addition will overflow, so special case this
  return -0x80000000; // aka INT_MIN
else
  return -(int)(~val + 1);

Это предполагает, что ваш ints представлен 32-битным представлением двойного дополнения (или имеет аналогичный диапазон). Он не полагается на поведение undefined, связанное с переполнением целых чисел (обратите внимание, что поведение неподписанного целочисленного переполнения определено корректно), хотя это тоже не должно быть!).

Обратите внимание, что если ваши ints не являются 32-битными, все становится более сложным. Возможно, вам нужно использовать что-то вроде ~(~0U >> 1) вместо 0x80000000. Кроме того, если ваши ints не являются двумя дополнениями, у вас могут возникнуть проблемы с переполнением определенных значений (например, на машине с одним дополнением, -0x80000000 не может быть представлено в 32-разрядном значении целого числа). Тем не менее, машины без двухкомпонентного оборудования сегодня очень редки, поэтому это вряд ли будет проблемой.

Ответ 4

Здесь другое решение, которое сработало для меня:

if (val <= INT_MAX) {
    return static_cast<int>(val);
}
else {
    int ret = static_cast<int>(val & ~INT_MIN);
    return ret | INT_MIN;
}

Если я маскирую высокий бит, я избегаю переполнения при кастинге. Тогда я смогу сделать это безопасно.

Ответ 5

unsigned int u = ~0U;
int s = *reinterpret_cast<int*>(&u); // -1

Сontrariwise:

int s = -1;
unsigned int u = *reinterpret_cast<unsigned int*>(&s); // all ones