Previous column

Next column


Not a Number of Floating Point Problems

Gary T. Leavens, Department of Computer Science, Iowa State University, USA

space COLUMN

PDF Icon
PDF Version

Floating-point numbers and floating-point arithmetic contain some surprising pitfalls. In particular, the widely-adopted IEEE 754 standard contains a number that is “not a number,” and thus has some surprising properties. One has to be extremely careful in writing assertions about floating point numbers, to avoid these pitfalls. This column describes the problems and how a language might eliminate them.

That Indian bread is heavenly, isn’t it?
Yes, it’s nan of the above.


1 THE STANDARD BACKGROUND

Floating-point numbers in many modern programming languages follow the IEEE 754 standard [IEE85]. This standard has had many beneficial effects, for example on the accuracy and portability of floating-point software, none of which are the subject of this column.

Instead, I will discuss a set of problems that arise from the treatment of NaN, the “not a number” value1, in floating-point operations.

The NaN value is intended to represent an “undefined” value. In particular, it is used to represent the value of expressions such as 0.0/0.0 and log(-3.14). The designers of the standard could have chosen to cause exceptions or to trap such expressions in some way, and indeed it is an option in the IEEE 754 standard to have such expressions cause exceptions or traps. However, not all programming languages support exception handling mechanisms, and so the standard also provides for a special NaN value, which represents such errors.

2 YOUR INTRODUCTION TO NAN PROBLEMS

Once NaN is admitted as a floating-point value, one must define how all of the floating-point operations act on it. Operations that return floating-point numbers are usually defined to be strict with respect to NaN arguments in the sense that if any argument is NaN, then the result is NaN. For example, for all floating-point numbers x, x + NaN = NaN. Strictness does not cause problems.

Problems arise when dealing with operators that do not return a floating-point number. In particular, comparisons return a Boolean value, and so they cannot be strict in the same sense as operators that return a floating-point value. So what should the value of NaN < NaN be? There are really only three choices. Besides true and false, the other choice would be to cause an exception or a trap. But again, since not all programming languages support exceptions, the standard committee allowed for such operations to return a proper Boolean value. In the case of NaN < NaN, it seems that the obvious answer is false, and this is the IEEE 754 standard’s answer.

Indeed, the standard says that most Boolean-valued comparisons return false when either argument (or both) is NaN. The only exception is that for all x, NaN ≠ x is true. But following the general rule, for all x, NaN = x is false, NaN < x is false, and so are NaN < x, NaN > x, and NaN > x.

These decisions require specifiers to be doubly precise when dealing with floatingpoint numbers.

The difficulty is that formal methods relies on mathematics, especially as embodied in logic and theorem provers. Both mathematics and theorem provers give equality a special status. Equality’s most basic attribute is that it is reflexive; that is, for all x, x = x. However, in the words of Mark Twain [Twa95] “this rule is flung down and danced upon” by the IEEE 754 standard, since NaN = NaN is false.

Another problem for specifiers is the violation of tricotomy. The tricotomy law says that either x < y, x = y, or x > y; but if either x or y is NaN, then all three of these expressions may be false. Violation of tricotomy tells us that, with NaN, floating-point numbers are no longer totally ordered. Of course, this makes sense if you consider NaN to be “not a number” as only the numbers are totally ordered. The trouble is that type systems usually consider NaN to be a number.

3 YOUR AVERAGE EXAMPLE

Let’s see some practical examples of the problems caused by these particular properties of NaN. In this column I will use Java [AGH00] and the Java Modeling Language (JML) [BCC+05, LBR06] for such examples. Java floating point follows the IEEE 754 standard, in having a NaN value with the properties of NaN described above. Although Java has an exception handling mechanism, the built-in floating-point operations in Java never raise exceptions, but use NaN to represent errors.

Consider a Java method that takes an array of double precision floating point numbers and returns their average. Figure 1 gives a straightforward implementation in Java.

Figure 1: The method average.

