Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

C uses 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.

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 denormalized numbers can severely impair the precision of floating point numbers and should not be used

Use of the %a or %A conversion specifiers has unspecified behavior when used on non-normalized floating-point 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 not produce values with a leading zero is error prone.

Noncompliant Code Example

This noncompliant code relies on the %a specifier to produce a result starting with 0x1. or -0x1.
This behavior is guaranteed only for normalized numbers, but may fail for non-normalized values.code attempts to reduce a floating point number to a denormalized value and then restore the value. This operation is very imprecise. FLP02-C

Code Block
bgColor#FFCCCC
#include <stdio.h>
#include <string.h>

/* Return the index of the first non-zero hexadecimalint main() {
  float doublex in the string line. Return NULL if there is= 1/3.0;
  printf("Original no double is present in line. */
static char *findHexDouble(char *line) { : %e\n", x);
  charx *index1= = strstr(line, "-0x1.")x * 7e-45;
  char *index2 = strstr(line, "0x1.");

  if (index1 == NULL && index2 == NULL) {
    return NULL;
  }
  else if (index1 == NULL) {
    return index2;
  }
  return (index1 < index2) ? index1 : index2;
}

static void printDouble(double val) {
  char buf[64];
  char *convertedDouble;
  sprintf(buf, "%.8a", val);
  convertedDouble = findHexDouble(buf);
  if (convertedDouble != NULL) {
    printf("%e is a double\n", val);
  }
  else {
    printf("%e is not a double\n", val);
  }
}

int main(void) {
  double tiny = 0x1.0p-1020;
  size_t i;
  for (i = 0; i < 6; i++) {
    printDouble(tiny);
    tiny /= 2;
  }
}

Implementation Specific

On a 32-bit Linux machine using GCC 4.3.2 this code produces the following output

Code Block

8.900295e-308 is a double
4.450148e-308 is a double
2.225074e-308 is a double
1.112537e-308 is not a double
5.562685e-309 is not a double
2.781342e-309 is not a double

Compliant Solution

TODO

Code Block
bgColor#ccccff
 TODO

Risk Assessment

TODO

printf("Denormalized? : %e\n", x);
  x = x / 7e-45;
  printf("Restored      : %e\n", x);
}

This code produces the following output

Code Block

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.

Code Block
bgColor#ccccff
#include <stdio.h>
#include <stdio.h>

int main() {
  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);
}
Code Block

Original      : 3.333333e-01
Denormalized? : 2.333333e-45
Restored      : 3.333333e-01

If using doubles also produces denormalized numbers some other solution must be found.

Risk Assessment

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

FLP05-C

medium

probable

high

---

P4

L3 ---

Automated Detection

TODO

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

References

TODOComputer Systems: A Programmer's Perspective. Section 2.4 Floating Point