Я хочу визначити функцію, яка приймає unsigned int
як аргумент і повертає до аргументу int
конгруентний модуль UINT_MAX + 1.
Перша спроба може виглядати так:
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
Але, як відомо будь-якому мовному юристу, приведення з непідписаного на підписане значення, більші за INT_MAX, визначається реалізацією.
Я хочу реалізувати це таким чином, що (а) воно покладається лише на поведінку, передбачену специфікацією; і (b) він компілюється у відсутність операцій на будь-якій сучасній машині та оптимізуючий компілятор.
Що стосується химерних машин ... Якщо немає підписаного int-конгруентного за модулем UINT_MAX + 1 неподписаного 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
- Зберегти значення mod UINT_MAX + 1
- Закликайте лише до стандартної поведінки
- Складіть компроміси на типовій машині з двома доповненнями з оптимізуючим компілятором
[Оновлення]
Дозвольте навести приклад, щоб показати, чому це не тривіальне питання.
Розглянемо гіпотетичну реалізацію С ++ із такими властивостями:
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
значення, конгруентне (мод UINT_MAX + 1) для кожного unsigned
значення. Тож моє запитання було б чітко визначеним.
Я стверджую, що ця гіпотетична реалізація 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 дозволено. Якщо ваша відповідь передбачає інше, це неправильно, якщо ви не цитуєте стандарт С ++, який підтверджує мені помилку.