Specifying the code in Figure 1 is surprisingly difficult. Part of the difficulty comes from the well-known approximate behavior of floating point numbers [Gol91]. Since an implementation’s algorithm may change the answer by a small amount, and since we want to permit different algorithms, the specification should not specify a single result, but a range of acceptable results. JML has some built-in convenience methods for specifying such a range of results. For doubles, one can use the class org.jmlspecs.models.JMLDouble, which has a static approximatelyEqualTo method that tests if the difference between its first two arguments within the tolerance given its third argument. Using this method, one could write the following postcondition in JML (assuming epsilon is a public static final field that is defined elsewhere).

However, given the theme of this column, we must immediately suspect whether or not this is a sensible specification.

First, we definitely do not want epsilon to be NaN. If epsilon were NaN, then it would make no sense to be approximately equal within epsilon, as all comparisons to NaN are false. But it will not do to write:

which true for all values of epsilon, even when epsilon is NaN. This is because in Java Double.NaN != x is true for all numbers, including Double.NaN itself. Fortunately, what we want can be specified by an invariant such as the following:

which is false when epsilon is NaN. And alternative is to use Java’s special test for NaN values and write:

Now we have to worry about what happens when one or more of the elements in the array items is NaN.2 In such a case, the Java code returns NaN, which becomes the value of \result in the postcondition. But as approximatelyEqualTo is specified to return false when any of its arguments are NaN.3 So the specification is violated whenever the code returns NaN, even when it “should” do so.

How can one fix this inconsistency between the code and the specification?

One technique is to specify that no element of the array items can be NaN. But again, one has to be doubly precise. A precondition such as the following won’t do.

The above precondition is equivalent to true, because != returns true when either of its operands is NaN. So one has to write something like the following.

However, even the precondition above is not enough to prevent problems. If items is a zero-length array, then the code for average will divide by zero, resulting in NaN. Of course, that will again lead to a violation of the postcondition. To prevent this one needs an additional precondition, like the following.

(Notice that there are no worries about using != for integers, since there are no “non-integer” values in Java’s int type.)

We can also fix the inconsistency between the specification and the code in a different way by adding an additional “specification case.” In JML method can have multiple specification cases, connected with the keyword “also.” Each specification case is governed by a precondition. When the precondition in a specification case is true, the method must satisfy that case’s postcondition. (If there are several specification cases with true preconditions, then all of the corresponding postconditions must be satisfied. This idea of breaking up a specification into several cases is due to Jeannette Wing [Win83] and was independently reinvented by Alan Wills [Wil94].)

Figure 2: The method average, with two specification cases. In JML specifications are written above the method that they specify

Thus, in JML, we could keep the specification we have developed so far, and add to it a specification case whose precondition allows items to contain NaN and also allows items to be empty. The postcondition should describe what the code does in this case, which is to return NaN. Of course we have to be doubly precise, and use Double.isNaN instead of using == to test for NaN, but if we do this, everything works. The result is the second specification case shown in Figure 2.

Looking at Figure 2, one immediately sees that the specification is more verbose than the code. Most of the extra specification, however, is there to handle NaN. We had to be quite careful about NaN in the specification, because we are necessarily writing Boolean assertions instead of code that returns floating point numbers. The part of the specification that is not concerned with NaN, namely the first specification case minus its first precondition (i.e., lines 3-8 of Figure 2), is quite similar in length to the method’s code. While the second specification case is entirely optional, we cannot eliminate the first precondition in the first specification case. That precondition is necessary to prevent inconsistencies. Thus the presence of NaN adds an unavoidable cost to this specification.

It is worth pointing out that this cost is not a result of formalizing the specification. Even if we were to write an informal English description of the average method, the presence of NaN as a value makes it necessary to state what happens when one of the elements of items is NaN. To see this, just consider what a test case for the average method: what is the expected result when one of the elements is NaN?

This kind of example illustrates our experience with specifying many methods in the Java libraries that work with floating-point numbers. Unless one considers NaN as a special case, the specification is likely to be wrong, even if it looks right.

4 NAN OF THESE LANGUAGES

The specification problems with NaN in Java and JML result from an inconsistency between the value space of floating-point numbers with NaN and the Booleans. This inconsistency could be resolved in several ways.

