Чому визначене поведінка беззнакового цілого числа переповнення, але переповнення підписаного цілого числа не є?


209

Ненаписане ціле число переповнення чітко визначено як стандартами C, так і C ++. Наприклад, стандарт C99 ( §6.2.5/9)

Обчислення, що включають непідписані операнди, ніколи не можуть перевищувати значення, тому що результат, який не може бути представлений результатом непідписаного цілого числа, зменшується за модулем на число, яке на одну величину більше, ніж найбільше значення, яке може бути представлено отриманим типом.

Однак обидва стандарти стверджують, що підписане ціле число переповнення є невизначеною поведінкою. Знову із стандарту C99 ( §3.4.3/1)

Прикладом невизначеної поведінки є поведінка на ціле число, що перевищує надходження

Чи є історична чи (ще краще!) Технічна причина цієї розбіжності?


50
Можливо, тому, що існує більше ніж один спосіб представлення підписаних цілих чисел. Який спосіб не вказаний у стандарті, принаймні, не на C ++.
juanchopanza

Корисне посилання: en.wikipedia.org/wiki/Signed_number_representations
Robᵩ

7
Те, що сказав juanchopanza, має сенс. Як я розумію, оригінальний стандарт C значною мірою кодифікував існуючу практику. Якщо всі впровадження в той час узгоджувались із тим, що слід робити без підпису "переповнення", це є вагомою причиною для стандартизації. Вони не погодилися з тим, що підписаний переповнення повинен робити, щоб це не ввійшло в стандарт.

2
@DavidElliman Ненаписане обгортання додавання також легко виявити ( if (a + b < a)). Переповнення при множенні важко як для підписаних, так і для непідписаних типів.

5
@DavidElliman: Справа не лише в тому, чи можна це виявити, а в тому, який результат. У реалізації знака + значення MAX_INT+1 == -0, хоча в доповненнях двох це будеINT_MIN
David Rodríguez - dribeas

Відповіді:


163

Історична причина полягає в тому, що більшість C-реалізацій (компіляторів) просто використовували будь-яку поведінку переповнення, яку найпростіше було реалізувати за допомогою цілого представлення, яке вона використовувала. В реалізаціях C зазвичай використовується одне і те ж представлення, яке використовує процесор, тому поведінка переповнення випливає з цілого представлення, яке використовується процесором.

На практиці лише подання для підписаних значень можуть відрізнятися залежно від реалізації: доповнення одного, два доповнення, знак-величина. Для непідписаного типу немає причини, щоб стандарт дозволяв змінювати, оскільки існує лише одне очевидне бінарне подання (стандарт дозволяє лише бінарне подання).

Відповідні цитати:

C99 6.2.6.1 :

Значення, що зберігаються у ненаписаних бітових полях та об'єктах типу безпідписаних символів, повинні бути представлені за допомогою чистого бінарного позначення.

C99 6.2.6.2:2 :

Якщо біт знака один, значення має бути змінено одним із наступних способів:

- відповідне значення зі знаком біта 0 заперечується ( знак та величина );

- бітовий знак має значення - (2 N ) ( два доповнення );

- бітовий знак має значення - (2 N - 1) ( доповнення ).


Сьогодні всі процесори використовують два доповнення, але підписане арифметичне переповнення залишається невизначеним, і виробники компіляторів хочуть, щоб він залишався невизначеним, оскільки вони використовують цю невизначеність, щоб допомогти в оптимізації. Перегляньте, наприклад, це повідомлення в блозі Ian Lance Taylor або скаргу Agner Fog та відповіді на його повідомлення про помилку.


6
Важлива примітка, однак, полягає в тому, що в сучасному світі не залишається жодної архітектури, яка використовувала б нічого, крім арифметики, підписаної двома додатками. Те, що мовні стандарти все ще дозволяють впровадити, наприклад, PDP-1, є чистою історичною артефактом.
Енді Росс

9
@AndyRoss, але все ж є системи (компілятори OS +, правда, зі старою історією) з доповненням та новими випусками станом на 2013 р. Приклад: OS 2200.
ouah

3
@Andy Ross Ви б вважали, що "жодна архітектура ... не використовує нічого, крім доповнення 2 ..." сьогодні включає в себе гаму DSP та вбудованих процесорів?
chux

11
@AndyRoss: Хоча архітектури "немає", що використовують щось інше, ніж доповнення 2s (для деякого визначення "ні"), напевно є архітектури DSP, які використовують насичуючу арифметику для підписаних цілих чисел.
Стівен Канон

10
Насичення арифметики за підписом, безумовно, відповідає стандарту. Звичайно, інструкції з обгортання повинні використовуватися для арифметики без підпису, але у компілятора завжди є інформація про те, чи виконується арифметика без підпису чи підписання, тому він, безумовно, може вибирати інструкції належним чином.
caf

15

Окрім гарної відповіді Паскаля (що я впевнений, що це основна мотивація), також можливо, що деякі процесори спричиняють виняток із підписаним цілим переповненням, що, звичайно, спричинило б проблеми, якщо компілятору довелося «домовитись про іншу поведінку» ( наприклад, використовуйте додаткові інструкції, щоб перевірити потенційне переповнення та обчислити по-різному в цьому випадку).

