]> granicus.if.org Git - postgresql/commitdiff
Portability fixes and bug fixes for recent floating point input changes.
authorNeil Conway <neilc@samurai.com>
Sun, 14 Mar 2004 05:22:52 +0000 (05:22 +0000)
committerNeil Conway <neilc@samurai.com>
Sun, 14 Mar 2004 05:22:52 +0000 (05:22 +0000)
In particular, don't depend on strtod() to accept 'NaN' and 'Infinity'
inputs (while this is required by C99, not all platforms are compliant
with that yet). Also, don't require glibc's behavior from isinf():
it seems that on a lot of platforms isinf() does not itself distinguish
between negative and positive infinity.

src/backend/utils/adt/float.c

index aed643d862b858bf1d63c1255a73f3e8a186fbbd..9e078bfb349b52dbc9397a4c2c8f419901389f7e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/float.c,v 1.99 2004/03/12 00:25:40 neilc Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/float.c,v 1.100 2004/03/14 05:22:52 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -109,9 +109,30 @@ int                        extra_float_digits = 0;         /* Added to DBL_DIG or FLT_DIG */
 
 static void CheckFloat4Val(double val);
 static void CheckFloat8Val(double val);
+static int     is_infinite(double val);
 static int     float4_cmp_internal(float4 a, float4 b);
 static int     float8_cmp_internal(float8 a, float8 b);
 
+/*
+ * Returns -1 if 'val' represents negative infinity, 1 if 'val'
+ * represents (positive) infinity, and 0 otherwise. On some platforms,
+ * this is equivalent to the isinf() macro, but not everywhere: C99
+ * does not specify that isinf() needs to distinguish between positive
+ * and negative infinity.
+ */
+static int
+is_infinite(double val)
+{
+       int inf = isinf(val);
+
+       if (inf == 0)
+               return 0;
+
+       if (val > 0)
+               return 1;
+
+       return -1;
+}
 
 /*
  * check to see if a float4 val is outside of the FLOAT4_MIN,
@@ -162,48 +183,78 @@ Datum
 float4in(PG_FUNCTION_ARGS)
 {
        char       *num = PG_GETARG_CSTRING(0);
+       char       *orig_num;
        double          val;
        char       *endptr;
 
+       /*
+        * endptr points to the first character _after_ the sequence we
+        * recognized as a valid floating point number. orig_num points to
+        * the original input string.
+        */
+       orig_num = num;
+
+       /*
+        * Check for an empty-string input to begin with, to avoid
+        * the vagaries of strtod() on different platforms.
+        *
+        * In releases prior to 7.5, we accepted an empty string as valid
+        * input (yielding a float4 of 0). In 7.5, we accept empty
+        * strings, but emit a warning noting that the feature is
+        * deprecated. In 7.6+, the warning should be replaced by an
+        * error.
+        */
+       if (*num == '\0')
+       {
+               ereport(WARNING,
+                               (errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
+                                errmsg("deprecated input syntax for type real: \"\""),
+                                errdetail("This input will be rejected in "
+                                                  "a future release of PostgreSQL.")));
+               PG_RETURN_FLOAT4((float4) 0.0);
+       }
+
+       /* skip leading whitespace */
+       while (*num != '\0' && isspace(*num))
+               num++;
+
        errno = 0;
        val = strtod(num, &endptr);
 
        if (errno == ERANGE)
                ereport(ERROR,
                                (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-                                errmsg("\"%s\" is out of range for type real", num)));
+                                errmsg("\"%s\" is out of range for type real",
+                                               orig_num)));
 
