Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 08:20
Оценка: -1
Приветствую, уважаемые коллеги.

Думаю над сабжевым вопросом. Существующие языки программирования разбираются с переполнением по-разному:

С++. Стратегия типа "суем голову в песок" — объявляем переполнение UB и не паримся. Кому надо — пусть руками ифы ставит. Плюсы — скорость, думать над кодом не надо. Минусы — полное отсутствие надежности.

C#. Каждая операция с проверкой. Плюсы — при переполнении поведение вполне определенное, опять же не надо специально думать при написании кода. Минусы — скорость, да и потенциальный вылет эксепшена хоть и не UB, но тоже скорее всего оставит программу в некорректном состоянии.

SPARK. Переполнения запрещаются статически. Плюсы — скорость, полная надежность. Минусы — очень геморройно писать/изменять код и согласовывать все контракты. Только для ракет и АЭС.

Python. Используем arbitrary precision arithmetic. Плюсы — надежно, код писать просто. Минусы — тормозно, тащим с собой огромную кучу кода (на фоне использования виртуальной машины может быть не важно, но если смотреть абстрактно — это большой минус).


Может быть есть иные подходы обработки переполнений? Хочется вариант с полной скоростью и полной надежностью, но не такой сложный в использовании, как контракты из SPARK. Можно немного поступиться удобством. Немного! Примерно так, как поступаются удобством статически типизированные языки, заставляя писать типы (по сравнению с динамическими). Возможно ли такое в мейнстим программировании в принципе?

И это я еще не говорю про числа с плавающей точкой...
Re: Арифметическое переполнение
От: andy1618 Россия  
Дата: 22.02.14 10:50
Оценка: +1
Здравствуйте, AlexRK, Вы писали:

ARK>C#. Каждая операция с проверкой. Плюсы — при переполнении поведение вполне определенное, опять же не надо специально думать при написании кода. Минусы — скорость, да и потенциальный вылет эксепшена хоть и не UB, но тоже скорее всего оставит программу в некорректном состоянии.


В C# поведение можно изменять опциями checked-unchecked:
http://msdn.microsoft.com/en-us//library/a569z7k8.aspx
Довольно удобно и наглядно, кстати.
Re: Арифметическое переполнение
От: koodeer  
Дата: 22.02.14 11:03
Оценка: +1
Здравствуйте, AlexRK, Вы писали:

ARK>C#. Каждая операция с проверкой. Плюсы — при переполнении поведение вполне определенное, опять же не надо специально думать при написании кода. Минусы — скорость, да и потенциальный вылет эксепшена хоть и не UB, но тоже скорее всего оставит программу в некорректном состоянии.


Не совсем так. andy1618 уже написал выше, что только при включенной опции checked будут проверки, при unchecked их нет.

А вообще, реакция на переполнение зависит от архитектуры процессора. В старом добром x86 просто устанавливается бит переполнения в регистре. И программный код должен содержать дополнительные инструкции, чтобы как-то обработать эту ситуацию. Опция checked в дотнете как раз добавляет такой код.

Я начинал с компьютера БК-0010-01, по системе команд он совместим с PDP-11. Так вот там поведение было другое: при арифметическом переполнении не только устанавливался соответствующий бит в регистре, но и происходило аппаратное прерывание: переход на определённый адрес. Если нам не нужно обрабатывать эту ситуацию, то просто помещаем в векторе перехода адрес на команду возврата. Если переполнение нужно обработать, то в векторе перехода должен быть адрес на код обработки.

Таким образом, в разных архитектурах разная цена игнорирования/обработки переполнения.
Re[2]: Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 11:18
Оценка:
Здравствуйте, andy1618, Вы писали:

A>В C# поведение можно изменять опциями checked-unchecked:

A>http://msdn.microsoft.com/en-us//library/a569z7k8.aspx
A>Довольно удобно и наглядно, кстати.

