Jump to content

Инвариант цикла

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

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

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

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

Неофициальный пример [ править ]

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

int max(int n, const int a[]) {
    int m = a[0];
    // m equals the maximum value in a[0...0]
    int i = 1;
    while (i != n) {
        // m equals the maximum value in a[0...i-1]
        if (m < a[i])
            m = a[i];
        // m equals the maximum value in a[0...i]
        ++i;
        // m equals the maximum value in a[0...i-1]
    }
    // m equals the maximum value in a[0...i-1], and i==n
    return m;
}

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

Логика Флойда-Хора [ править ]

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

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

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

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

С некоторыми изменениями в используемых обозначениях и предпосылкой остановки цикла это правило также известно как теорема об инвариантных отношениях . [4] [5] В одном из учебников 1970-х годов это представлено так, чтобы оно было доступно студентам-программистам: [4]

Пусть обозначения P { seq } Q имею в виду, что если P истинно перед последовательностью утверждений seq беги, тогда Q правда после этого. Тогда справедлива теорема об инвариантном соотношении, что

P & c { seq } P
подразумевает
P { DO WHILE (c); seq END; } P & ¬c

Пример [ править ]

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

while (x < 10)
    x := x+1;

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

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

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

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

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

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

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

Эйфелева [ править ]

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

    from
        x := 0
    invariant
        x <= 10
    until
        x > 10
    loop
        x := x + 1
    end

Пока [ править ]

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

function max(int[] items) -> (int r)
// Requires at least one element to compute max
requires |items| > 0
// (1) Result is not smaller than any element
ensures all { i in 0..|items| | items[i] <= r }
// (2) Result matches at least one element
ensures some { i in 0..|items| | items[i] == r }:
    //
    nat i = 1
    int m = items[0]
    //
    while i < |items|
    // (1) No item seen so far is larger than m
    where all { k in 0..i | items[k] <= m }
    // (2) One or more items seen so far matches m
    where some { k in 0..i | items[k] == m }:
        if items[i] > m:
            m = items[i]
        i = i + 1
    //
    return m

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

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

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

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

Для 1. комментарий на естественном языке (например, // m equals the maximum value in a[0...i-1] в приведенном выше примере) достаточно.

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

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

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

Отличие от кода, инвариантного к циклу [ править ]

Инвариантный к циклу код состоит из операторов или выражений, которые можно перемещать за пределы тела цикла, не затрагивая семантику программы. Такие преобразования, называемые движением инвариантного к циклу кода , выполняются некоторыми компиляторами для оптимизации программ. Пример кода, инвариантного к циклу (на языке программирования C ):

for (int i=0; i<n; ++i) {
    x = y+z;
    a[i] = 6*i + x*x;
}

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

x = y+z;
t1 = x*x;
for (int i=0; i<n; ++i) {
    a[i] = 6*i + t1;
}

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

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

x1 = y+z;
t1 = x1*x1;
for (int i=0; i<n; ++i) {
    x2 = y+z;
    a[i] = 6*i + t1;
}

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

См. также [ править ]

Ссылки [ править ]

  1. ^ Карло А. Фуриа, Бертран Мейер и Сергей Вельдер. «Инварианты циклов: анализ, классификация и примеры». Обзоры вычислений ACM. том. 46, нет. 3 февраля 2014 г.( [1]
  2. ^ Роберт В. Флойд (1967). «Придание значения программам» (PDF) . В Дж. Т. Шварце (ред.). Материалы симпозиумов по прикладной математике . Математические аспекты информатики. Том. 19. Провиденс, Род-Айленд: Американское математическое общество. стр. 19–32.
  3. ^ Хоар, ЦАР (октябрь 1969 г.). «Аксиоматическая основа компьютерного программирования» (PDF) . Коммуникации АКМ . 12 (10): 576–580. дои : 10.1145/363235.363259 . S2CID   207726175 . Архивировано из оригинала (PDF) 4 марта 2016 г.
  4. ^ Jump up to: Перейти обратно: а б Конвей, Ричард ; Грис, Дэвид (1973). Введение в программирование: структурированный подход с использованием PL/1 и PL/C . Кембридж, Массачусетс: Уинтроп. стр. 198–200.
  5. ^ Хуанг, JC (2009). Обнаружение ошибок программного обеспечения посредством тестирования и анализа . Хобокен, Нью-Джерси: John Wiley & Sons. стр. 156–157.
  6. ^ Мейер, Бертран , Эйфель: Язык, Prentice Hall , 1991, стр. 129–131.
  7. ^ Пирс, Дэвид Дж.; Гроувс, Линдси (2015). «Проектирование проверяющего компилятора: уроки, извлеченные из разработки Whiley». Наука компьютерного программирования . 113 : 191–220. дои : 10.1016/j.scico.2015.09.006 .

Дальнейшее чтение [ править ]

Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 98261c3cb7f50ec6beb4ffcf44f5d4d2__1692388020
URL1:https://arc.ask3.ru/arc/aa/98/d2/98261c3cb7f50ec6beb4ffcf44f5d4d2.html
Заголовок, (Title) документа по адресу, URL1:
Loop invariant - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)