1 Introduction
The development of software that depends on floatingpoint computations is particularly challenging due to the presence of roundoff errors in computer arithmetic. Roundoff errors originate from the difference between real numbers and their finite precision representation. Since roundoff errors accumulate during numerical computations, they may significantly affect the evaluation of both arithmetic and Boolean expressions. In particular, unstable tests occur when the guard of a conditional statement contains a floatingpoint expression whose roundoff error makes the actual Boolean value of the guard differ from the value that would be obtained assuming real arithmetic. The presence of unstable tests amplifies, even more, the divergence between the output of a floatingpoint program and its ideal evaluation in real arithmetic. This divergence may lead to catastrophic consequences in safetycritical applications.
Writing software that takes into consideration how unstable tests affect the execution flow of floatingpoint programs requires a deep comprehension of floatingpoint arithmetic. Furthermore, this process can be tedious and errorprone for programs with function calls and complex mathematical expressions. This paper presents a
fully automatic toolchain to generate and verify teststable floatingpoint C code from a functional specification in real arithmetic. This toolchain consists of:
a formallyverified program transformation that generates and instruments a floatingpoint program to detect unstable tests,

PRECiSA [MoscatoTDM17, TitoloFMM18], a static analyzer that computes sound estimations of the roundoff error that may occur in a floatingpoint program,

FramaC [KirchnerKPSY15], a collaborative tool suite for the analysis of C code, and

the Prototype Verification System (PVS) [OwreRS92], an interactive theorem prover for higherorder logic.
The input of the toolchain is a PVS specification of a numerical algorithm in real arithmetic, the desired floatingpoint format (single or double precision), and, optionally, initial ranges for the input variables. This program specification is straightforwardly implemented using floatingpoint arithmetic. This is done by replacing each realvalued operator by its floatingpoint counterpart. Furthermore, each realnumber constant and variable is rounded to its closest floatingpoint in the chosen format and rounding modality. Then, the proposed program transformation is applied. Numerically unstable tests are replaced with more restrictive ones that preserve the control flow of the realvalued original specification. These new tests take into consideration the roundoff error that may occur when the expressions of the original program are evaluated in floatingpoint arithmetic. In addition, the transformation instruments the program to emit a warning when the floatingpoint flow may diverge with respect to the original real number specification.
The transformed program is expressed in C syntax along with ACSL Specification Language annotations stating the relationship between the floatingpoint C implementation and its functional specification in real arithmetic. To this end, the roundoff errors that occur in conditional tests and in the overall computation of the program are soundly estimated by the static analyzer PRECiSA. The correctness property of the C program is specified as an ACSL postcondition stating that if the program terminates without a warning, it follows the same computational path as the realvalued specification, i.e., all unstable tests are detected.
An extension to the FramaC/WP plugin (Weakest Precondition calculus) is implemented to automatically generate verification conditions in the PVS language from the annotated C code. These verification conditions encode the correctness of the transformed program and are automatically discharged by proof strategies implemented in PVS. Therefore, no expertise in theorem proving nor knowledge on floatingpoint arithmetic is required from the user to verify the correctness of the generated C program.
The contributions of this work are summarized below.

A new and enhanced version of the program trasformation initially defined in [TitoloMFM18] that adds support for function calls, bounded recursion (forloops), and symbolic parameters.

A PVS formalization of the correctness of the proposed transformation.

An implementation of the proposed transformation integrated within the static analyzer PRECiSA.

An extension of the FramaC/WP plugin to generate proof obligations in the PVS specification language.

