Инвариант цикла - Википедия - Loop invariant

В Информатика, а инвариант цикла является собственностью программа петля это верно до (и после) каждой итерации. Это логическое утверждение, иногда проверяется в коде утверждение вызов. Знание его инварианта (ов) необходимо для понимания эффекта цикла.

В формальная проверка программы, особенно Подход Флойда-Хора, инварианты цикла выражаются формальными логика предикатов и используется для доказательства свойств петель и, как следствие, алгоритмы которые используют циклы (обычно правильность Инварианты цикла будут истинными при входе в цикл и после каждой итерации, так что при выходе из цикла могут быть гарантированы как инварианты цикла, так и условие завершения цикла.

С точки зрения методологии программирования, инвариант цикла можно рассматривать как более абстрактную спецификацию цикла, которая характеризует более глубокую цель цикла, выходящую за рамки деталей этой реализации. Обзорная статья [1] охватывает фундаментальные алгоритмы из многих областей информатики (поиск, сортировка, оптимизация, арифметика и т. д.), характеризуя каждый из них с точки зрения его инварианта.

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

Неформальный пример

Следующее C подпрограмма Максимум() возвращает максимальное значение в своем массиве аргументов а []при условии его длины п равно не менее 1. Комментарии представлены в строках 3, 6, 9, 11 и 13. Каждый комментарий содержит утверждение о значениях одной или нескольких переменных на этом этапе функции. Выделенные утверждения в теле цикла, в начало и конец цикла (строки 6 и 11) точно такие же. Таким образом, они описывают инвариантное свойство цикла. Когда достигается строка 13, этот инвариант все еще сохраняется, и известно, что условие цикла я! = п из строки 5 стало ложным. Оба свойства вместе означают, что м равно максимальному значению в a [0 ... n-1], то есть правильное значение возвращается из строки 14.

 1int Максимум(int п, const int а[]) { 2    int м = а[0]; 3    // m равно максимальному значению в [0 ... 0] 4    int я = 1; 5    пока (я != п) { 6        // m равно максимальному значению в [0 ... i-1] 7        если (м < а[я]) 8            м = а[я]; 9        // m равно максимальному значению в [0 ... i]10        ++я;11        // m равно максимальному значению в [0 ... i-1]12    }13    // m равно максимальному значению в [0 ... i-1], а i == n14    возвращаться м;15}

После защитное программирование парадигма, условие цикла я! = п в строке 5 лучше изменить на я <п, чтобы избежать бесконечного зацикливания на недопустимые отрицательные значения п. Хотя это изменение в коде интуитивно не должно иметь никакого значения, рассуждения, ведущие к его правильности, становятся несколько более сложными, поскольку с тех пор только я> = п известен в строке 13. Чтобы получить это также я <= п это условие должно быть включено в инвариант цикла. Легко заметить, что я <= птоже является инвариантом цикла, так как я <п в строке 6 можно получить из (модифицированного) условия цикла в строке 5, и, следовательно, я <= п занимает строку 11 после я был увеличен в строке 10. Однако, когда инварианты цикла должны быть предоставлены вручную для формальной проверки программы, такие интуитивно слишком очевидные свойства, как я <= п часто упускаются из виду.

Логика Флойда – Хора

В Логика Флойда – Хора,[2][3] то частичная правильность из пока цикл регулируется следующим правилом вывода:

Это означает:

  • Если какое-то свойство сохраняется кодом - точнее, если удерживается после выполнения когда оба и проводился заранее - (верхняя строка) тогда
  • и гарантированно будут ложными и истинными, соответственно, после выполнения всего цикла , при условии было правдой до цикла (нижняя строка).

Другими словами: приведенное выше правило - дедуктивный шаг, в основе которого лежит Хоар тройной . Эта тройка на самом деле связь по состояниям машин. Он выполняется всякий раз, когда начинается с состояния, в котором логическое выражение верно и успешно выполняет некоторый код, называемый , машина попадает в состояние, в котором правда. Если это соотношение может быть доказано, правило позволяет нам сделать вывод, что успешное выполнение программы приведет из состояния, в котором верно для состояния, в котором держит. Логическая формула в этом правиле называется инвариантом цикла.

Пример

В следующем примере показано, как работает это правило. Рассмотрим программу

а (х <10) х: = х + 1;

Тогда можно доказать следующую тройку Хоара:

Условие C из пока петля . Полезный инвариант цикла нужно угадывать; окажется, что является целесообразным. При этих предположениях можно доказать следующую тройку Хоара:

Хотя эту тройку формально можно вывести из правил логики Флойда-Хоара, управляющих присваиванием, она также интуитивно оправдана: вычисление начинается в состоянии, когда верно, что просто означает, что правда. Вычисление добавляет 1 к , что обозначает по-прежнему верно (для целого x).

Согласно этой предпосылке, правило для пока петли позволяет сделать следующий вывод:

Однако постусловие ( меньше или равно 10, но не меньше 10) логически эквивалентный к , что мы и хотели показать.

Недвижимость - еще один инвариант цикла примера, а тривиальное свойство - еще один. Применение приведенного выше правила вывода к предыдущему инварианту дает . Применяя его к инварианту дает , что немного более выразительно.

Поддержка языков программирования

Эйфель

В Эйфель язык программирования обеспечивает встроенную поддержку инвариантов цикла.[4] Инвариант цикла выражается тем же синтаксисом, что и для инвариант класса. В приведенном ниже примере выражение инварианта цикла х <= 10 должно быть истинным после инициализации цикла и после каждого выполнения тела цикла; это проверяется во время выполнения.

    из        Икс := 0    инвариантный        Икс <= 10    до того как        Икс > 10    петля        Икс := Икс + 1    конец

Пока

В Пока язык программирования также обеспечивает первоклассную поддержку инвариантов цикла.[5] Инварианты цикла выражаются с помощью одного или нескольких куда пункты, как показано ниже:

функция Максимум(int[] Предметы) -> (int р)// Требуется хотя бы один элемент для вычисления макс.требует |Предметы| > 0// (1) Результат не меньше любого элементаобеспечивает все { я в 0..|Предметы| | Предметы[я] <= р }// (2) Результат соответствует хотя бы одному элементуобеспечивает немного { я в 0..|Предметы| | Предметы[я] == р }:    //    нац я = 1    int м = Предметы[0]    //    пока я < |Предметы|    // (1) Ни один из видимых на данный момент элементов не превышает m    куда все { k в 0..я | Предметы[k] <= м }    // (2) Один или несколько видимых до сих пор элементов соответствует m    куда немного { k в 0..я | Предметы[k] == м }:        если Предметы[я] > м:            м = Предметы[я]        я = я + 1    //    возвращаться м

В Максимум() функция определяет самый большой элемент в целочисленном массиве. Чтобы это было определено, массив должен содержать хотя бы один элемент. В постусловия из Максимум() требовать, чтобы возвращаемое значение было: (1) не меньше любого элемента; и (2) он соответствует по крайней мере одному элементу. Инвариант цикла определяется индуктивно через два куда предложения, каждое из которых соответствует предложению в постусловии. Принципиальное отличие состоит в том, что каждое предложение инварианта цикла определяет результат как правильный вплоть до текущего элемента. я, в то время как постусловия определяют результат как правильный для всех элементов.

Использование инвариантов цикла

Инвариант цикла может служить одной из следующих целей:

  1. чисто документальный
  2. для проверки в коде с помощью вызова утверждения
  3. подлежит проверке на основе Подход Флойда-Хора

Для 1. комментарий на естественном языке (например, // m равно максимальному значению в [0 ... i-1] в над пример) достаточно.

Для 2. требуется поддержка языков программирования, таких как C библиотека assert.h, или над -показано инвариантный пункт в Эйфеле. Часто проверка во время выполнения может быть включена (для запусков отладки) и выключена (для производственных запусков) компилятором или параметром времени выполнения.[нужна цитата ]

Для 3. существуют некоторые инструменты для поддержки математических доказательств, обычно основанные на над -показанное правило Флойда – Хоара, что данный код цикла фактически удовлетворяет заданному (набору) инварианту (ам) цикла.

Техника абстрактная интерпретация может использоваться для автоматического определения инварианта цикла данного кода. Однако этот подход ограничен очень простыми инвариантами (такими как 0 <= я && я <= п && я% 2 == 0).

Отличие от кода, инвариантного к циклам

Инвариант цикла (инвариант цикла свойство) следует отличать от инвариантных к циклам код; обратите внимание на «инвариант цикла» (существительное) по сравнению с «инвариантным к циклу» (прилагательное). Код, инвариантный к циклу, состоит из операторов или выражений, которые можно переместить за пределы тела цикла, не затрагивая семантику программы; такие преобразования, называемые движение кода с инвариантным циклом, выполняются некоторыми компиляторами для оптимизировать программ. Пример кода, инвариантного к циклам (в Язык программирования C ) является

за (int я=0; я<п; ++я) {    Икс = у+z;    а[я] = 6*я + Икс*Икс;}

где расчеты х = у + г и х * х можно переместить перед циклом, что приведет к созданию эквивалентной, но более быстрой программы:

Икс = у+z;t1 = Икс*Икс;за (int я=0; я<п; ++я) {    а[я] = 6*я + t1;}

Напротив, например, недвижимость 0 <= я && я <= п является инвариантом цикла как для исходной, так и для оптимизированной программы, но не является частью кода, поэтому не имеет смысла говорить о «перемещении его из цикла».

Код, инвариантный к циклам, может вызывать соответствующее свойство, инвариантное к циклам.[требуется разъяснение ] Для приведенного выше примера самый простой способ увидеть это - рассмотреть программу, в которой инвариантный код цикла вычисляется как до, так и внутри цикла:

x1 = у+z;t1 = x1*x1;за (int я=0; я<п; ++я) {    x2 = у+z;    а[я] = 6*я + t1;}

Свойство этого кода, инвариантное к циклам: (x1 == x2 && t1 == x2 * x2) || я == 0, указывая, что значения, вычисленные перед циклом, согласуются с вычисленными в нем (кроме перед первой итерацией).[нужна цитата ]

Смотрите также

Рекомендации

  1. ^ Карло А. Фурия, [Бертран Мейер] и Сергей Вельдер. «Инварианты цикла: анализ, классификация и примеры». ACM Computing Surveys. т. 46, нет. 3 февраля 2014 г. ([1]
  2. ^ Роберт В. Флойд (1967). «Придание смысла программам» (PDF). В J.T. Шварц (ред.). Материалы симпозиумов по прикладной математике. Математические аспекты информатики. 19. Провиденс, Род-Айленд: Американское математическое общество. С. 19–32.
  3. ^ Хоар, К.А.Р. (Октябрь 1969 г.). «Аксиоматическая основа компьютерного программирования» (PDF). Коммуникации ACM. 12 (10): 576–580. Дои:10.1145/363235.363259. Архивировано из оригинал (PDF) на 2016-03-04.
  4. ^ Мейер, Бертран, Эйфель: язык, Prentice Hall, 1991, стр. 129–131.
  5. ^ Пирс, Дэвид Дж .; Рощи, Линдси (2015). «Проектирование проверяющего компилятора: уроки, извлеченные из опыта разработки». Наука компьютерного программирования. 113: 191–220. Дои:10.1016 / j.scico.2015.09.006.

дальнейшее чтение