+       /* did we not see anything that looks like a double? */
        if (num == endptr)
        {
                /*
-                * We didn't find anything that looks like a float in the input
-                *
-                * In releases prior to 7.5, we accepted an empty string as
-                * valid input (yielding a float8 of 0). In 7.5, we accept
-                * empty strings, but emit a warning noting that the feature
-                * is deprecated. In 7.6+, the warning should be replaced by
-                * an error.
+                * C99 requires that strtod() accept NaN and [-]Infinity, but
+                * not all platforms support that yet. Therefore, we check for
+                * these inputs ourselves.
                 */
-               if (*num == '\0')
+               if (strncasecmp(num, "NaN", 3) == 0)
                {
-                       ereport(WARNING,
-                                       (errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
-                                        errmsg("deprecated input syntax for type real: \"\""),
-                                        errdetail("This input will be rejected in "
-                                                          "a future release of PostgreSQL.")));
-                       Assert(val == 0.0);
-               }
-               else if (strcasecmp(num, "NaN") == 0)
                        val = NAN;
-               else if (strcasecmp(num, "Infinity") == 0)
+                       endptr = num + 3;
+               }
+               else if (strncasecmp(num, "Infinity", 8) == 0)
+               {
                        val = HUGE_VAL;
-               else if (strcasecmp(num, "-Infinity") == 0)
+                       endptr = num + 8;
+               }
+               else if (strncasecmp(num, "-Infinity", 9) == 0)
+               {
                        val = -HUGE_VAL;
+                       endptr = num + 9;
+               }
                else
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                         errmsg("invalid input syntax for type real: \"%s\"",
-                                                       num)));
+                                                       orig_num)));
        }
 
        /* skip trailing whitespace */
@@ -215,11 +266,11 @@ float4in(PG_FUNCTION_ARGS)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                 errmsg("invalid input syntax for type real: \"%s\"",
-                                               num)));
+                                               orig_num)));
 
        /*
         * if we get here, we have a legal double, still need to check to see
-        * if it's a legal float
+        * if it's a legal float4
         */
        if (!isinf(val))
                CheckFloat4Val(val);
@@ -236,22 +287,27 @@ float4out(PG_FUNCTION_ARGS)
 {
        float4          num = PG_GETARG_FLOAT4(0);
        char       *ascii = (char *) palloc(MAXFLOATWIDTH + 1);
-       int                     infflag;
-       int                     ndig;
 
        if (isnan(num))
                PG_RETURN_CSTRING(strcpy(ascii, "NaN"));
-       infflag = isinf(num);
-       if (infflag > 0)
-               PG_RETURN_CSTRING(strcpy(ascii, "Infinity"));
-       if (infflag < 0)
-               PG_RETURN_CSTRING(strcpy(ascii, "-Infinity"));
 
-       ndig = FLT_DIG + extra_float_digits;
-       if (ndig < 1)
-               ndig = 1;
+       switch (is_infinite(num))
+       {
+               case 1:
+                       strcpy(ascii, "Infinity");
+                       break;
+               case -1:
+                       strcpy(ascii, "-Infinity");
+                       break;
+               default:
+               {
+                       int ndig = FLT_DIG + extra_float_digits;
+                       if (ndig < 1)
+                               ndig = 1;
 
-       sprintf(ascii, "%.*g", ndig, num);
+                       sprintf(ascii, "%.*g", ndig, num);
+               }
+       }
 
        PG_RETURN_CSTRING(ascii);
 }
@@ -292,48 +348,78 @@ Datum
 float8in(PG_FUNCTION_ARGS)
 {
        char       *num = PG_GETARG_CSTRING(0);
+       char       *orig_num;
        double          val;
        char       *endptr;
 
+       /*
+        * endptr points to the first character _after_ the sequence we
+        * recognized as a valid floating point number. orig_num points to
+        * the original input string.
+        */
+       orig_num = num;
+
+       /*
+        * Check for an empty-string input to begin with, to avoid
+        * the vagaries of strtod() on different platforms.
+        *
+        * In releases prior to 7.5, we accepted an empty string as valid
+        * input (yielding a float8 of 0). In 7.5, we accept empty
+        * strings, but emit a warning noting that the feature is
+        * deprecated. In 7.6+, the warning should be replaced by an
+        * error.
+        */
+       if (*num == '\0')
+       {
+               ereport(WARNING,
+                               (errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
+                                errmsg("deprecated input syntax for type double precision: \"\""),
+                                errdetail("This input will be rejected in "
+                                                  "a future release of PostgreSQL.")));
+               PG_RETURN_FLOAT8(0.0);
+       }
+
+       /* skip leading whitespace */
+       while (*num != '\0' && isspace(*num))
+               num++;
+
        errno = 0;
        val = strtod(num, &endptr);
 
        if (errno == ERANGE)
                ereport(ERROR,
                                (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-                                errmsg("\"%s\" is out of range for type double precision", num)));
+                                errmsg("\"%s\" is out of range for type double precision",
+                                               orig_num)));
 