Ну окей, будет либо как в С++, либо с проверками. C# я привел просто в качестве примера подхода к "решению" проблемы переполнения (на самом деле, никакое это не решение).
Re[2]: Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 11:23
Оценка:
Здравствуйте, koodeer, Вы писали:

K>А вообще, реакция на переполнение зависит от архитектуры процессора. В старом добром x86 просто устанавливается бит переполнения в регистре. И программный код должен содержать дополнительные инструкции, чтобы как-то обработать эту ситуацию. Опция checked в дотнете как раз добавляет такой код.


K>Я начинал с компьютера БК-0010-01, по системе команд он совместим с PDP-11. Так вот там поведение было другое: при арифметическом переполнении не только устанавливался соответствующий бит в регистре, но и происходило аппаратное прерывание: переход на определённый адрес. Если нам не нужно обрабатывать эту ситуацию, то просто помещаем в векторе перехода адрес на команду возврата. Если переполнение нужно обработать, то в векторе перехода должен быть адрес на код обработки.


Спасибо, понятно. Но это все же не то, чего мне хочется. Хочется доказанного отсутствия переполнения (как в SPARK), но не такой дорогой ценой — чтобы получившееся решение было пригодно для массового использования. Если переполнение в аппаратуре произошло, то это значит, что _уже_ что-то пошло не так — программа написана некорректно.
Re: Арифметическое переполнение
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 22.02.14 11:32
Оценка: 3 (1) +2
Здравствуйте, AlexRK, Вы писали:


ARK>Может быть есть иные подходы обработки переполнений? Хочется вариант с полной скоростью и полной надежностью, но не такой сложный в использовании, как контракты из SPARK.


Гипотетический вариант: брать подход С++, но использовать специальный анализатор кода, который сделает range inference. В некоторых местах добавить в исходники пометки для анализатора. Будет как типы, но менее интрузивно.
Re[2]: Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 11:37
Оценка:
Здравствуйте, D. Mon, Вы писали:

DM>Гипотетический вариант: брать подход С++, но использовать специальный анализатор кода, который сделает range inference. В некоторых местах добавить в исходники пометки для анализатора. Будет как типы, но менее интрузивно.


Спасибо. Да, это приходило в голову. Но при таком раскладе мы неизбежно вытаскиваем в контракты метода сильную зависимость от его реализации. Поменяли реализацию в библиотеке — и все клиенты сломались. Тут нужен некий промежуточный вариант, причем простой, но вот какой? Зависимость от имплементации, безусловно, должна быть, но в каких-то довольно широких рамках, которые соблюдают и создатель метода, и его клиенты.
Re[3]: Арифметическое переполнение
От: koodeer  
Дата: 22.02.14 11:42
Оценка: +1
Здравствуйте, AlexRK, Вы писали:

ARK> Но это все же не то, чего мне хочется. Хочется доказанного отсутствия переполнения (как в SPARK), но не такой дорогой ценой — чтобы получившееся решение было пригодно для массового использования. Если переполнение в аппаратуре произошло, то это значит, что _уже_ что-то пошло не так — программа написана некорректно.