Proof strategies in PVS to automatically discharge the verification conditions generated by the FramaC/WP plugin.
The remainder of the paper is organized as follows. sec:fp_err provides technical background on floatingpoint numbers, roundoff errors, and unstable tests. A denotational semantics that collects information about the differences between floatingpoint and real computational flows is presented in sec:sem. The proposed program transformation to detect test instability is described in sec:transformation. sec:cgen illustrates the use of the proposed toolchain to automatically generate and verify a probably correct floatingpoint C program from a PVS realvalued specification. sec:related discusses related work and sec:concl concludes the paper.
2 FloatingPoint Numbers, RoundOff Errors, and Unstable Tests
Floatingpoint numbers [IEEE754floating] are finite precision representations of real numbers widely used in computer programs. In this work, a floatingpoint number, or a float, is formalized as a pair of integers , where is called the significand and the exponent of the float [Daumas2001, BoldoMunoz06]. A floatingpoint format is defined as a pair of integers , where is called the precision and is called the minimal exponent. Given a base , a pair represents a floatingpoint number in the format if and only if it holds that and . For instance, IEEE single and double precision floatingpoint numbers are specified by the formats and , respectively. Henceforth, will denote the set of floatingpoint numbers and the expression will denote a floatingpoint number in . A conversion function is defined to refer to the real number represented by a given float, i.e., , where is the base of the representation. The expression denotes the floatingpoint number in format closest to , i.e., the rounding of . The format will be omitted when clear from the context or irrelevant.
Definition 1 (Roundoff error)
Let be a floatingpoint number that represents a real number , the difference is called the roundoff error (or rounding error) of with respect to .
The unit in the last place (ulp) is a measure of the precision of a floatingpoint number as a representation of a real number. Given , represents the difference between two closest consecutive floatingpoint numbers and such that and . The can be used to bound the roundoff error of a real number with respect to its floatingpoint representation in the following way:
(1) 
Roundoff errors accumulate through the computation of mathematical operators. Therefore, an initial error that seems negligible may become significantly larger when combined and propagated inside nested mathematical expressions. The accumulated roundoff error is the difference between a floatingpoint expression and its realvalued counterpart and it depends on (a) the error introduced by the application of versus and (b) the propagation of the errors carried out by the arguments, i.e., the difference between and , for , in the application. Henceforth, it is assumed that for any floatingpoint operator of interest , there exists an error bound function such that, if holds for all , then:
(2) 
For example, in the case of the sum, the accumulated roundff error is defined as . More examples of error bound functions can be found in [MoscatoTDM17, TitoloFMM18].
The evaluation of Boolean expressions is also affected by rounding errors. When a Boolean expression evaluates differently in real and floatingpoint arithmetic, is said to be unstable. The presence of unstable tests amplifies the effect of roundoff errors in numerical programs since the computational flow of a floatingpoint program may significantly diverge from the ideal execution of its representation in real arithmetic. In fact, the output of a floatingpoint program is not only directly influenced by rounding errors accumulating in the mathematical expressions, but also by the error of taking the incorrect branch in the case of unstable tests.
Given a set of predefined floatingpoint operations, the corresponding set of operations over real numbers, a set of function symbols, a finite set of variables representing real values, and a finite set of variables representing floatingpoint values, where and are disjoint, the sets and of arithmetic expressions over real numbers and over floatingpoint numbers, respectively, are defined by the following grammars.
where , , , , , , , , and . It is assumed that there is a function that associates to each floatingpoint variable a variable representing the real value of . The function converts an arithmetic expression on floatingpoint numbers to an arithmetic expression on real numbers. It is defined by replacing each floatingpoint operation with the corresponding one on real numbers and by applying and to floatingpoint values and variables, respectively. Conversely, the function converts a real expression into a floatingpoint one by applying the rounding to constants and variables and by replacing each realvalued operator with the corresponding floatingpoint one. By abuse of notation, floatingpoint expressions are interpreted as their real number evaluation when occurring inside a realvalued expression.
Boolean expressions over the reals and over the floats are defined by the following grammar,
where and . The conjunction , disjunction , negation , , and have the usual classical logic meaning. The functions and convert a Boolean expression on floatingpoint numbers to a Boolean expression on real numbers and viceversa. They are defined, respectively, as the natural extension of and to Boolean expressions. Given a variable assignment , denotes the evaluation of the real Boolean expression . Similarly, given and , denotes the evaluation of the floatingpoint Boolean expression .
Definition 2 (Unstable Test)
A test is unstable if there exist two assignments and such that for all , and . Otherwise, the conditional expression is said to be stable.
In other words, a test is unstable when there exists an assignment from the free variables in to such that evaluates to a different Boolean value with respect to its realvalued counterpart . The evaluation of a conditional statement is said to follow an unstable path when is unstable and it is evaluated differently in real and floatingpoint arithmetic. When the flows coincide, the evaluation is said to follow a stable path.
3 A Denotational Semantics for FloatingPoint Programs
This section illustrates a denotational semantics to reason about roundoff errors and test instability in floatingpoint programs. This semantics collects information about both real and floatingpoint path conditions and soundly estimates the difference between the ideal realvalued result and the actual floatingpoint one. This information is collected symbolically. Therefore, the semantics supports symbolic parameters for which the numerical inputs are unknown. This semantics is an extension of the one presented in [TitoloFMM18] and it has been implemented in the static analyzer PRECiSA, which computes provably correct overestimations of the roundoff errors occurring in a floatingpoint program.
The language considered in this work is a simple functional language with binary and ary conditionals, letin expressions, arithmetic expressions, function calls, forloops, and a warning exceptional statement . The syntax of floatingpoint program expressions in is given by the following grammar.
(3)  
where , , , , , , , , and . The notation denotes a list of conditional branches.
Bounded recursion is added to the language as syntactic sugar using the construct. The expression emulates a for loop where is the control variable that ranges from to , is the variable where the result is accumulated with initial value , and is the body of the loop. For instance, represents the value , where is the recursive function .
A floatingpoint program is defined as a set of function declarations of the form , where are pairwise distinct variables in and all free variables appearing in are in . The natural number is called the arity of . Henceforth, it is assumed that programs are wellformed in the sense that, in a program , for every function call that occurs in the body of the declaration of a function , a unique function of arity is defined in before . Hence, the only recursion allowed is the one provided by the forloop construct. The set of floatingpoint programs is denoted as .
The proposed semantics collects for each combination of real and floatingpoint program paths: the real and floatingpoint path conditions, and three symbolic expressions representing: (1) the value of the output assuming the use of real arithmetic, (2) the value of the output assuming floatingpoint arithmetic, and (3) an overapproximation of the maximum roundoff error occurring in the computation. In addition, a flag is provided indicating if the element refers to either a stable or an unstable path. Since the semantics collects information about real and floatingpoint execution paths, it is possible to consider the error of taking the incorrect branch compared to the ideal execution using exact real arithmetic. This enables a sound treatment of unstable tests. The previous information is stored in a conditional error bound.
Definition 3 (Conditional error bound)
A conditional error bound is an expression of the form , where , , , , and ,.
Intuitively, indicates that if both conditions and are satisfied, the output of the ideal realvalued implementation of the program is , the output of the floatingpoint execution is , and the roundoff error is at most , i.e., . The subindex is used to mark by construction whether a conditional error bound corresponds to an unstable path, when , or to a stable path, when .
Let be the set of all conditional error bounds, and be the domain formed by sets of conditional error bounds. An environment is defined as a function mapping a variable to a set of conditional error bounds, i.e., . The empty environment is denoted as and maps every variable to the empty set . Let be the set of all possible function calls. An interpretation is a function
modulo variance
^{1}^{1}1Two functions are variants if for each there exists a renaming such that .. The set of all interpretations is denoted as . The empty interpretation is denoted as and maps everything to .Given and , the semantics of program expressions is defined in fig:sem as a function that returns the set of conditional error bounds representing the possible real and floatingpoint results, their difference, and their corresponding path conditions. Conditional error bounds of the form whose conditions’ conjunction is unsatisfiable, i.e., , are considered spurious and they are dropped from the semantics since they do not correspond to an actual trace of the program. In the following, the nontrivial cases are described.
 Variable.