The first is to permit a third Boolean value, NaB (which stands for “not a Boolean”). We would define all floating-point comparisons involving NaN to return NaB. However, this requires the definition of a three-valued logic for the Booleans. While various consistent three-valued logics have been defined [BCJ84], they are not popular in current programming languages or theorem provers. Furthermore, for consistency, one would probably need to have such “not a T” values for every type T. Consider, for example, what should be the result of the following if-expression?

In a language like Java, a reasonable answer might be “not-an-integer.” While threevalued logics do not present insurmountable problems for specification languages, it is not clear how well they would fare with users.

The second way to resolve the inconsistency would be to ban NaN from the language. Built-in operations and user-defined methods would throw various exceptions whenever an expression would otherwise return NaN. Besides simplifying the language, this strategy makes the treatment of floating-point numbers consistent with integer arithmetic. It also avoids the mathematical problems with NaN and specification problems described above. Specifiers would not have to consider NaN as a special case, saving considerable overhead and eliminating a wide class of potential errors. Programmers already are familiar with exceptions and there are well established techniques for specifying them, including simply specifying the normal case using a precondition [Mey97]. The only disadvantage is the possible loss of efficiency in floating point computations. However, Hauser has argued that appropriately-designed exception handling mechanisms can both clarify floating point code and even improve its speed [Hau96].

Are there compromises between these approaches? One can imagine, as allowed by the IEEE 754 standard, a set of switches, global parameters, or options, that cause floating-point arithmetic to either cause exceptions or return NaN. However, once NaN is part of the language, specifications will have to consider it, except in programs where NaN values are prohibited. Libraries, however, would either have to consider NaN values in their specifications or require the switches to be set in such a way as to prohibit NaN values. This seems unsatisfactory from the point of view of specification and somewhat ad hoc.

A better approach might be to allow NaN as a value, but not in the standard floating-point types. Instead, there would be a separate type that included all of the normal floating-point values and NaN. (This is analogous to the treatment of null proposed by Fähndrich and Leino [FL03], and similar to ideas of Goguen and Meseguer [GM87].) Applying this idea to Java would mean that double and float would not include NaN as a value, and instead there would be two new types, doubleWithNaN and floatWithNaN. The type double would be a subtype of doubleWithNaN, and so assignment of double values to variables of the latter type would be permitted without casting. However, casting would be necessary to convert from doubleWithNaN to double, and the cast would throw an exception if the value was NaN.

To avoid the problems with the lack of reflexive equality, the == and != operators would not be defined on the type doubleWithNaN. There would instead be a builtin primitive to test if a doubleWithNaN is NaN. Users and libraries could use this primitive together with casting to construct tests, but since they would not be using the == and != syntax, they would not cause the same kind of understanding problems.

The same treatment could also be applied to other built-in comparison operators. That is, the operations <, <=, >, and >= would not be available for the type doubleWithNaN. Again, users could construct their own operations to do comparisons and such operations could be put in libraries, however the lack of a standard syntax would give users a clue that the tricotomy law might not hold.

It would be interesting to flesh out these approaches, implement them, and then compare them from many points of view, including efficiency, reliability of floatingpoint code, and ease and clarity of specification.

5 CONCLUSIONS

When writing specifications for methods involving floating-point numbers, specifiers have to be doubly careful. In particular, they must always consider the possibility of NaN as a value, and the peculiar ways in which comparisons work in the presence of NaN. While specification language features such as the ability to write multiple specification cases can help clarify the intended specification, language designers could completely eliminate these problems. A simple and consistent approach is to throw exceptions instead of using NaN. If it is desired to keep NaN as a value, the type system could be adjusted to avoid some of its pitfalls. While the resulting type system is more complex, specifications would be considerably simplified for many common cases. This might ultimately help programmers and lead to more reliable languages.

ACKNOWLEDGMENTS

Thanks to the JML community for their work on JML and its tools. This work was supported in part by NSF grants CCF-0428078, and CCF-0429567.

Footnote:

1 Technically there is one such NaN value for each floating-point type; for example Java has Float.NaN and Double.NaN.

2 I am using a version of JML that implicitly adds preconditions to assert that each argument of a reference type, such as items is not null [CR05].

