]> sourceware.org Git - newlib-cygwin.git/commitdiff
strtod: Convert 64 bit double to 64 bit int during computation
authorCorinna Vinschen <corinna@vinschen.de>
Mon, 9 Apr 2018 09:31:04 +0000 (11:31 +0200)
committerCorinna Vinschen <corinna@vinschen.de>
Mon, 9 Apr 2018 09:31:04 +0000 (11:31 +0200)
The gdtoa implementation uses the type long, defined as Long, in lots
of code.  For historical reason newlib defines Long as int32_t instead.

This works fine, as long as floating point exceptions are not enabled.
The conversion to 32 bit int can lead to a FE_INVALID situation.

Example:

  const char *str = "121645100408832000.0";
  char *ptr;

  feenableexcept (FE_INVALID);
  strtod (str, &ptr);

This leads to the following situation in strtod

  double aadj;
  Long L;

  [...]
  L = (Long)aadj;

For instance, on x86_64 the code here is

  cvttsd2si %xmm0,%eax

At this point, aadj is 2529648000.0 in our example.  The conversion to
32 bit %eax results in a negative int value, thus the conversion is
invalid.  With feenableexcept (FE_INVALID), a SIGFPE is raised.

Fix this by always using 64 bit ints here if double is not a 32 bit type
to avoid this type of FP exceptions.

Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
newlib/libc/stdlib/strtod.c

index 402510cdf24ee332a9fc09c6f73df06b1975a6e2..0cfa9e6ae78114b06d80b53e8f78d40c6db33c1b 100644 (file)
@@ -1186,7 +1186,16 @@ _strtod_l (struct _reent *ptr, const char *__restrict s00, char **__restrict se,
 #endif
                if (y == z) {
                        /* Can we stop now? */
+#ifndef _DOUBLE_IS_32BITS
+                       /* If FE_INVALID floating point exceptions are
+                          enabled, a conversion to a 32 bit value is
+                          dangerous.  A positive double value can result
+                          in a negative 32 bit int, thus raising SIGFPE.
+                          To avoid this, always convert into 64 bit here. */
+                       __int64_t L = (__int64_t)aadj;
+#else
                        L = (Long)aadj;
+#endif
                        aadj -= L;
                        /* The tolerances below are conservative. */
                        if (dsign || dword1(rv) || dword0(rv) & Bndry_mask) {
This page took 0.031698 seconds and 5 git commands to generate.