Варто також зазначити, що "невизначена поведінка" не означає "не працює". Це означає, що в цій ситуації реалізація може робити все, що їй заманеться. Сюди можна віднести "правильну справу", а також "викликати поліцію" або "врізати". Більшість компіляторів, коли це можливо, оберуть "зробити правильно", припускаючи, що це визначити порівняно просто (у цьому випадку це так). Однак якщо у вас є переповнення в обчисленнях, важливо зрозуміти, що насправді призводить до цього, і що компілятор МОЖЕ зробити щось інше, ніж те, що ви очікуєте (і що це може залежати від версії компілятора, налаштувань оптимізації тощо) .


23
Укладачі не хочуть, щоб ви покладалися на те, що вони роблять правильно, і більшість з них покаже вам це, як тільки ви компілюєте int f(int x) { return x+1>x; }з оптимізацією. GCC та ICC, за допомогою стандартних параметрів, оптимізують вищезазначене return 1;.
Паскаль Куок

1
Для прикладу програми, яка дає різні результати при зіткненні із intпереповненням залежно від рівнів оптимізації, див. Ideone.com/cki8nM Я думаю, що це свідчить про те, що ваша відповідь дає погані поради.
Магнус Хофф

Я трохи змінив цю частину.
Матс Петерсон

Якщо C повинен був забезпечити засіб декларування цілого числа "завершення підписаного двоє доповнення", жодна платформа, яка може запустити C, не повинна мати великих проблем, підтримуючи її хоча б помірно ефективно. Додаткових накладних витрат буде достатньо, щоб код не використовував такий тип, коли поведінка при обтіканні не потрібна, але більшість операцій над двома цілими комплементами є ідентичними операціям з непідписаними цілими числами, за винятком порівнянь та промоцій.
supercat

1
Негативні значення повинні існувати та "працювати", щоб компілятор працював правильно. Звичайно, цілком можливо подолати відсутність підписаних значень в процесорі та використовувати неподписані значення як доповнення, так і подвійні доповнення, залежно від того, що робить більшість сенс, заснований на тому, що набір інструкцій. Це, як правило, набагато повільніше робити це, ніж мати апаратну підтримку для нього, але це не відрізняється від процесорів, які не підтримують плаваючу точку в апараті чи подібне - це просто додає багато додаткового коду.
Матс Петерсон

10

Перш за все, зверніть увагу, що C11 3.4.3, як і всі приклади та примітки до ноги, не є нормативним текстом і тому не має відношення до цитування!

Відповідний текст, в якому йдеться про те, що переповнення цілих чисел і плавців є невизначеною поведінкою, це наступне:

C11 6.5 / 5

Якщо під час оцінки виразу виникає виняткова умова (тобто якщо результат не визначений математично чи не знаходиться в діапазоні представлених значень для його типу), поведінка не визначена.

Пояснення стосовно поведінки неподписаних цілих типів конкретно можна знайти тут:

C11 6.2.5 / 9

Діапазон негативних значень підписаного цілого типу є піддіапазоном відповідного непідписаного цілого типу, а представлення одного і того ж значення в кожному типі однакове. Обчислення, що включають непідписані операнди, ніколи не можуть переповнюватись, тому що результат, який не може бути представлений результатом неподписаного цілого числа, зменшується по модулю на число, яке на одне більше, ніж найбільше значення, яке може бути представлено отриманим типом.

Це робить неподписані цілі типи особливим випадком.

Також зауважте, що існує виняток, якщо будь-який тип перетворений у підписаний тип і старе значення більше не може бути представлене. Тоді поведінка визначається лише реалізацією, хоча сигнал може бути підвищений.

C11 6.3.1.3

6.3.1.3 Підписані та непідписані цілі числа

Коли значення з цілим типом перетворюється на інший цілий тип, відмінний від _Bool, якщо значення може бути представлено новим типом, воно не змінюється.

В іншому випадку, якщо новий тип не підписаний, значення перетворюється шляхом багаторазового додавання або віднімання на одне більше, ніж максимальне значення, яке може бути представлене в новому типі, поки значення не буде в діапазоні нового типу.

В іншому випадку новий тип підписаний, і значення не може бути представлено в ньому; або результат визначається реалізацією, або підвищується сигнал, визначений реалізацією.


6