+       /* did we not see anything that looks like a double? */
        if (num == endptr)
        {
                /*
-                * We didn't find anything that looks like a float in the input
-                *
-                * In releases prior to 7.5, we accepted an empty string as
-                * valid input (yielding a float8 of 0). In 7.5, we accept
-                * empty strings, but emit a warning noting that the feature
-                * is deprecated. In 7.6+, the warning should be replaced by
-                * an error.
+                * C99 requires that strtod() accept NaN and [-]Infinity, but
+                * not all platforms support that yet. Therefore, we check for
+                * these inputs ourselves.
                 */
-               if (*num == '\0')
+               if (strncasecmp(num, "NaN", 3) == 0)
                {
-                       ereport(WARNING,
-                                       (errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
-                                        errmsg("deprecated input syntax for type double precision: \"\""),
-                                        errdetail("This input will be rejected in "
-                                                          "a future release of PostgreSQL.")));
-                       Assert(val == 0.0);
-               }
-               else if (strcasecmp(num, "NaN") == 0)
                        val = NAN;
-               else if (strcasecmp(num, "Infinity") == 0)
+                       endptr = num + 3;
+               }
+               else if (strncasecmp(num, "Infinity", 8) == 0)
+               {
                        val = HUGE_VAL;
-               else if (strcasecmp(num, "-Infinity") == 0)
+                       endptr = num + 8;
+               }
+               else if (strncasecmp(num, "-Infinity", 9) == 0)
+               {
                        val = -HUGE_VAL;
+                       endptr = num + 9;
+               }
                else
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                         errmsg("invalid input syntax for type double precision: \"%s\"",
-                                                       num)));
+                                                       orig_num)));
        }
 
        /* skip trailing whitespace */
@@ -345,7 +431,7 @@ float8in(PG_FUNCTION_ARGS)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                 errmsg("invalid input syntax for type double precision: \"%s\"",
-                                               num)));
+                                               orig_num)));
 
        if (!isinf(val))
                CheckFloat8Val(val);
@@ -362,22 +448,27 @@ float8out(PG_FUNCTION_ARGS)
 {
        float8          num = PG_GETARG_FLOAT8(0);
        char       *ascii = (char *) palloc(MAXDOUBLEWIDTH + 1);
-       int                     infflag;
-       int                     ndig;
 
        if (isnan(num))
                PG_RETURN_CSTRING(strcpy(ascii, "NaN"));
-       infflag = isinf(num);
-       if (infflag > 0)
-               PG_RETURN_CSTRING(strcpy(ascii, "Infinity"));
-       if (infflag < 0)
-               PG_RETURN_CSTRING(strcpy(ascii, "-Infinity"));
 
-       ndig = DBL_DIG + extra_float_digits;
-       if (ndig < 1)
-               ndig = 1;
+       switch (is_infinite(num))
+       {
+               case 1:
+                       strcpy(ascii, "Infinity");
+                       break;
+               case -1:
+                       strcpy(ascii, "-Infinity");
+                       break;
+               default:
+               {
+                       int ndig = DBL_DIG + extra_float_digits;
+                       if (ndig < 1)
+                               ndig = 1;
 
-       sprintf(ascii, "%.*g", ndig, num);
+                       sprintf(ascii, "%.*g", ndig, num);
+               }
+       }
 
        PG_RETURN_CSTRING(ascii);
 }