The semantics of a variable consists of two cases. If belongs to the environment, then the variable has been previously bound to a program expression through a letin expression. In this case, the semantics of is exactly the semantics of . If does not belong to the environment, then is a parameter of the function. Here, a new conditional error bound is added with two placeholder and , representing the real value and the error of , respectively.
 Mathematical Operator.

The semantics of a floatingpoint operation is computed by composing the semantics of its operands. The real and floatingpoint values are obtained by applying the corresponding arithmetic operation to the values of the operands. The effect of the warning construct is propagated in the arithmetic expressions. Thus, it is assumed that for all floatingpoint and real operator , when for some . The new conditions are obtained as the combination of the conditions of the operands. The new conditional error bounds for are marked unstable if any of the conditional error bounds in the semantics of is unstable. is defined as if it exists such that , otherwise it is defined as .
 Letin expression.

The semantics of the expression updates the current environment by associating with variable the semantics of expression .
 Binary conditional.

The semantics of the conditional uses an auxiliary operator .
Definition 4 (Condition propagation operator)
Let and , if , otherwise it is undefined. The definition of naturally extends to sets of conditional error bounds, i.e., let , .
The semantics of and are enriched with information about the fact that real and floatingpoint control flows match, i.e., both and have the same value. In addition, new conditional error bounds are built to model the unstable cases when real and floatingpoint control flows do not coincide and, therefore, real and floatingpoint computations diverge. For example, if is satisfied but is not, the branch is taken in the floatingpoint computation, but the would have been taken in the real one. In this case, the real condition and its corresponding output are taken from the semantics of , while the floatingpoint condition and its corresponding output are taken from the semantics of . The condition is propagated in order to model that holds but does not. The conditional error bounds representing this case are marked with .
 Nary conditional.

