diff options
Diffstat (limited to 'src/backend/utils/adt/float.c')
-rw-r--r-- | src/backend/utils/adt/float.c | 56 |
1 files changed, 43 insertions, 13 deletions
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 117ded8d1de..eb111ee2dbf 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -104,13 +104,39 @@ is_infinite(double val) /* * float4in - converts "num" to float4 + * + * Note that this code now uses strtof(), where it used to use strtod(). + * + * The motivation for using strtof() is to avoid a double-rounding problem: + * for certain decimal inputs, if you round the input correctly to a double, + * and then round the double to a float, the result is incorrect in that it + * does not match the result of rounding the decimal value to float directly. + * + * One of the best examples is 7.038531e-26: + * + * 0xAE43FDp-107 = 7.03853069185120912085...e-26 + * midpoint 7.03853100000000022281...e-26 + * 0xAE43FEp-107 = 7.03853130814879132477...e-26 + * + * making 0xAE43FDp-107 the correct float result, but if you do the conversion + * via a double, you get + * + * 0xAE43FD.7FFFFFF8p-107 = 7.03853099999999907487...e-26 + * midpoint 7.03853099999999964884...e-26 + * 0xAE43FD.80000000p-107 = 7.03853100000000022281...e-26 + * 0xAE43FD.80000008p-107 = 7.03853100000000137076...e-26 + * + * so the value rounds to the double exactly on the midpoint between the two + * nearest floats, and then rounding again to a float gives the incorrect + * result of 0xAE43FEp-107. + * */ Datum float4in(PG_FUNCTION_ARGS) { char *num = PG_GETARG_CSTRING(0); char *orig_num; - double val; + float val; char *endptr; /* @@ -135,7 +161,7 @@ float4in(PG_FUNCTION_ARGS) "real", orig_num))); errno = 0; - val = strtod(num, &endptr); + val = strtof(num, &endptr); /* did we not see anything that looks like a double? */ if (endptr == num || errno != 0) @@ -143,14 +169,14 @@ float4in(PG_FUNCTION_ARGS) int save_errno = errno; /* - * C99 requires that strtod() accept NaN, [+-]Infinity, and [+-]Inf, + * C99 requires that strtof() accept NaN, [+-]Infinity, and [+-]Inf, * but not all platforms support all of these (and some accept them * but set ERANGE anyway...) Therefore, we check for these inputs - * ourselves if strtod() fails. + * ourselves if strtof() fails. * * Note: C99 also requires hexadecimal input as well as some extended * forms of NaN, but we consider these forms unportable and don't try - * to support them. You can use 'em if your strtod() takes 'em. + * to support them. You can use 'em if your strtof() takes 'em. */ if (pg_strncasecmp(num, "NaN", 3) == 0) { @@ -195,8 +221,18 @@ float4in(PG_FUNCTION_ARGS) * precision). We'd prefer not to throw error for that, so try to * detect whether it's a "real" out-of-range condition by checking * to see if the result is zero or huge. + * + * Use isinf() rather than HUGE_VALF on VS2013 because it generates + * a spurious overflow warning for -HUGE_VALF. Also use isinf() if + * HUGE_VALF is missing. */ - if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL) + if (val == 0.0 || +#if !defined(HUGE_VALF) || (defined(_MSC_VER) && (_MSC_VER < 1900)) + isinf(val) +#else + (val >= HUGE_VALF || val <= -HUGE_VALF) +#endif + ) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("\"%s\" is out of range for type real", @@ -232,13 +268,7 @@ float4in(PG_FUNCTION_ARGS) errmsg("invalid input syntax for type %s: \"%s\"", "real", orig_num))); - /* - * if we get here, we have a legal double, still need to check to see if - * it's a legal float4 - */ - check_float4_val((float4) val, isinf(val), val == 0); - - PG_RETURN_FLOAT4((float4) val); + PG_RETURN_FLOAT4(val); } /* |