Ага, я понял. У меня на самом деле тоже такая хотелка очень давно. После знакомства с ассемблером, я всегда хотел такого же подхода к получению результата, как в нём. Например, если перемножаем два байта, то результат всегда получаем в двух байтах.
Полагаю, этот же подход можно перенести в языки высокого уровня: по умолчанию результат арифметических операций, которые могут привести к переполнению, должен возвращаться в виде значения большего разряда. То есть складываем два числа int32, результат получаем в int64.
При этом придётся убрать из языка неявные приведения типа в арифметических операциях (вариант: сделать включение/отключение неявных преобразований опциональным, как checked в C#).
Re: Арифметическое переполнение
От: LaptevVV Россия  
Дата: 22.02.14 11:46
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Приветствую, уважаемые коллеги.

ARK>Думаю над сабжевым вопросом. Существующие языки программирования разбираются с переполнением по-разному:

ARK>И это я еще не говорю про числа с плавающей точкой...

А вот надо бы. Ибо для целых и для дробных, как говорится, две БОЛЬШИЕ разницы.
Если речь идет об Интел, то там в регистре управления сопроцессора можно установить прерывание по переполнению.
Для целых я такого не припомню, хотя флаг переполнения в регистре флагов, естественно, есть.
Его можно проверить и генерировать исключение.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[4]: Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 11:47
Оценка:
Здравствуйте, koodeer, Вы писали:

K>Ага, я понял. У меня на самом деле тоже такая хотелка очень давно. После знакомства с ассемблером, я всегда хотел такого же подхода к получению результата, как в нём. Например, если перемножаем два байта, то результат всегда получаем в двух байтах.

K>Полагаю, этот же подход можно перенести в языки высокого уровня: по умолчанию результат арифметических операций, которые могут привести к переполнению, должен возвращаться в виде значения большего разряда. То есть складываем два числа int32, результат получаем в int64.
K>При этом придётся убрать из языка неявные приведения типа в арифметических операциях (вариант: сделать включение/отключение неявных преобразований опциональным, как checked в C#).

Да, абсолютно верно, речь именно об этом. В принципе при сложении двух Int32 для результата достаточно одного лишнего разряда, чтобы ошибки не возникло (Int33 ). Но возникает масса проблем с циклами, да и просто с вызовом методов — все будут требовать специфические условия для себя. Вот я и интересуюсь, может есть какие-то интересные подходы...
Re: Арифметическое переполнение
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 22.02.14 11:48
Оценка: +1
Здравствуйте, AlexRK, Вы писали:

ARK>Может быть есть иные подходы обработки переполнений?


В Ada еще есть отдельные модульные типы, как вариант решения проблемы.
Re[2]: Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 11:49
Оценка:
Здравствуйте, LaptevVV, Вы писали:

ARK>>И это я еще не говорю про числа с плавающей точкой...

LVV>А вот надо бы. Ибо для целых и для дробных, как говорится, две БОЛЬШИЕ разницы.

Я в курсе. Но это отдельный большой вопрос. Для начала с целыми числами бы разобраться.

LVV>Если речь идет об Интел, то там в регистре управления сопроцессора можно установить прерывание по переполнению.

LVV>Для целых я такого не припомню, хотя флаг переполнения в регистре флагов, естественно, есть.
LVV>Его можно проверить и генерировать исключение.

Это понятно, но исключение мне не нужно. Если произошло исключение (хоть программное, хоть аппаратное), то это уже некорректное поведение программы.
Re[2]: Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 11:51
Оценка:
Здравствуйте, Mystic, Вы писали:

ARK>>Может быть есть иные подходы обработки переполнений?


M>В Ada еще есть отдельные модульные типы, как вариант решения проблемы.


Да, верно, забыл про них. Но это, ИМХО, некорректное поведение в общем случае. То есть неправильное с математической точки зрения решение. Можно и просто отбрасывать лишние разряды — это тоже не решение.
Re[3]: Арифметическое переполнение
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 22.02.14 12:01
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Здравствуйте, Mystic, Вы писали:


ARK>>>Может быть есть иные подходы обработки переполнений?


M>>В Ada еще есть отдельные модульные типы, как вариант решения проблемы.


ARK>Да, верно, забыл про них. Но это, ИМХО, некорректное поведение в общем случае. То есть неправильное с математической точки зрения решение. Можно и просто отбрасывать лишние разряды — это тоже не решение.


С переполнением есть другой аспект — оно может быть полезно с точки зрения алгоритма. Например, при подсчете CRC. Или циклический буфер. В этом случае проще использовать модульные типы.

А так SPARK и последняя версия Ada с контрактами представляет собой, имхо, наилучшее решение. Там где компилятор может доказать, что переполнения не будет, там и дополнительных проверок никаких не будет. А если мы хотим гарантировать отсутствие переполнений в некотором критическом коде, то ставим соответствующий контракт и далее ручками помогаем компилятору построить доказательство этого факта используя диапазоны и т. п.
Re[3]: Арифметическое переполнение
От: LaptevVV Россия  
Дата: 22.02.14 12:04
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Это понятно, но исключение мне не нужно. Если произошло исключение (хоть программное, хоть аппаратное), то это уже некорректное поведение программы.

Как я понял, надо заранее предупреждать переполнение?
Тогда надо проверять диапазон слагаемых, и опять же что-то делать, если значения не удовлетворяют.
Но вообще признак переполнение при сложении — знак результата отличается от истинного.
Переполнение же случается только при сложении слагаемых одинакового знака.
Если вам нужно для любой операции — надо немного поисследовать.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[4]: Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 12:08
Оценка:
Здравствуйте, Mystic, Вы писали:

M>С переполнением есть другой аспект — оно может быть полезно с точки зрения алгоритма. Например, при подсчете CRC. Или циклический буфер. В этом случае проще использовать модульные типы.


Да. Но это частные случаи, которые к тому же легко реализовать на "корректных числах", не модульных.

M>А так SPARK и последняя версия Ada с контрактами представляет собой, имхо, наилучшее решение. Там где компилятор может доказать, что переполнения не будет, там и дополнительных проверок никаких не будет. А если мы хотим гарантировать отсутствие переполнений в некотором критическом коде, то ставим соответствующий контракт и далее ручками помогаем компилятору построить доказательство этого факта используя диапазоны и т. п.


Ну да. Вопрос заключается в том, чтобы сделать код глобально корректным, без рантайм-проверок вообще. И при этом с достаточной степенью свободы для изменения реализации методов. Условно говоря, чтобы контракт метода не изменялся, когда мы внутри метода меняем строку "i := i + 1" на "i := i + 2".
Re[4]: Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 12:11
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>Как я понял, надо заранее предупреждать переполнение?


Именно.

LVV>Тогда надо проверять диапазон слагаемых, и опять же что-то делать, если значения не удовлетворяют.


Что делать, как раз понятно — не компилировать программу. Вопрос — как правильно построить программу, чтобы такого не возникало.
Re[5]: Арифметическое переполнение
От: LaptevVV Россия  
Дата: 22.02.14 12:17
Оценка:
Здравствуйте, AlexRK, Вы писали:

LVV>>Тогда надо проверять диапазон слагаемых, и опять же что-то делать, если значения не удовлетворяют.

ARK>Что делать, как раз понятно — не компилировать программу. Вопрос — как правильно построить программу, чтобы такого не возникало.
Не компилировать не получится. Переполнение — это жеж во время выполнения.
Или вам нужно на этапе компилЯции определять ?!
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[5]: Арифметическое переполнение
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 22.02.14 12:20
Оценка: +1
Здравствуйте, AlexRK, Вы писали:

ARK>Ну да. Вопрос заключается в том, чтобы сделать код глобально корректным, без рантайм-проверок вообще. И при этом с достаточной степенью свободы для изменения реализации методов. Условно говоря, чтобы контракт метода не изменялся, когда мы внутри метода меняем строку "i := i + 1" на "i := i + 2".


Заранее выбирать тип range для i такой, чтобы он вмещал все разумные значения. Например, выделение памяти. Если мы заранее знаем, что не будут выделяться фрагменты более 100M, мы можем указать диапазон с некоторым запасом, скажем 1G, чтобы (A) иметь свободу маневра и (B) исключить переполнение при элементарных операциях.
Re[6]: Арифметическое переполнение
От: AlexRK  
Дата: 22.02.14 12:23
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>Не компилировать не получится. Переполнение — это жеж во время выполнения.

LVV>Или вам нужно на этапе компилЯции определять ?!

ИМЕННО ТАК.

Более того — уже есть системы, позволяющие это (см. первый пост темы — SPARK). Но писать для них программы сильно сложно. Для мейнстрима не годится.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.