The semantics of an nary conditional is composed of stable and unstable cases. The stable cases are built from the semantics of all the program subexpressions by enriching them with information stating that the correspondent guard and its real counterpart hold and all the previous guards and their real counterparts do not hold. All the unstable combinations are built by combining the real parts of the semantics of a program expression and the floatingpoint contributions of a different program expression . In addition, the operator is used to propagate the information that the real guard of and the floatingpoint guard of hold, while the guards of the previous branches do not hold.
 Function call.

The semantics of a function call combines the conditions coming from the interpretation of the function and the ones coming from the semantics of the parameters. Variables representing real values, floatingpoint values, and errors of formal parameters are replaced with the expressions coming from the semantics of the actual parameters. The notation denotes the substitution of for in the expression .
The semantics of a program is a function defined as the least fixed point of the immediate consequence operator , i.e., given , , which is defined as follows for each function symbol defined in .
(4) 
The least fixed point of is guaranteed to exist from the KnasterTarski Fixpoint theorem [Tarski55] since is monotonic over . This least fixedpoint converges in a finite number of steps for the programs with bounded recursion considered in this paper.
Example 1
Consider the function that is part of DAIDALUS^{2}^{2}2DAIDALUS is available from https://shemesh.larc.nasa.gov/fm/DAIDALUS/. (Detect and Avoid Alerting Logic for Unmanned Systems), a NASA library that implements detectandavoid algorithms for unmanned aircraft systems. This function computes the time to coaltitude of two vertically converging aircraft given their relative vertical position and relative vertical velocity . When the aircraft air vertically diverging, the function returns 0.
The semantics of consists of four conditional error bounds:
The first two elements correspond to the cases where real and floatingpoint computational flows coincide. In these cases, the roundoff error is bounded by when the branch is taken, otherwise, it is 0 since the integer is exactly representable as a float. The other two elements model the unstable paths. In these cases, the error is computed as the difference between the output of the two branches plus the accumulated roundoff error of the floatingpoint result.
A realvalued program (or, simply, a real program) has the same structure of a floatingpoint program where floatingpoint expressions are replaced with real number ones. A realvalued program does not contain any statements. The set of realvalued programs is denoted as . The function converts a real program into a floatingpoint one by applying, respectively, and to Boolean and arithmetic expressions occurring in the function declarations in . Conversely, returns the realnumber counterpart of a floatingpoint program. For every floatingpoint program , it holds that .
The presented semantics correctly models the difference between the floatingpoint program and its real number counterpart as stated in the following theorem.
Theorem 3.1
Let be a floatingpoint program. For every function symbol defined in , let be its realvalued counterpart defined in such that for all , . It holds that
where . The expression is called the overall error of the function .
Proof (Proof Sketch.)
Given , for each declaration occurring in , it exists a declaration in . Thus, holds if and only if holds. The proof proceeds by structural induction on the structure of the program expression . The main cases are the arithmetic expression and the conditional.
Given an arithmetic expression , from Formula (2), it follows that the error expression associated to is a correct overapproximation of the roundoff error, therefore , where .
Let , , and assume . By structural induction and by def:prop, it follows that
In addition, given and , the error of taking an unstable path is defined as the difference between the real and the floatingpoint results, which is bounded by the following value
Comments
There are no comments yet.