Я хочу определить функцию, которая принимает аргумент unsigned int
as и возвращает аргумент int
congruent modulo UINT_MAX + 1 в аргумент.
Первая попытка может выглядеть так:
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
Но, как знает любой юрист, кастинг от неподписанных к подписанным для значений, больших INT_MAX, определяется реализацией.
Я хочу реализовать это так, чтобы (а) он полагался только на поведение, заданное спецификацией; и (b) он компилируется в no-op на любой современной машине и оптимизирует компилятор.
Как для причудливых машин... Если нет подписанного int congruent modulo 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 году он мог бы сделать, кто-знает-что. Поэтому позвольте сказать, что я хочу этого избежать.
Вопрос: Как должна выглядеть моя "третья попытка"?
Чтобы повторить, я хочу:
- Передача из unsigned int в подписанный int
- Сохраните значение mod UINT_MAX + 1
- Вызывать только стандартное поведение.
- Компиляция в no-op на типичной машине с двумя дополнительными компонентами с оптимизирующим компилятором
[Обновление]
Позвольте мне привести пример, чтобы показать, почему это не тривиальный вопрос.
Рассмотрим гипотетическую реализацию С++ со следующими свойствами:
-
sizeof(int)
равно 4 -
sizeof(unsigned)
равно 4 -
INT_MAX
равно 32767 -
INT_MIN
равно -2 32 + 32768 -
UINT_MAX
равно 2 32 - 1 - Арифметика на
int
по модулю 2 32 (в диапазонеINT_MIN
черезINT_MAX
) -
std::numeric_limits<int>::is_modulo
истинно - Литье без знака
n
в int сохраняет значение для 0 <= n <= 32767 и возвращает ноль в противном случае
В этой гипотетической реализации для каждого значения unsigned
имеется ровно одно значение int
(mod UINT_MAX + 1). Поэтому мой вопрос будет четко определен.
Я утверждаю, что эта гипотетическая реализация на С++ полностью соответствует спецификациям С++ 98, С++ 03 и С++ 11. Я признаю, что я не запомнил каждое слово из всех... Но я считаю, что внимательно прочитал соответствующие разделы. Поэтому, если вы хотите, чтобы я принял ваш ответ, вы либо должны (a) указать спецификацию, которая исключает эту гипотетическую реализацию, либо (b) обрабатывать ее правильно.
Действительно, правильный ответ должен обрабатывать каждую гипотетическую реализацию, разрешенную стандартом. Это то, что "вызывает только стандартное поведение" означает, по определению.
Кстати, обратите внимание, что std::numeric_limits<int>::is_modulo
здесь совершенно бесполезен по нескольким причинам. Во-первых, это может быть true
, даже если неподписанные приведения не работают для больших значений без знака. Для другого это может быть true
даже в системах с одним дополнением или знаковой величиной, если арифметика просто по модулю всего целочисленного диапазона. И так далее. Если ваш ответ зависит от is_modulo
, это неправильно.
[Обновить 2]
hvd answer научил меня чему-то: моя гипотетическая реализация на С++ для целых чисел не допускается современными C. Стандарты C99 и C11 очень специфичны в отношении представления целых чисел со знаком; действительно, они допускают только двойное дополнение, одно-дополнение и знаковое значение (раздел 6.2.6.2, пункт (2);).
Но С++ не является C. Как выясняется, этот факт лежит в самом сердце моего вопроса.
Оригинальный стандарт С++ 98 был основан на гораздо более старом C89, который гласит (раздел 3.1.2.5):
Для каждого из знаковых целых типов существует соответствующая (но другой) беззнаковый целочисленный тип (обозначается ключевым словом без знака), который использует тот же объем хранения (включая знак информация) и имеет те же требования к выравниванию. Диапазон неотрицательные значения знакового целочисленного типа являются поддиапазонами соответствующий беззнаковый целочисленный тип, а также представление одинаковое значение в каждом типе одинаково.
C89 ничего не говорит о том, что имеет только один битовый знак или разрешает только двоично-дополняющую/единичную/значную величину.
Стандарт С++ 98 принял этот язык почти дословно (раздел 3.9.1 (3)):
Для каждого из знаковых целых типов существует соответствующая (но различный) беззнаковый целочисленный тип: "
unsigned char
", "unsigned short int
", "unsigned int
" и "unsigned long int
", каждый из который занимает один и тот же объем хранения и имеет такое же выравнивание требования (3.9) в качестве соответствующего знакового целочисленного типа; что каждый знак целочисленного типа имеет то же объектное представление, что и его соответствующий целочисленный тип без знака. Диапазон неотрицательных значения знакового целочисленного типа являются поддиапазонами соответствующих беззнаковый целочисленный тип и представление значений каждого соответствующий тип подписанного/неподписанного типа должен быть одинаковым.
В стандарте С++ 03 используется практически идентичный язык, как и С++ 11.
Никакая стандартная спецификация С++ не ограничивает свои подписанные целочисленные представления ни на какой C spec, насколько я могу судить. И нет ничего, что бы означало единичный знак или что-то в этом роде. Все это говорит о том, что неотрицательные целые числа со знаком должны быть поддиапазоном соответствующего без знака.
Итак, снова утверждаю, что INT_MAX = 32767 с INT_MIN = -2 32 +32768 разрешено. Если ваш ответ предполагает иное, это неверно, если вы не указали стандарт С++, доказывающий, что я ошибаюсь.