Здравствуйте, Mystic, Вы писали:
M>Заранее выбирать тип range для i такой, чтобы он вмещал все разумные значения. Например, выделение памяти. Если мы заранее знаем, что не будут выделяться фрагменты более 100M, мы можем указать диапазон с некоторым запасом, скажем 1G, чтобы (A) иметь свободу маневра и (B) исключить переполнение при элементарных операциях.
Да, что-то в этом направлении. Но все равно руками контракты писать придется? А если там формула Result:=((A+B)/(B*C-D))^E ? Как программист сможет выбрать "нормальный вариант с запасом" без того, чтобы сесть и выводить математические диапазоны?
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, Mystic, Вы писали:
M>>Заранее выбирать тип range для i такой, чтобы он вмещал все разумные значения. Например, выделение памяти. Если мы заранее знаем, что не будут выделяться фрагменты более 100M, мы можем указать диапазон с некоторым запасом, скажем 1G, чтобы (A) иметь свободу маневра и (B) исключить переполнение при элементарных операциях.
ARK>Да, что-то в этом направлении. Но все равно руками контракты писать придется? А если там формула Result:=((A+B)/(B*C-D))^E ? Как программист сможет выбрать "нормальный вариант с запасом" без того, чтобы сесть и выводить математические диапазоны?
Если брать исходники Ada, то там стараются использовать диапазоны. Соответственно, при объявлении переменных A, B, C, D, E я должен задаться вопросом, а что хранится в этих переменных? Если A это численность студентов в группе, то мне надо писать range 0 .. 99. Если B это голубой цвет, то range 0..255. С одной стороны лишняя работа. С другой стороны, статический анализ поможет раньше выловить разные баги и опечатки.
Здравствуйте, AlexRK, Вы писали:
K>>Полагаю, этот же подход можно перенести в языки высокого уровня: по умолчанию результат арифметических операций, которые могут привести к переполнению, должен возвращаться в виде значения большего разряда. То есть складываем два числа int32, результат получаем в int64. K>>При этом придётся убрать из языка неявные приведения типа в арифметических операциях (вариант: сделать включение/отключение неявных преобразований опциональным, как checked в C#).
ARK>Да, абсолютно верно, речь именно об этом. В принципе при сложении двух Int32 для результата достаточно одного лишнего разряда, чтобы ошибки не возникло (Int33 ). Но возникает масса проблем с циклами, да и просто с вызовом методов — все будут требовать специфические условия для себя. Вот я и интересуюсь, может есть какие-то интересные подходы...
И такой бит есть. Он правда отдельно расположен, обычно в флаговом регистре. На тех нескольких платформах, на которых я поработал, он везде есть. Единственное, что меня всегда удивляло, так это то, что в языке C/C++ нет какой-то инструкции/конструкции, которая бы помогала определять факт переполнения, вроде же "высокоуровневый ассемблер". Наверно на PDP, или на чем там первый C делали, не было такого флага.
При наличии такой конструкции можно было бы либо игнорировать переполнение/заём, если алгоритму безразличен; обработать его, как надо, если это важно; или бросить исключение (C++), если не знаешь, что делать.
Здравствуйте, Mystic, Вы писали:
ARK>>Да, что-то в этом направлении. Но все равно руками контракты писать придется? А если там формула Result:=((A+B)/(B*C-D))^E ? Как программист сможет выбрать "нормальный вариант с запасом" без того, чтобы сесть и выводить математические диапазоны?
M>Если брать исходники Ada, то там стараются использовать диапазоны. Соответственно, при объявлении переменных A, B, C, D, E я должен задаться вопросом, а что хранится в этих переменных? Если A это численность студентов в группе, то мне надо писать range 0 .. 99. Если B это голубой цвет, то range 0..255. С одной стороны лишняя работа. С другой стороны, статический анализ поможет раньше выловить разные баги и опечатки.
Диапазоны входных параметров — это еще полбеды. А вот с возвращаемыми значениями все куда веселее. Для формулы, которую я привел выше, диапазон уже совсем не очевиден. Можно его вывести автоматически, но тогда мы выставляем наружу сильную зависимость от реализации. Чуть изменили формулу — и все.
Да еще и с циклами большой вопрос... С ними проблемы предупреждения переполнения многократно хуже.
Видимо, я хочу невозможного и только искусственный интеллект может помочь.
Здравствуйте, Marty, Вы писали:
M>И такой бит есть. Он правда отдельно расположен, обычно в флаговом регистре. На тех нескольких платформах, на которых я поработал, он везде есть. Единственное, что меня всегда удивляло, так это то, что в языке C/C++ нет какой-то инструкции/конструкции, которая бы помогала определять факт переполнения, вроде же "высокоуровневый ассемблер". Наверно на PDP, или на чем там первый C делали, не было такого флага.
Это только для сложения достаточно лишнего бита. А для умножения двух N-разрядных чисел для результата нужно уже 2N разрядов.
M>При наличии такой конструкции можно было бы либо игнорировать переполнение/заём, если алгоритму безразличен; обработать его, как надо, если это важно; или бросить исключение (C++), если не знаешь, что делать.
Ну, вообще говоря, тема посвящена не обработке переполнения постфактум, а вопросу предупреждения переполнения на этапе компиляции.
Здравствуйте, AlexRK, Вы писали:
ARK>Диапазоны входных параметров — это еще полбеды. А вот с возвращаемыми значениями все куда веселее. Для формулы, которую я привел выше, диапазон уже совсем не очевиден. Можно его вывести автоматически, но тогда мы выставляем наружу сильную зависимость от реализации. Чуть изменили формулу — и все.
ARK>Да еще и с циклами большой вопрос... С ними проблемы предупреждения переполнения многократно хуже.
ARK>Видимо, я хочу невозможного и только искусственный интеллект может помочь.
Возвращаемые значения они же тоже что-то значат. Если функция возвращает количество фигур на шахматной доске, то ее результат вполне можно описать как range 2..32. Конечно, нужны достаточно выразительные средства для контрактов. Особенно если позиция хранится в битовых масках.
А с другой стороны, а это так надо? Все таки критического кода не так уж и много, и если это действительно надо, то надо заморочиться.
Здравствуйте, AlexRK, Вы писали:
M>>При наличии такой конструкции можно было бы либо игнорировать переполнение/заём, если алгоритму безразличен; обработать его, как надо, если это важно; или бросить исключение (C++), если не знаешь, что делать.
ARK>Ну, вообще говоря, тема посвящена не обработке переполнения постфактум, а вопросу предупреждения переполнения на этапе компиляции.
По поводу того, что тема посвящена исключительно отлову на этапе компиляции — это как-то не очевидно из исходного сообщения.
Интересно было бы почитать, как там в SPARK устроено, но в любом случае имхо придется в рантайме вопрос решать — можно конечно требовать от любой арифметической операции возвращать тип, который гарантированно вмещает любой ее результат, но все равно в рантайме предется усекать разрядность и проверять, не обрезали ли лишнее, иначе размеры типов для программы более сложной, чем два-три сложения, перестанут вмещаться в память, а о циклах с переменным числом итераций вообще можно забыть.
Здравствуйте, Mystic, Вы писали:
M>Возвращаемые значения они же тоже что-то значат. Если функция возвращает количество фигур на шахматной доске, то ее результат вполне можно описать как range 2..32. Конечно, нужны достаточно выразительные средства для контрактов. Особенно если позиция хранится в битовых масках.
Да, действительно. Хотел сейчас привести спорные примеры из дотнета, но что-то с налету не нашел. Width/Height из контролов — диапазоны определяются размерами экрана. Controls.Count, TabIndex — максимальное количество контролов на форме. Правда, сейчас этим не заморачиваются, везде просто int.
А вот для абстрактных функций типа "сумма всех элементов массива целых чисел" — тут уже ни входные параметры, ни выходные ничего не значат. Но таких функций, думаю, не очень много.
M>А с другой стороны, а это так надо? Все таки критического кода не так уж и много, и если это действительно надо, то надо заморочиться.
Надо ли — философский вопрос. В принципе, много чего не надо. Вон на Ц пишут операционные системы, а в нем ничего нет.
Здравствуйте, Marty, Вы писали:
M>По поводу того, что тема посвящена исключительно отлову на этапе компиляции — это как-то не очевидно из исходного сообщения.
Согласен, первый пост не вполне очевиден в этом плане.
M>Интересно было бы почитать, как там в SPARK устроено, но в любом случае имхо придется в рантайме вопрос решать — можно конечно требовать от любой арифметической операции возвращать тип, который гарантированно вмещает любой ее результат, но все равно в рантайме предется усекать разрядность и проверять, не обрезали ли лишнее, иначе размеры типов для программы более сложной, чем два-три сложения, перестанут вмещаться в память, а о циклах с переменным числом итераций вообще можно забыть.
M>Или я что-то не понимаю?
Да нет, фишка как раз в том, что в рантайме ничего не решается. Отсутствие ошибок гарантируется на этапе компиляции. Но, конечно, все функции в качестве входных и выходных параметров используют не простые типы вроде int, а ограниченные диапазоны (0..10, -1000..10000 и т.п.). Таким образом, например, если мы складываем не больше 10 чисел, каждое из которых не больше 100 и не меньше 0, то результат гарантировано будет лежать в диапазоне 0..1000.
Здравствуйте, AlexRK, Вы писали:
ARK>Может быть есть иные подходы обработки переполнений?
Для целых чисел — не допускать их вообще. Используйте арифметику с соответствующей длиной операндов.
>Хочется вариант с полной скоростью и полной надежностью
Здравствуйте, Pavel Dvorkin, Вы писали:
ARK>>Может быть есть иные подходы обработки переполнений? PD>Для целых чисел — не допускать их вообще. Используйте арифметику с соответствующей длиной операндов.
А можно немного подробнее? Что вы имеете в виду под "арифметикой с соответствующей длиной операндов"?
Здравствуйте, AlexRK, Вы писали:
ARK>Думаю над сабжевым вопросом. Существующие языки программирования разбираются с переполнением по-разному:
Я в своем разрабатываемом языке программирования принял стратегию С/С++ с возможностью использовать альтернативные режимы явно:
Из C#: checked/unchecked
И режим насыщения sated/unsated (т.е. для байта например sated { 255+1 } == 255 )
разумеется, в такие блоки можно заключать фрагменты кода любого размера, хоть целые модули.
Здравствуйте, NeoCode, Вы писали:
NC>Я в своем разрабатываемом языке программирования принял стратегию С/С++ с возможностью использовать альтернативные режимы явно:
Покажете язык-то? Я интересуюсь языками программирования.
NC>Из C#: checked/unchecked NC>И режим насыщения sated/unsated (т.е. для байта например sated { 255+1 } == 255 ) NC>разумеется, в такие блоки можно заключать фрагменты кода любого размера, хоть целые модули.
Понятно. В принципе, выбранные решения просты в реализации.
А я тяготею к математической корректности в условиях ограничений реальной аппаратуры.
Здравствуйте, AlexRK, Вы писали:
ARK>А можно немного подробнее? Что вы имеете в виду под "арифметикой с соответствующей длиной операндов"?
Если ожидается, что значения не выйдут за пределы (беру знаковый случай) -2^15..2^15-1 — хватит и short, хотя, возможно, стоит все же использовать int, так как short для 32/64-битного процессора не родной
Если в этом нет уверенности, но ожидается, что значения не выйдут за пределы -2^31..2^31 -1 — хватит int
Если и это под вопросом, но ожидается, что значения не выйдут за пределы -2^63..2^63 -1 — брать long
Если же целые числа могут выйти за пределы -2^63..2^63 -1 — задать себе вопрос, что это за такие целые (точные!) числа и почему они должны быть целыми.
А для вещественных чисел в случае Windows есть __try — _except. Не знаю насчет платформонезависимого варианта.
Здравствуйте, minorlogic, Вы писали:
M>еще есть вариант не выполнять все операции по месту а только накапливать буффер операций, и выводить результат когда он нужен по факту.
Если я правильно понимаю, это уже некая надстройка, подход скорее для высокоуровневых пакетов типа матлаба. Вряд ли такое применимо там, где требуется производительность.
M>Также есть комбинированные подходы , хранить дроби неограниченной длниы для простых операций и т.д
Хранение рациональных чисел в виде простых дробей? ИМХО, это из той же оперы. Да и к переполнению вряд ли имеет отношение.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Если ожидается, что значения не выйдут за пределы (беру знаковый случай) -2^15..2^15-1 — хватит и short, хотя, возможно, стоит все же использовать int, так как short для 32/64-битного процессора не родной PD>Если в этом нет уверенности, но ожидается, что значения не выйдут за пределы -2^31..2^31 -1 — хватит int PD>Если и это под вопросом, но ожидается, что значения не выйдут за пределы -2^63..2^63 -1 — брать long
А, вы об этом.
Меня интересует немного другое — корректность программ более сильного вида, а именно формальная. Т.е. отсутствие переполнений, гарантированное компилятором.
Здравствуйте, AlexRK, Вы писали:
ARK>Меня интересует немного другое — корректность программ более сильного вида, а именно формальная. Т.е. отсутствие переполнений, гарантированное компилятором.
А зачем решать проблему, если можно ее легко обойти ?
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>А зачем решать проблему, если можно ее легко обойти ?
Ну, это вопрос философский.
Лично мне нравится такая цель — создание корректного ПО. Гарантированно корректного, а не с некоторой вероятностью. Под корректностью в данном контексте я понимаю отсутствие ошибок времени исполнения.
Если можно достичь этого, то почему бы и нет?
Здравствуйте, AlexRK, Вы писали:
PD>>А зачем решать проблему, если можно ее легко обойти ?
ARK>Ну, это вопрос философский.
Не согласен. Если есть способ избавиться от проблемы, то ИМХО решать ее не надо, а надо именно избавиться. Других проблем хватит, от которых избавиться так просто не удастся.
ARK>Лично мне нравится такая цель — создание корректного ПО. Гарантированно корректного, а не с некоторой вероятностью. Под корректностью в данном контексте я понимаю отсутствие ошибок времени исполнения.
А в том, что я предложил, именно эта цель и достигается. Дело в том, что абстрактная задача избавиться от переполнений вообще не решается, так как размер памяти, отводимой для чисел, ограничен всегда, а значит можно придумать абстрактную ситуацию, когда переполнение все равно возникнет. Однако из этого вовсе не следует, что если в реальной ситуации можно сделать так, чтобы оно не возникало, то реально работающему программисту нужно обязательно поразмышлять над этой абстрактной и в практике не существующей ситуацией.
Мало ли какие абстрактные ситуации могут быть... Например, можно пообсуждать, как обеспечить работу программы при адресном пространстве в 64 Кбайта, каким оно и было во времена первых микропроцессоров. Тогда для того, чтобы программы уложить в эту память, придуманы были различные методики, о которых сейчас никто уже и не помнит, так как проблема самоликвидировалась. Нет ее, и решать незачем.