3

This program calls strtod() on the first command line argument and prints the returned value:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <float.h>

int main(int argc, char **argv)
{
  errno = 0;
  double d = strtod(argv[1], NULL);
  int errno_sav = errno;

  printf("string = %s\n", argv[1]);
  printf("d = %.*e = %a\n", DBL_DIG + 2, d, d);
  printf("errno = %d\n", errno_sav);
  printf("DBL_MIN = %.*e = %a\n", DBL_DIG + 2, DBL_MIN, DBL_MIN);
  return 0;
}

When I run it on SUSE Linux Enterprise Server 11 SP2 or Linux Mint 17 Qiana with an argument of 2.22507385850720138309e-308, corresponding to the smallest representable1 double value (DBL_MIN), it gives the output I expect:

string = 2.22507385850720138309e-308
d = 2.22507385850720138e-308 = 0x1p-1022
errno = 0
DBL_MIN = 2.22507385850720138e-308 = 0x1p-1022

However, on SUSE Linux Enterprise Server 11 SP3 with the same argument2, errno is set to ERANGE:

string = 2.22507385850720138309e-308
d = 2.22507385850720138e-308 = 0x1p-1022
errno = 34
DBL_MIN = 2.22507385850720138e-308 = 0x1p-1022

Is the second behaviour valid, and if so why?


Footnotes:

  1. Since DBL_MIN is representable I think this question is different from "Odd behavior when converting C strings to/from doubles" where the value converted had underflowed.

  2. On SUSE if I run the program with an argument of 2.22507385850720138310e-308 then errno is set to 0. (And if I run the program with an argument of 0x1p-1022 then errno is also set to 0.)

Community
  • 1
  • 1
Ben C
  • 658
  • 6
  • 18
  • Rather than `printf("d = %e\n", d);`, use `printf("d = %.*e\n", DBL_DIG + 2, d);` to show `d` to sufficient precision. May also want to use `printf("d = %a\n", d);` for exact output. – chux - Reinstate Monica Feb 02 '17 at 15:08
  • 1
    Both systems `2.22507385850720138e-308`. I'll modify the program to show these. – Ben C Feb 02 '17 at 15:10
  • Note also that your input number is certainly *not* exactly representable, as can be determined quickly by sight. Given a proper fraction greater than zero and with a finite binary representation, the decimal representation of that fraction is also finite, and the least-significant non-zero digit of its decimal representation is 5. Since the least-significant decimal digit of *your* number is 9 (and this is in the fractional part of the number), the fractional part, and thus the whole number, does not have a finite binary representation. – John Bollinger Feb 02 '17 at 15:28
  • Note about last edit: post now discusses 3 different strings `...138309e-308`, `...138310e-308`, `...138e-308` unlike the original `...138309e-308` used consistently. – chux - Reinstate Monica Feb 02 '17 at 16:07
  • @chux - thanks, I have amended it with the output from the program run with the correct argument `...138309e-308`. (The format `%.*e` does still return `...138e-308` for both d and DBL_MIN.) – Ben C Feb 02 '17 at 17:09
  • You should read the value of `errno` before invoking `printf()`. `printf()` or any if the functions it invokes might change the value of `errno`. – chqrlie Feb 04 '17 at 16:08
  • @chqrlie - thanks, I've fixed that. (The output is unchanged.) – Ben C Feb 06 '17 at 07:58

2 Answers2

3

The input value 2.22507385850720138309e-308 is smaller than the exact value of DBL_MIN (2-1022). A larger decimal expansion is:

2.225073858507201383090232717332404064219215980462331830553327416887204434813918...e-308 v.
2.22507385850720138309e-308 

So technically this results in underflow.

The C 1999 (7.20.1.3) states that in this case:

If the result underflows (7.12.1), the functions return a value whose magnitude is no greater than the smallest normalized positive number in the return type; whether errno acquires the value ERANGE is implementation-defined.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
Simon Byrne
  • 7,694
  • 1
  • 26
  • 50
  • Yes, that makes sense. E.g. to confirm in `bc` use `scale = 1000 ; 2^(-1022) - 2.22507385850720138309 * 10^(-308)` and observe that the result is greater than zero. Thanks! – Ben C Feb 02 '17 at 15:17
  • Why then don't they #define DBL_MIN as the apparently less problematic value `2.22507385850720138310e-308`? – Ben C Feb 02 '17 at 15:20
  • Note that SUSE's strtod() manpage says: "If the correct value would cause underflow, zero is returned and ERANGE is stored in errno.". So its behaviour seems consistent with the C standard but not its own documentation! – Ben C Feb 02 '17 at 15:23
  • @BenC The smallest normalized `double` is `2.225073858507201_38309...e-308` the next larger value is `2.225073858507201_87715...e-308` `double d = 2.225073858507201_38310e-308` would get the value `2.225073858507201_38309...e-308`. – chux - Reinstate Monica Feb 02 '17 at 15:25
3

As I read the C spec, this is not correct behavior.

strtod() refers to 7.12.1:

7.12.1 Treatment of error conditions
The result underflows if the magnitude of the mathematical result is so small that the mathematical result cannot be represented, without extraordinary roundoff error, in an object of the specified type. C11 §7.12.1 6

Although, mathematically, input strings are less than DBL_MIN,...

text
2.22507385850720_138e-308       // later post
2.22507385850720_138309e-308    // original post
DBL_MIN
2.22507385850720_13830902...e-308 

... the conversion to (double) DBL_MIN is not with extraordinary roundoff error. Value should convert to DBL_MIN without setting errno.

For reference, other nearby double are shown.

2.22507385850720_08890245...e-308  nextafter(DBL_MIN, 0.0),if sub-normals allowed, else 0.0
2.22507385850720_13830902...e-308  DBL_MIN
2.22507385850720_18771558...e-308  nextafter(DBL_MIN, 1.0)

I suspect the underlying code simple performed the conversion with extend precision like long double and then tested if the long double result was less than DBL_MIN rather than considering the edge conditions.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256