McCarthy 91 function

Last updated

The McCarthy 91 function is a recursive function, defined by the computer scientist John McCarthy as a test case for formal verification within computer science.

Contents

The McCarthy 91 function is defined as

The results of evaluating the function are given by M(n) = 91 for all integer arguments n  100, and M(n) = n  10 for n> 100. Indeed, the result of M(101) is also 91 (101 - 10 = 91). All results of M(n) after n = 101 are continually increasing by 1, e.g. M(102) = 92, M(103) = 93.

History

The 91 function was introduced in papers published by Zohar Manna, Amir Pnueli and John McCarthy in 1970. These papers represented early developments towards the application of formal methods to program verification. The 91 function was chosen for being nested-recursive (contrasted with single recursion, such as defining by means of ). The example was popularized by Manna's book, Mathematical Theory of Computation (1974). As the field of Formal Methods advanced, this example appeared repeatedly in the research literature. In particular, it is viewed as a "challenge problem" for automated program verification.

It is easier to reason about tail-recursive control flow, this is an equivalent (extensionally equal) definition:

As one of the examples used to demonstrate such reasoning, Manna's book includes a tail-recursive algorithm equivalent to the nested-recursive 91 function. Many of the papers that report an "automated verification" (or termination proof) of the 91 function only handle the tail-recursive version.

This is an equivalent mutually tail-recursive definition:

A formal derivation of the mutually tail-recursive version from the nested-recursive one was given in a 1980 article by Mitchell Wand, based on the use of continuations.

Examples

Example A:

M(99) = M(M(110)) since 99 ≤ 100       = M(100)    since 110 > 100       = M(M(111)) since 100 ≤ 100       = M(101)    since 111 > 100       = 91        since 101 > 100

Example B:

M(87) = M(M(98))       = M(M(M(109)))       = M(M(99))       = M(M(M(110)))       = M(M(100))       = M(M(M(111)))       = M(M(101))       = M(91)       = M(M(102))       = M(92)       = M(M(103))       = M(93)    .... Pattern continues increasing till M(99), M(100) and M(101), exactly as we saw on the example A)       = M(101)    since 111 > 100       = 91        since 101 > 100

Code

Here is an implementation of the nested-recursive algorithm in Lisp:

(defunmc91(n)(cond((<=n100)(mc91(mc91(+n11))))(t(-n10))))

Here is an implementation of the nested-recursive algorithm in Haskell:

mc91n|n>100=n-10|otherwise=mc91$mc91$n+11

Here is an implementation of the nested-recursive algorithm in OCaml:

letrecmc91n=ifn>100thenn-10elsemc91(mc91(n+11))

Here is an implementation of the tail-recursive algorithm in OCaml:

letmc91n=letrecauxnc=ifc=0thennelseifn>100thenaux(n-10)(c-1)elseaux(n+11)(c+1)inauxn1

Here is an implementation of the nested-recursive algorithm in Python:

defmc91(n:int)->int:"""McCarthy 91 function."""ifn>100:returnn-10else:returnmc91(mc91(n+11))

Here is an implementation of the nested-recursive algorithm in C:

intmc91(intn){if(n>100){returnn-10;}else{returnmc91(mc91(n+11));}}

Here is an implementation of the tail-recursive algorithm in C:

intmc91(intn){returnmc91taux(n,1);}intmc91taux(intn,intc){if(c!=0){if(n>100){returnmc91taux(n-10,c-1);}else{returnmc91taux(n+11,c+1);}}else{returnn;}}

Proof

Here is a proof that the McCarthy 91 function is equivalent to the non-recursive algorithm defined as:

For n> 100, the definitions of and are the same. The equality therefore follows from the definition of .

For n ≤ 100, a strong induction downward from 100 can be used:

For 90 ≤ n ≤ 100,

M(n) = M(M(n + 11)), by definition      = M(n + 11 - 10), since n + 11 > 100      = M(n + 1)

This can be used to show M(n) = M(101) = 91 for 90 ≤ n ≤ 100:

M(90) = M(91), M(n) = M(n + 1) was proven above       = …       = M(101), by definition       = 101 − 10       = 91

M(n) = M(101) = 91 for 90 ≤ n ≤ 100 can be used as the base case of the induction.

For the downward induction step, let n ≤ 89 and assume M(i) = 91 for all n<i ≤ 100, then

M(n) = M(M(n + 11)), by definition      = M(91), by hypothesis, since n < n + 11 ≤ 100      = 91, by the base case.

This proves M(n) = 91 for all n ≤ 100, including negative values.

Knuth's generalization

Donald Knuth generalized the 91 function to include additional parameters. [1] John Cowles developed a formal proof that Knuth's generalized function was total, using the ACL2 theorem prover. [2]

Related Research Articles

<span class="mw-page-title-main">Algorithm</span> Sequence of operations for a task

In mathematics and computer science, an algorithm is a finite sequence of rigorous instructions, typically used to solve a class of specific problems or to perform a computation. Algorithms are used as specifications for performing calculations and data processing. More advanced algorithms can use conditionals to divert the code execution through various routes and deduce valid inferences, achieving automation eventually. Using human characteristics as descriptors of machines in metaphorical ways was already practiced by Alan Turing with terms such as "memory", "search" and "stimulus".

In computability theory, the Ackermann function, named after Wilhelm Ackermann, is one of the simplest and earliest-discovered examples of a total computable function that is not primitive recursive. All primitive recursive functions are total and computable, but the Ackermann function illustrates that not all total computable functions are primitive recursive.

<span class="mw-page-title-main">Binary search algorithm</span> Search algorithm finding the position of a target value within a sorted array

In computer science, binary search, also known as half-interval search, logarithmic search, or binary chop, is a search algorithm that finds the position of a target value within a sorted array. Binary search compares the target value to the middle element of the array. If they are not equal, the half in which the target cannot lie is eliminated and the search continues on the remaining half, again taking the middle element to compare to the target value, and repeating this until the target value is found. If the search ends with the remaining half being empty, the target is not in the array.

In mathematics and computer programming, exponentiating by squaring is a general method for fast computation of large positive integer powers of a number, or more generally of an element of a semigroup, like a polynomial or a square matrix. Some variants are commonly referred to as square-and-multiply algorithms or binary exponentiation. These can be of quite general use, for example in modular arithmetic or powering of matrices. For semigroups for which additive notation is commonly used, like elliptic curves used in cryptography, this method is also referred to as double-and-add.

In computer science, linear search or sequential search is a method for finding an element within a list. It sequentially checks each element of the list until a match is found or the whole list has been searched.

In mathematical logic and computer science, a general recursive function, partial recursive function, or μ-recursive function is a partial function from natural numbers to natural numbers that is "computable" in an intuitive sense – as well as in a formal one. If the function is total, it is also called a total recursive function. In computability theory, it is shown that the μ-recursive functions are precisely the functions that can be computed by Turing machines. The μ-recursive functions are closely related to primitive recursive functions, and their inductive definition (below) builds upon that of the primitive recursive functions. However, not every total recursive function is a primitive recursive function—the most famous example is the Ackermann function.

<span class="mw-page-title-main">Floor and ceiling functions</span> Nearest integers from a number

In mathematics, the floor function (or greatest integer function) is the function that takes as input a real number x, and gives as output the greatest integer less than or equal to x, denoted x or floor(x). Similarly, the ceiling function maps x to the smallest integer greater than or equal to x, denoted x or ceil(x).

Standard ML (SML) is a general-purpose, modular, functional programming language with compile-time type checking and type inference. It is popular for writing compilers, for programming language research, and for developing theorem provers.

<span class="mw-page-title-main">Dynamic programming</span> Problem optimization method

Dynamic programming is both a mathematical optimization method and an algorithmic paradigm. The method was developed by Richard Bellman in the 1950s and has found applications in numerous fields, from aerospace engineering to economics.

In computability theory, a set S of natural numbers is called computably enumerable (c.e.), recursively enumerable (r.e.), semidecidable, partially decidable, listable, provable or Turing-recognizable if:

