Я хочу определить функцию, которая принимает unsigned int
аргумент as и возвращает аргументу, int
совпадающему по модулю UINT_MAX + 1.
Первая попытка может выглядеть так:
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
Но, как известно любому юристу по языку, приведение беззнакового к подписанному для значений, превышающих INT_MAX, определяется реализацией.
Я хочу реализовать это так, чтобы (а) он полагался только на поведение, предписанное спецификацией; и (б) он компилируется в нерабочий режим на любой современной машине и оптимизирует компилятор.
Что касается странных машин ... Если нет подписанного int, совпадающего по модулю UINT_MAX + 1 с unsigned int, скажем, я хочу выбросить исключение. Если их несколько (я не уверен, что это возможно), скажем, мне нужен самый большой.
Хорошо, вторая попытка:
int unsigned_to_signed(unsigned n)
{
int int_n = static_cast<int>(n);
if (n == static_cast<unsigned>(int_n))
return int_n;
// else do something long and complicated
}
Меня не особо заботит эффективность, когда я не использую типичную систему с дополнением до двух, поскольку, по моему скромному мнению, это маловероятно. И если мой код станет узким местом во вездесущих знаково-величинных системах 2050 года, я готов поспорить, что кто-то сможет это понять и оптимизировать.
Эта вторая попытка довольно близка к тому, что я хочу. Хотя приведение к int
определено реализацией для некоторых входных данных, приведение обратно кunsigned
гарантировано стандартом для сохранения значения по модулю UINT_MAX + 1. Таким образом, условное выражение действительно проверяет именно то, что я хочу, и оно не будет компилироваться ни в какую систему в любой системе, с которой я, вероятно, столкнусь.
Однако ... я все еще int
выполняю приведение к без предварительной проверки, будет ли он вызывать поведение, определяемое реализацией. По какой-то гипотетической системе в 2050 году он может делать неизвестно что. Допустим, я хочу этого избежать.
Вопрос: Как должна выглядеть моя «третья попытка»?
Подводя итоги, я хочу:
- Преобразование из беззнакового int в подписанное int
- Сохранить значение по модулю UINT_MAX + 1
- Вызвать только стандартное поведение
- Скомпилировать без операции на типичной машине с дополнением до двух с оптимизирующим компилятором
[Обновить]
Позвольте мне привести пример, чтобы показать, почему это нетривиальный вопрос.
Рассмотрим гипотетическую реализацию C ++ со следующими свойствами:
sizeof(int)
равно 4sizeof(unsigned)
равно 4INT_MAX
равно 32767INT_MIN
равно -2 32 + 32768UINT_MAX
равен 2 32 - 1- Арифметика по
int
модулю 2 32 (в диапазонINT_MIN
черезINT_MAX
) std::numeric_limits<int>::is_modulo
правда- Приведение без знака
n
к int сохраняет значение для 0 <= n <= 32767 и дает ноль в противном случае
В этой гипотетической реализации int
каждому unsigned
значению соответствует ровно одно значение (mod UINT_MAX + 1) . Так что мой вопрос будет четко определен.
Я утверждаю, что эта гипотетическая реализация C ++ полностью соответствует спецификациям C ++ 98, C ++ 03 и C ++ 11. Признаюсь, я не запомнил каждое слово из них ... Но думаю, что внимательно прочитал соответствующие разделы. Поэтому, если вы хотите, чтобы я принял ваш ответ, вы должны либо (а) указать спецификацию, которая исключает эту гипотетическую реализацию, либо (б) правильно ее обработать.
Действительно, правильный ответ должен соответствовать каждой гипотетической реализации, разрешенной стандартом. Это то, что по определению означает «вызывать только стандартное поведение».
Кстати, обратите внимание, что std::numeric_limits<int>::is_modulo
здесь совершенно бесполезно по нескольким причинам. Во-первых, это может быть, true
даже если преобразование без знака в знак не работает для больших значений без знака. С другой стороны, это может быть true
даже система с дополнением до единицы или знак-величина, если арифметика просто модулирует весь диапазон целых чисел. И так далее. Если ваш ответ зависит от is_modulo
, это неправильно.
[Обновление 2]
Ответ hvd научил меня кое-чему: моя гипотетическая реализация на C ++ для целых чисел не разрешена современным C. Стандарты C99 и C11 очень специфичны в отношении представления целых чисел со знаком; действительно, они допускают только дополнение до двух, дополнение до единицы и знаковую величину (раздел 6.2.6.2 параграф (2);).
Но C ++ - это не C. Как оказалось, этот факт лежит в основе моего вопроса.
Исходный стандарт C ++ 98 был основан на гораздо более старом C89, в котором говорится (раздел 3.1.2.5):
Для каждого из целочисленных типов со знаком существует соответствующий (но другой) целочисленный тип без знака (обозначенный ключевым словом unsigned), который использует тот же объем памяти (включая информацию о знаках) и имеет одинаковые требования к выравниванию. Диапазон неотрицательных значений целочисленного типа со знаком является поддиапазоном соответствующего целочисленного типа без знака, и представление одного и того же значения в каждом типе одинаково.
C89 ничего не говорит о наличии только одного знакового бита или о разрешении только дополнения до двух / дополнения до единицы / величины знака.
Стандарт C ++ 98 практически дословно принял этот язык (раздел 3.9.1, параграф (3)):
Для каждого из целочисленных типов со знаком существует соответствующий (но разный) целочисленный тип без знака : «
unsigned char
», «unsigned short int
», «unsigned int
» и «unsigned long int
», каждый из которых занимает одинаковый объем памяти и имеет одинаковые требования к выравниванию (3.9 ) как соответствующий знаковый целочисленный тип; то есть каждый знаковый целочисленный тип имеет то же представление объекта, что и соответствующий ему беззнаковый целочисленный тип. Диапазон неотрицательных значений целочисленного типа со знаком является поддиапазоном соответствующего целочисленного типа без знака, и представление значения каждого соответствующего типа со знаком / без знака должно быть одинаковым.
Стандарт C ++ 03 использует практически идентичный язык, как и C ++ 11.
Насколько я могу судить, никакая стандартная спецификация C ++ не ограничивает представление целых чисел со знаком какой-либо спецификацией C. И нет ничего, что требовало бы использования одинарного бита или чего-либо подобного. Все, что он говорит, это то, что неотрицательные целые числа со знаком должны быть поддиапазоном соответствующего беззнакового.
Итак, я снова утверждаю, что INT_MAX = 32767 с INT_MIN = -2 32 +32768 разрешено. Если ваш ответ предполагает иное, он неверен, если вы не цитируете стандарт C ++, доказывающий, что я ошибаюсь.