Most implementations of C use the IEEE 754 standard for floating point representation. In this representation floats are encoded using 1 sign bit, 8 exponent bits and 23 mantissa bits. Doubles are encoded and used exactly the same way except they use 1 sign bit, 11 exponent bits, and 52 mantissa bits. These bits encode the values of s, the sign; M, the significand; and E, the exponent. Floating point numbers are then calculated as (-1)s * M * 2 E.
Ordinarily all of the mantissa bits are used to express significant figures in addition to a leading 1 which is implied and therefore left out. Thus floats ordinarily have 24 significant bits of precision and doubles ordinarily have 53 significant bits of precision. Such numbers are called normalized numbers. All floating point numbers are limited in this sense that they have fixed precision. FLP00-C. Understand the limitations of floating point numbers.
However, to express extremely small numbers that are too small to encode normally because not enough exponent bits are available mantissa bits are used to extend the possible range of exponents. These bits no longer function as significant bits of precision so the total precision of extremely small numbers is smaller than usual. Such numbers are called denormalized. Using even normalized numbers where precision is required can pose a risk FLP02-C. Avoid using floating point numbers when precise computation is needed, but denormalized numbers are vastly more limited than one would expect from dealing only with normalized values.
Using denormalized numbers can severely impair the precision of floating point numbers and should not be used.
Noncompliant Code Example
This code attempts to reduce a floating point number to a denormalized value and then restore the value. This operation is very imprecise.
#include <stdio.h> float x = 1/3.0; printf("Original : %e\n", x); x = x * 7e-45; printf("Denormalized? : %e\n", x); x = x / 7e-45; printf("Restored : %e\n", x);
This code produces the following output on implementations that use IEEE 754 floats
Original : 3.333333e-01 Denormalized? : 2.802597e-45 Restored : 4.003710e-01
Compliant Solution
Don't produce code that could use denormalized numbers. If floats are producing denormalized numbers use doubles instead.
#include <stdio.h> double x = 1/3.0; printf("Original : %e\n", x); x = x * 7e-45; printf("Denormalized? : %e\n", x); x = x / 7e-45; printf("Restored : %e\n", x);
Original : 3.333333e-01 Denormalized? : 2.333333e-45 Restored : 3.333333e-01
If using doubles also produces denormalized numbers, using long doubles might help or it might not (on some implementations long double has the same exponent range as double). If using long doubles produces denormalized numbers, some other solution must be found.
Printing denormalized numbers
Denormalized numbers can also be troublesome because some functions have implementation defined behavior when used with denormalized values. For example, using the %a or $%A conversion specifier in a format string can produce implementation defined results when applied to denormalized numbers.
According to ISO/IEC 9899:TC3 §7.19.6.1:
A double argument representing a floating-point number is converted in the style ?0xh.hhhh p±d, where there is one hexadecimal digit (which is nonzero if the argument is a normalized floating-point number and is otherwise unspecified) before the decimal-point character
Relying on the %a
and %A
specifiers to produce values without a leading zero is error prone.
#include<stdio.h> float x = 0x1p-125; double y = 0x1p-1020; printf("normalized float with %%e : %e\n", x); printf("normalized float with %%a : %a\n", x); x = 0x1p-140; printf("denormalized float with %%e : %e\n", x); printf("denormalized float with %%a : %a\n", x); printf("normalized double with %%e : %e\n", y); printf("normalized double with %%a : %a\n", y); y = 0x1p-1050; printf("denormalized double with %%e : %e\n", y); printf("denormalized double with %%a : %a\n", y);
Implementation Details
On a 32-bit Linux machine using gcc version 4.3.2 this code produces the following output.
normalized float with %e : 2.350989e-38 normalized float with %a : 0x1p-125 denormalized float with %e : 7.174648e-43 denormalized float with %a : 0x1p-140 normalized double with %e : 8.900295e-308 normalized double with %a : 0x1p-1020 denormalized double with %e : 8.289046e-317 denormalized double with %a : 0x0.0000001p-1022
Risk Assessment
Floating point numbers are an approximation; using subnormal floating point number are a worse approximation.
Rule |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
FLP05-C |
low |
probable |
high |
P4 |
L3 |
Automated Detection
TODO
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Bibliography
[IEEE 754]
[Bryant 03] Computer Systems: A Programmer's Perspective. Section 2.4 Floating Point
[ISO/IEC 9899:1999]
FLP04-C. Check floating point inputs for exceptional values 05. Floating Point (FLP) FLP30-C. Do not use floating point variables as loop counters