In information theory, linguistics, and computer science, the Levenshtein distance is a string metric for measuring the difference between two sequences. Informally, the Levenshtein distance between two words is the minimum number of single-character edits required to change one word into the other. It is named after the Soviet mathematician Vladimir Levenshtein, who considered this distance in 1965.

In computer science, program synthesis is the task to construct a program that provably satisfies a given high-level formal specification. In contrast to program verification, the program is to be constructed rather than given; however, both fields make use of formal proof techniques, and both comprise approaches of different degrees of automation. In contrast to automatic programming techniques, specifications in program synthesis are usually non-algorithmic statements in an appropriate logical calculus.

<span class="mw-page-title-main">Sorting network</span> Abstract devices built up of a fixed number of "wires"

In computer science, comparator networks are abstract devices built up of a fixed number of "wires", carrying values, and comparator modules that connect pairs of wires, swapping the values on the wires if they are not in a desired order. Such networks are typically designed to perform sorting on fixed numbers of values, in which case they are called sorting networks.

In computability theory, the μ-operator, minimization operator, or unbounded search operator searches for the least natural number with a given property. Adding the μ-operator to the primitive recursive functions makes it possible to define all computable functions.

In computer science, corecursion is a type of operation that is dual to recursion. Whereas recursion works analytically, starting on data further from a base case and breaking it down into smaller data and repeating until one reaches a base case, corecursion works synthetically, starting from a base case and building it up, iteratively producing data further removed from a base case. Put simply, corecursive algorithms use the data that they themselves produce, bit by bit, as they become available, and needed, to produce further bits of data. A similar but distinct concept is generative recursion which may lack a definite "direction" inherent in corecursion and recursion.

<span class="mw-page-title-main">Quicksort</span> Divide and conquer sorting algorithm

Quicksort is an efficient, general-purpose sorting algorithm. Quicksort was developed by British computer scientist Tony Hoare in 1959 and published in 1961. It is still a commonly used algorithm for sorting. Overall, it is slightly faster than merge sort and heapsort for randomized data, particularly on larger distributions.

<span class="mw-page-title-main">Recursion (computer science)</span> Use of functions that call themselves

In computer science, recursion is a method of solving a computational problem where the solution depends on solutions to smaller instances of the same problem. Recursion solves such recursive problems by using functions that call themselves from within their own code. The approach can be applied to many types of problems, and recursion is one of the central ideas of computer science.

The power of recursion evidently lies in the possibility of defining an infinite set of objects by a finite statement. In the same manner, an infinite number of computations can be described by a finite recursive program, even if this program contains no explicit repetitions.

In computer science, weight-balanced binary trees (WBTs) are a type of self-balancing binary search trees that can be used to implement dynamic sets, dictionaries (maps) and sequences. These trees were introduced by Nievergelt and Reingold in the 1970s as trees of bounded balance, or BB[α] trees. Their more common name is due to Knuth.

In computer science and recursion theory the McCarthy Formalism (1963) of computer scientist John McCarthy clarifies the notion of recursive functions by use of the IF-THEN-ELSE construction common to computer science, together with four of the operators of primitive recursive functions: zero, successor, equality of numbers and composition. The conditional operator replaces both primitive recursion and the mu-operator.

Interpolation sort is a sorting algorithm that is a kind of bucket sort. It uses an interpolation formula to assign data to the bucket. A general interpolation formula is:

References

  1. Knuth, Donald E. (1991). "Textbook Examples of Recursion". Artificial Intelligence and Mathematical Theory of Computation: 207–229. arXiv: cs/9301113 . Bibcode:1993cs........1113K. doi:10.1016/B978-0-12-450010-5.50018-9. ISBN   9780124500105. S2CID   6411737.
  2. Cowles, John (2013) [2000]. "Knuth's generalization of McCarthy's 91 function". In Kaufmann, M.; Manolios, P.; Strother Moore, J (eds.). Computer-Aided reasoning: ACL2 case studies. Kluwer Academic. pp. 283–299. ISBN   9781475731880.