На додаток до інших згаданих питань, непідписане математичне обгортання змушує неподписані цілі типи поводитись як абстрактні алгебраїчні групи (мається на увазі, що, серед іншого, для будь-якої пари значень Xі Y, існуватиме якесь інше значення Z, яке X+Zбуде, якщо належним чином подано , рівний Yі Y-Zбуде, якщо правильно закинути, рівнимX). Якщо непідписані значення були просто типовими місцями зберігання, а не типами проміжних виразів (наприклад, якщо не було безпідписаного еквівалента найбільшого цілого типу, а арифметичні операції на неподписаних типах поводилися так, ніби вони спочатку перетворили їх у більші підписані типи, тоді не було б такої великої потреби у визначеному режимі загортання, але важко робити обчислення у типі, який не має, наприклад, додаткової оберненої добавки.

Це допомагає в ситуаціях, коли поведінка при обробці даних фактично корисна - наприклад, з номерами послідовностей TCP або певними алгоритмами, такими як хеш-обчислення. Це також може допомогти у ситуаціях, коли необхідно виявити переповнення, оскільки виконувати обчислення та перевірити, чи переповнюються вони, часто простіше, ніж заздалегідь перевірити, чи не переповнять вони, особливо якщо обчислення включають найбільший доступний цілий тип.


Я не дуже дотримуюся - чому це допомагає мати добавку, обернену? Я не можу реально придумати будь-яку ситуацію, коли поведінка над переливом насправді корисна ...
sleske

@sleske: Якщо використовувати десятковий для читабельності людина, якщо лічильник енергії зчитує 0003, а попереднє зчитування було 9995, чи означає це, що було використано -9992 одиниці енергії, або було використано 0008 одиниць енергії? Маючи врожайність 0003-9995 0008, це легко обчислити останній результат. Якщо врожайність -9992 зробить це трохи незграбніше. Однак, не маючи змоги це зробити, потрібно було б порівняти 0003 до 9995, зауважте, що це менше, зробіть зворотне віднімання, відніміть результат у 9999 і додайте 1.
supercat

@sleske: Це також дуже корисно як для людей, так і для компіляторів, щоб мати можливість застосовувати асоціативні, розподільні та комутативні закони арифметики для перезапису виразів та спрощення їх; наприклад, якщо вираз a+b-cобчислюється в циклі, але bі cє постійними в межах цього циклу, це може бути корисно , щоб перемістити обчислення (b-c)поза циклом, але робить це зажадає серед інших речей , які (b-c)дають значення , яке, при додаванні до a, буде давати вихід a+b-c, який, у свою чергу, вимагає, щоб cмати добавку, обернену.
supercat

: Дякую за пояснення. Якщо я правильно це розумію, усі ваші приклади припускають, що ви насправді хочете впоратися із переповненням. У більшості випадків, з якими я стикався, переповнення небажане, і ви хочете запобігти цьому, оскільки результат розрахунку із переповненням не корисний. Наприклад, для лічильника енергії ви, мабуть, хочете використовувати такий тип, що переповнення ніколи не відбувається.
sleske

1
... такий, що (a+b)-cдорівнює a+(b-c)тому, чи є арифметичне значення b-cрепрезентабельним у межах типу, підміна буде дійсно незалежно від можливого діапазону значень для (b-c).
supercat

1

Можливо, ще одна причина, чому визначається непідписана арифметика, полягає в тому, що непідписані числа утворюють цілі числа по модулю 2 ^ n, де n - ширина непідписаного числа. Непідписані числа - це просто цілі числа, представлені з використанням двійкових цифр замість десяткових цифр. Виконання стандартних операцій в модульній системі добре зрозуміло.

Цитата ОП посилається на цей факт, але також підкреслює той факт, що існує лише один, однозначний, логічний спосіб представлення непідписаних цілих чисел у двійкових. Навпаки, підписані номери найчастіше представлені за допомогою доповнення двох, але можливі інші варіанти, як описано в стандарті (розділ 6.2.6.2).

Представлення комплексу двох дозволяє певним операціям мати більше сенсу у двійковому форматі. Наприклад, приріст від'ємних чисел такий самий, як і для додатних чисел (очікуйте в умовах переповнення). Деякі операції на рівні машини можуть бути однаковими для підписаних і неподписаних номерів. Однак, інтерпретуючи результат цих операцій, деякі випадки не мають сенсу - позитивний та негативний перелив. Крім того, результати переповнення відрізняються залежно від поданого підписаного подання.


Щоб структура була полем, кожен елемент структури, окрім адитивної ідентичності, повинен мати мультиплікативну зворотну. Структура цілих чисел конгруентного мода N буде полем лише тоді, коли N є одним або простим [поле деградації, коли N == 1]. Чи є щось, що ви відчуваєте, що я пропустив у своїй відповіді?
supercat

Ти правий. Мене збентежили модулі основної потужності. Оригінальна відповідь відредагована.
YTH

Тут зайвим заплутаним є те, що є поле порядку 2 ^ n, воно просто не кільце-ізоморфне цілому числу модулю 2 ^ n.
Кевін Вентулло,

І, 2 ^ 31-1 - прем'єр Мерсена (але 2 ^ 63-1 не є простим). Таким чином, моя первісна ідея була зруйнована. Крім того, цілі розміри були різними в той день. Отже, моя ідея була в кращому випадку ревізіоністською.
YTH

Той факт, що цілі числа без підпису утворюють кільце (а не поле), приймаючи частину низького порядку, також виходить кільце, а виконання операцій над усім значенням, а потім обрізання буде вести себе еквівалентно виконанню операцій лише на нижній частині, було IMHO майже напевно міркування.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.