3 Making approximatelyEqualTo return false when one of its arguments is NaN is necessary to be consistent with the IEEE 754 standard and Java’s arithmetic.


REFERENCES

[AGH00] Ken Arnold, James Gosling, and David Holmes. The Java Programming Language Third Edition. Addison-Wesley, Reading, MA, 2000.

[BCC+05] Lilian Burdy, Yoonsik Cheon, David Cok, Michael Ernst, Joe Kiniry, Gary T. Leavens, K. Rustan M. Leino, and Erik Poll. An overview of JML tools and applications. International Journal on Software Tools for Technology Transfer (STTT), 7(3):212–232, June 2005.

[BCJ84] H. Barringer, J. H. Cheng, and C. B. Jones. A logic covering undefinedness in program proofs. Acta Informatica, 21(3):251–269, October 1984.

[CR05] Patrice Chalin and Frèdèeric Rioux. Non-null references by default in the java modeling language. In Proceedings of the Workshop on the Specification and Verification of Component-Based Systems (SAVCBS’05), volume 31(2) of ACM Software Engineering Notes. ACM, 2005.

[FL03] Manuel Fähndrich and K. Rustan M. Leino. Declaring and checking nonnull types in an object-oriented langauge. In OOPSLA ’03: Proceedings of the 18th ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications, volume 38(11) of ACM SIGPLAN Notices, pages 302–312, New York, NY, November 2003. ACM.

[GM87] Joseph A. Goguen and Jose Meseguer. Order-sorted algebra solves the constructor-selector, multiple representation and coercion problems. Technical Report CSLI-87-92, Center for the Study of Language and Information, March 1987. Appears in Second Annual Symposium on Logic in Computer Science, Ithaca, NY, June, 1987, pages 18-29.

[Gol91] David Goldberg. What every computer scientist should know about floating-point arithmetic. ACM Computing Surveys, 23(1):5–48, March 1991.

[Hau96] John R. Hauser. Handling floating-point exceptions in numeric programs. ACM Transactions on Programming Languages and Systems, 18(2):139–174, 1996.

[IEE85] IEEE Standards Committee 754. IEEE Standard for binary floating-point arithmetic, ANSI/IEEE Standard 754-1985. Institute of Electrical and Electronics Engineers, New York, 1985. Reprinted in ACM SIGPLAN Notices, 22(2):9-25, 1987.

[LBR06] Gary T. Leavens, Albert L. Baker, and Clyde Ruby. Preliminary design of JML: A behavioral interface specification language for Java. Technical Report 98-06-rev29, Iowa State University, Department of Computer Science, January 2006. To appear in ACM SIGSOFT Software Engineering Notes.

[LH94] K. Lano and H. Haughton, editors. Object-Oriented Specification Case Studies. The Object-Oriented Series. Prentice Hall, New York, NY, 1994.

[Mey97] Bertrand Meyer. Object-oriented Software Construction. Prentice Hall, New York, NY, second edition, 1997.

[Twa95] Mark Twain. Fenimore Cooper’s Literary Offenses, 1895. Reprinted in Twain: Collected Tales, Sketches, Speeches, and Essays: Volume 2: 1891-1910, pages 180-192 (Penguin Putman Inc., 1992).

[Wil94] Alan Wills. Refinement in Fresco. In Lano and Houghton [LH94], chapter 9, pages 184–201.

[Win83] Jeannette Marie Wing. A two-tiered approach to specifying programs. Technical Report TR-299, Massachusetts Institute of Technology, Laboratory for Computer Science, 1983.

About the author





Gary T. Leavens is a professor of computer science at Iowa State University in Ames, Iowa. He has taught there since receiving his Ph.D. from MIT in 1989. His research interests include programming and specification language design and semantics, program verification, and formal methods, with an emphasis on the objectoriented and aspect-oriented paradigms. His email is leavens@cs.iastate.edu. See also http://www.cs.iastate.edu/~leavens/


Cite this column as follows: Gary T. Leavens: "Not a Number of Floating Point Problems", in Journal of Object Technology, vol. 5, no. 2, March–April 2006, pages 75-83, http://www.jot.fm/issues/issue_2006_03/column8


Previous column

Next column