Думаю над сабжевым вопросом. Существующие языки программирования разбираются с переполнением по-разному:
С++. Стратегия типа "суем голову в песок" — объявляем переполнение UB и не паримся. Кому надо — пусть руками ифы ставит. Плюсы — скорость, думать над кодом не надо. Минусы — полное отсутствие надежности.
C#. Каждая операция с проверкой. Плюсы — при переполнении поведение вполне определенное, опять же не надо специально думать при написании кода. Минусы — скорость, да и потенциальный вылет эксепшена хоть и не UB, но тоже скорее всего оставит программу в некорректном состоянии.
SPARK. Переполнения запрещаются статически. Плюсы — скорость, полная надежность. Минусы — очень геморройно писать/изменять код и согласовывать все контракты. Только для ракет и АЭС.
Python. Используем arbitrary precision arithmetic. Плюсы — надежно, код писать просто. Минусы — тормозно, тащим с собой огромную кучу кода (на фоне использования виртуальной машины может быть не важно, но если смотреть абстрактно — это большой минус).
Может быть есть иные подходы обработки переполнений? Хочется вариант с полной скоростью и полной надежностью, но не такой сложный в использовании, как контракты из SPARK. Можно немного поступиться удобством. Немного! Примерно так, как поступаются удобством статически типизированные языки, заставляя писать типы (по сравнению с динамическими). Возможно ли такое в мейнстим программировании в принципе?
И это я еще не говорю про числа с плавающей точкой...
Здравствуйте, AlexRK, Вы писали:
ARK>C#. Каждая операция с проверкой. Плюсы — при переполнении поведение вполне определенное, опять же не надо специально думать при написании кода. Минусы — скорость, да и потенциальный вылет эксепшена хоть и не UB, но тоже скорее всего оставит программу в некорректном состоянии.
Здравствуйте, AlexRK, Вы писали:
ARK>C#. Каждая операция с проверкой. Плюсы — при переполнении поведение вполне определенное, опять же не надо специально думать при написании кода. Минусы — скорость, да и потенциальный вылет эксепшена хоть и не UB, но тоже скорее всего оставит программу в некорректном состоянии.
Не совсем так. andy1618 уже написал выше, что только при включенной опции checked будут проверки, при unchecked их нет.
А вообще, реакция на переполнение зависит от архитектуры процессора. В старом добром x86 просто устанавливается бит переполнения в регистре. И программный код должен содержать дополнительные инструкции, чтобы как-то обработать эту ситуацию. Опция checked в дотнете как раз добавляет такой код.
Я начинал с компьютера БК-0010-01, по системе команд он совместим с PDP-11. Так вот там поведение было другое: при арифметическом переполнении не только устанавливался соответствующий бит в регистре, но и происходило аппаратное прерывание: переход на определённый адрес. Если нам не нужно обрабатывать эту ситуацию, то просто помещаем в векторе перехода адрес на команду возврата. Если переполнение нужно обработать, то в векторе перехода должен быть адрес на код обработки.
Таким образом, в разных архитектурах разная цена игнорирования/обработки переполнения.
Ну окей, будет либо как в С++, либо с проверками. C# я привел просто в качестве примера подхода к "решению" проблемы переполнения (на самом деле, никакое это не решение).
Здравствуйте, koodeer, Вы писали:
K>А вообще, реакция на переполнение зависит от архитектуры процессора. В старом добром x86 просто устанавливается бит переполнения в регистре. И программный код должен содержать дополнительные инструкции, чтобы как-то обработать эту ситуацию. Опция checked в дотнете как раз добавляет такой код.
K>Я начинал с компьютера БК-0010-01, по системе команд он совместим с PDP-11. Так вот там поведение было другое: при арифметическом переполнении не только устанавливался соответствующий бит в регистре, но и происходило аппаратное прерывание: переход на определённый адрес. Если нам не нужно обрабатывать эту ситуацию, то просто помещаем в векторе перехода адрес на команду возврата. Если переполнение нужно обработать, то в векторе перехода должен быть адрес на код обработки.
Спасибо, понятно. Но это все же не то, чего мне хочется. Хочется доказанного отсутствия переполнения (как в SPARK), но не такой дорогой ценой — чтобы получившееся решение было пригодно для массового использования. Если переполнение в аппаратуре произошло, то это значит, что _уже_ что-то пошло не так — программа написана некорректно.
ARK>Может быть есть иные подходы обработки переполнений? Хочется вариант с полной скоростью и полной надежностью, но не такой сложный в использовании, как контракты из SPARK.
Гипотетический вариант: брать подход С++, но использовать специальный анализатор кода, который сделает range inference. В некоторых местах добавить в исходники пометки для анализатора. Будет как типы, но менее интрузивно.
Здравствуйте, D. Mon, Вы писали:
DM>Гипотетический вариант: брать подход С++, но использовать специальный анализатор кода, который сделает range inference. В некоторых местах добавить в исходники пометки для анализатора. Будет как типы, но менее интрузивно.
Спасибо. Да, это приходило в голову. Но при таком раскладе мы неизбежно вытаскиваем в контракты метода сильную зависимость от его реализации. Поменяли реализацию в библиотеке — и все клиенты сломались. Тут нужен некий промежуточный вариант, причем простой, но вот какой? Зависимость от имплементации, безусловно, должна быть, но в каких-то довольно широких рамках, которые соблюдают и создатель метода, и его клиенты.
Здравствуйте, AlexRK, Вы писали:
ARK> Но это все же не то, чего мне хочется. Хочется доказанного отсутствия переполнения (как в SPARK), но не такой дорогой ценой — чтобы получившееся решение было пригодно для массового использования. Если переполнение в аппаратуре произошло, то это значит, что _уже_ что-то пошло не так — программа написана некорректно.
Ага, я понял. У меня на самом деле тоже такая хотелка очень давно. После знакомства с ассемблером, я всегда хотел такого же подхода к получению результата, как в нём. Например, если перемножаем два байта, то результат всегда получаем в двух байтах.
Полагаю, этот же подход можно перенести в языки высокого уровня: по умолчанию результат арифметических операций, которые могут привести к переполнению, должен возвращаться в виде значения большего разряда. То есть складываем два числа int32, результат получаем в int64.
При этом придётся убрать из языка неявные приведения типа в арифметических операциях (вариант: сделать включение/отключение неявных преобразований опциональным, как checked в C#).
Здравствуйте, AlexRK, Вы писали:
ARK>Приветствую, уважаемые коллеги. ARK>Думаю над сабжевым вопросом. Существующие языки программирования разбираются с переполнением по-разному:
ARK>И это я еще не говорю про числа с плавающей точкой...
А вот надо бы. Ибо для целых и для дробных, как говорится, две БОЛЬШИЕ разницы.
Если речь идет об Интел, то там в регистре управления сопроцессора можно установить прерывание по переполнению.
Для целых я такого не припомню, хотя флаг переполнения в регистре флагов, естественно, есть.
Его можно проверить и генерировать исключение.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, koodeer, Вы писали:
K>Ага, я понял. У меня на самом деле тоже такая хотелка очень давно. После знакомства с ассемблером, я всегда хотел такого же подхода к получению результата, как в нём. Например, если перемножаем два байта, то результат всегда получаем в двух байтах. K>Полагаю, этот же подход можно перенести в языки высокого уровня: по умолчанию результат арифметических операций, которые могут привести к переполнению, должен возвращаться в виде значения большего разряда. То есть складываем два числа int32, результат получаем в int64. K>При этом придётся убрать из языка неявные приведения типа в арифметических операциях (вариант: сделать включение/отключение неявных преобразований опциональным, как checked в C#).
Да, абсолютно верно, речь именно об этом. В принципе при сложении двух Int32 для результата достаточно одного лишнего разряда, чтобы ошибки не возникло (Int33 ). Но возникает масса проблем с циклами, да и просто с вызовом методов — все будут требовать специфические условия для себя. Вот я и интересуюсь, может есть какие-то интересные подходы...
Здравствуйте, LaptevVV, Вы писали:
ARK>>И это я еще не говорю про числа с плавающей точкой... LVV>А вот надо бы. Ибо для целых и для дробных, как говорится, две БОЛЬШИЕ разницы.
Я в курсе. Но это отдельный большой вопрос. Для начала с целыми числами бы разобраться.
LVV>Если речь идет об Интел, то там в регистре управления сопроцессора можно установить прерывание по переполнению. LVV>Для целых я такого не припомню, хотя флаг переполнения в регистре флагов, естественно, есть. LVV>Его можно проверить и генерировать исключение.
Это понятно, но исключение мне не нужно. Если произошло исключение (хоть программное, хоть аппаратное), то это уже некорректное поведение программы.
Здравствуйте, Mystic, Вы писали:
ARK>>Может быть есть иные подходы обработки переполнений?
M>В Ada еще есть отдельные модульные типы, как вариант решения проблемы.
Да, верно, забыл про них. Но это, ИМХО, некорректное поведение в общем случае. То есть неправильное с математической точки зрения решение. Можно и просто отбрасывать лишние разряды — это тоже не решение.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, Mystic, Вы писали:
ARK>>>Может быть есть иные подходы обработки переполнений?
M>>В Ada еще есть отдельные модульные типы, как вариант решения проблемы.
ARK>Да, верно, забыл про них. Но это, ИМХО, некорректное поведение в общем случае. То есть неправильное с математической точки зрения решение. Можно и просто отбрасывать лишние разряды — это тоже не решение.
С переполнением есть другой аспект — оно может быть полезно с точки зрения алгоритма. Например, при подсчете CRC. Или циклический буфер. В этом случае проще использовать модульные типы.
А так SPARK и последняя версия Ada с контрактами представляет собой, имхо, наилучшее решение. Там где компилятор может доказать, что переполнения не будет, там и дополнительных проверок никаких не будет. А если мы хотим гарантировать отсутствие переполнений в некотором критическом коде, то ставим соответствующий контракт и далее ручками помогаем компилятору построить доказательство этого факта используя диапазоны и т. п.
Здравствуйте, AlexRK, Вы писали:
ARK>Это понятно, но исключение мне не нужно. Если произошло исключение (хоть программное, хоть аппаратное), то это уже некорректное поведение программы.
Как я понял, надо заранее предупреждать переполнение?
Тогда надо проверять диапазон слагаемых, и опять же что-то делать, если значения не удовлетворяют.
Но вообще признак переполнение при сложении — знак результата отличается от истинного.
Переполнение же случается только при сложении слагаемых одинакового знака.
Если вам нужно для любой операции — надо немного поисследовать.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, Mystic, Вы писали:
M>С переполнением есть другой аспект — оно может быть полезно с точки зрения алгоритма. Например, при подсчете CRC. Или циклический буфер. В этом случае проще использовать модульные типы.
Да. Но это частные случаи, которые к тому же легко реализовать на "корректных числах", не модульных.
M>А так SPARK и последняя версия Ada с контрактами представляет собой, имхо, наилучшее решение. Там где компилятор может доказать, что переполнения не будет, там и дополнительных проверок никаких не будет. А если мы хотим гарантировать отсутствие переполнений в некотором критическом коде, то ставим соответствующий контракт и далее ручками помогаем компилятору построить доказательство этого факта используя диапазоны и т. п.
Ну да. Вопрос заключается в том, чтобы сделать код глобально корректным, без рантайм-проверок вообще. И при этом с достаточной степенью свободы для изменения реализации методов. Условно говоря, чтобы контракт метода не изменялся, когда мы внутри метода меняем строку "i := i + 1" на "i := i + 2".
Здравствуйте, AlexRK, Вы писали:
LVV>>Тогда надо проверять диапазон слагаемых, и опять же что-то делать, если значения не удовлетворяют. ARK>Что делать, как раз понятно — не компилировать программу. Вопрос — как правильно построить программу, чтобы такого не возникало.
Не компилировать не получится. Переполнение — это жеж во время выполнения.
Или вам нужно на этапе компилЯции определять ?!
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, AlexRK, Вы писали:
ARK>Ну да. Вопрос заключается в том, чтобы сделать код глобально корректным, без рантайм-проверок вообще. И при этом с достаточной степенью свободы для изменения реализации методов. Условно говоря, чтобы контракт метода не изменялся, когда мы внутри метода меняем строку "i := i + 1" на "i := i + 2".
Заранее выбирать тип range для i такой, чтобы он вмещал все разумные значения. Например, выделение памяти. Если мы заранее знаем, что не будут выделяться фрагменты более 100M, мы можем указать диапазон с некоторым запасом, скажем 1G, чтобы (A) иметь свободу маневра и (B) исключить переполнение при элементарных операциях.
Здравствуйте, LaptevVV, Вы писали:
LVV>Не компилировать не получится. Переполнение — это жеж во время выполнения. LVV>Или вам нужно на этапе компилЯции определять ?!
ИМЕННО ТАК.
Более того — уже есть системы, позволяющие это (см. первый пост темы — SPARK). Но писать для них программы сильно сложно. Для мейнстрима не годится.