mirror of https://github.com/postgres/postgres
Using strtod() creates a double-rounding problem; the input decimal value is first rounded to the nearest double; rounding that to the nearest float may then give an incorrect result. An example is that 7.038531e-26 when input via strtod and then rounded to float4 gives 0xAE43FEp-107 instead of the correct 0xAE43FDp-107. Values output by earlier PG versions with extra_float_digits=3 should all be read in with the same values as previously. However, values supplied by other software using shortest representations could be mis-read. On platforms that lack a strtof() entirely, we fall back to the old incorrect rounding behavior. (As strtof() is required by C99, such platforms are considered of primarily historical interest.) On VS2013, some workarounds are used to get correct error handling. The regression tests now test for the correct input values, so platforms that lack strtof() will need resultmap entries. An entry for HP-UX 10 is included (more may be needed). Reviewed-By: Tom Lane Discussion: https://postgr.es/m/871s5emitx.fsf@news-spur.riddles.org.uk Discussion: https://postgr.es/m/87d0owlqpv.fsf@news-spur.riddles.org.ukpull/38/head
parent
37d9916020
commit
f397e08599
@ -0,0 +1,123 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* strtof.c |
||||
* |
||||
* Portions Copyright (c) 2019, PostgreSQL Global Development Group |
||||
* |
||||
* |
||||
* IDENTIFICATION |
||||
* src/port/strtof.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
#include "c.h" |
||||
|
||||
#include <float.h> |
||||
#include <math.h> |
||||
|
||||
#ifndef HAVE_STRTOF |
||||
/*
|
||||
* strtof() is part of C99; this version is only for the benefit of obsolete |
||||
* platforms. As such, it is known to return incorrect values for edge cases, |
||||
* which have to be allowed for in variant files for regression test results |
||||
* for any such platform. |
||||
*/ |
||||
|
||||
float |
||||
strtof(const char *nptr, char **endptr) |
||||
{ |
||||
int caller_errno = errno; |
||||
double dresult; |
||||
float fresult; |
||||
|
||||
errno = 0; |
||||
dresult = strtod(nptr, endptr); |
||||
fresult = (float) dresult; |
||||
|
||||
if (errno == 0) |
||||
{ |
||||
/*
|
||||
* Value might be in-range for double but not float. |
||||
*/ |
||||
if (dresult != 0 && fresult == 0) |
||||
caller_errno = ERANGE; /* underflow */ |
||||
if (!isinf(dresult) && isinf(fresult)) |
||||
caller_errno = ERANGE; /* overflow */ |
||||
} |
||||
else |
||||
caller_errno = errno; |
||||
|
||||
errno = caller_errno; |
||||
return fresult; |
||||
} |
||||
|
||||
#elif HAVE_BUGGY_WINDOWS_STRTOF |
||||
/*
|
||||
* On Windows, there's a slightly different problem: VS2013 has a strtof() |
||||
* that returns the correct results for valid input, but may fail to report an |
||||
* error for underflow or overflow, returning 0 instead. Work around that by |
||||
* trying strtod() when strtof() returns 0.0 or [+-]Inf, and calling it an |
||||
* error if the result differs. Also, strtof() doesn't handle subnormal input |
||||
* well, so prefer to round the strtod() result in such cases. (Normally we'd |
||||
* just say "too bad" if strtof() doesn't support subnormals, but since we're |
||||
* already in here fixing stuff, we might as well do the best fix we can.) |
||||
*/ |
||||
float |
||||
pg_strtof(const char *nptr, char **endptr) |
||||
{ |
||||
int caller_errno = errno; |
||||
float fresult; |
||||
|
||||
errno = 0; |
||||
fresult = (strtof)(nptr, endptr); |
||||
if (errno) |
||||
{ |
||||
/* On error, just return the error to the caller. */ |
||||
return fresult; |
||||
} |
||||
else if ((*endptr == nptr) || isnan(fresult) || |
||||
((fresult >= FLT_MIN || fresult <= -FLT_MIN) && !isinf(fresult))) |
||||
{ |
||||
/*
|
||||
* If we got nothing parseable, or if we got a non-0 non-subnormal |
||||
* finite value (or NaN) without error, then return that to the caller |
||||
* without error. |
||||
*/ |
||||
errno = caller_errno; |
||||
return fresult; |
||||
} |
||||
else |
||||
{ |
||||
/*
|
||||
* Try again. errno is already 0 here. |
||||
*/ |
||||
double dresult = strtod(nptr, NULL); |
||||
if (errno) |
||||
{ |
||||
/* On error, just return the error */ |
||||
return fresult; |
||||
} |
||||
else if ((dresult == 0.0 && fresult == 0.0) || |
||||
(isinf(dresult) && isinf(fresult) && (fresult == dresult))) |
||||
{ |
||||
/* both values are 0 or infinities of the same sign */ |
||||
errno = caller_errno; |
||||
return fresult; |
||||
} |
||||
else if ((dresult > 0 && dresult <= FLT_MIN && (float)dresult != 0.0) || |
||||
(dresult < 0 && dresult >= -FLT_MIN && (float)dresult != 0.0)) |
||||
{ |
||||
/* subnormal but nonzero value */ |
||||
errno = caller_errno; |
||||
return (float) dresult; |
||||
} |
||||
else |
||||
{ |
||||
errno = ERANGE; |
||||
return fresult; |
||||
} |
||||
} |
||||
} |
||||
|
||||
#endif |
||||
@ -0,0 +1,436 @@ |
||||
-- |
||||
-- FLOAT4 |
||||
-- |
||||
CREATE TABLE FLOAT4_TBL (f1 float4); |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES (' 0.0'); |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('1004.30 '); |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES (' -34.84 '); |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('1.2345678901234e+20'); |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('1.2345678901234e-20'); |
||||
-- test for over and under flow |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'); |
||||
ERROR: "10e70" is out of range for type real |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'); |
||||
ERROR: "-10e70" is out of range for type real |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'); |
||||
ERROR: "10e-70" is out of range for type real |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'); |
||||
ERROR: "-10e-70" is out of range for type real |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400'); |
||||
ERROR: "10e400" is out of range for type real |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400'); |
||||
ERROR: "-10e400" is out of range for type real |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400'); |
||||
ERROR: "10e-400" is out of range for type real |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400'); |
||||
ERROR: "-10e-400" is out of range for type real |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400'); |
||||
^ |
||||
-- bad input |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES (''); |
||||
ERROR: invalid input syntax for type real: "" |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (''); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES (' '); |
||||
ERROR: invalid input syntax for type real: " " |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (' '); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('xyz'); |
||||
ERROR: invalid input syntax for type real: "xyz" |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('xyz'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('5.0.0'); |
||||
ERROR: invalid input syntax for type real: "5.0.0" |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5.0.0'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('5 . 0'); |
||||
ERROR: invalid input syntax for type real: "5 . 0" |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5 . 0'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0'); |
||||
ERROR: invalid input syntax for type real: "5. 0" |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0'); |
||||
ERROR: invalid input syntax for type real: " - 3.0" |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0'); |
||||
^ |
||||
INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); |
||||
ERROR: invalid input syntax for type real: "123 5" |
||||
LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); |
||||
^ |
||||
-- special inputs |
||||
SELECT 'NaN'::float4; |
||||
float4 |
||||
-------- |
||||
NaN |
||||
(1 row) |
||||
|
||||
SELECT 'nan'::float4; |
||||
float4 |
||||
-------- |
||||
NaN |
||||
(1 row) |
||||
|
||||
SELECT ' NAN '::float4; |
||||
float4 |
||||
-------- |
||||
NaN |
||||
(1 row) |
||||
|
||||
SELECT 'infinity'::float4; |
||||
float4 |
||||
---------- |
||||
Infinity |
||||
(1 row) |
||||
|
||||
SELECT ' -INFINiTY '::float4; |
||||
float4 |
||||
----------- |
||||
-Infinity |
||||
(1 row) |
||||
|
||||
-- bad special inputs |
||||
SELECT 'N A N'::float4; |
||||
ERROR: invalid input syntax for type real: "N A N" |
||||
LINE 1: SELECT 'N A N'::float4; |
||||
^ |
||||
SELECT 'NaN x'::float4; |
||||
ERROR: invalid input syntax for type real: "NaN x" |
||||
LINE 1: SELECT 'NaN x'::float4; |
||||
^ |
||||
SELECT ' INFINITY x'::float4; |
||||
ERROR: invalid input syntax for type real: " INFINITY x" |
||||
LINE 1: SELECT ' INFINITY x'::float4; |
||||
^ |
||||
SELECT 'Infinity'::float4 + 100.0; |
||||
?column? |
||||
---------- |
||||
Infinity |
||||
(1 row) |
||||
|
||||
SELECT 'Infinity'::float4 / 'Infinity'::float4; |
||||
?column? |
||||
---------- |
||||
NaN |
||||
(1 row) |
||||
|
||||
SELECT 'nan'::float4 / 'nan'::float4; |
||||
?column? |
||||
---------- |
||||
NaN |
||||
(1 row) |
||||
|
||||
SELECT 'nan'::numeric::float4; |
||||
float4 |
||||
-------- |
||||
NaN |
||||
(1 row) |
||||
|
||||
SELECT '' AS five, * FROM FLOAT4_TBL; |
||||
five | f1 |
||||
------+------------- |
||||
| 0 |
||||
| 1004.3 |
||||
| -34.84 |
||||
| 1.23457e+20 |
||||
| 1.23457e-20 |
||||
(5 rows) |
||||
|
||||
SELECT '' AS four, f.* FROM FLOAT4_TBL f WHERE f.f1 <> '1004.3'; |
||||
four | f1 |
||||
------+------------- |
||||
| 0 |
||||
| -34.84 |
||||
| 1.23457e+20 |
||||
| 1.23457e-20 |
||||
(4 rows) |
||||
|
||||
SELECT '' AS one, f.* FROM FLOAT4_TBL f WHERE f.f1 = '1004.3'; |
||||
one | f1 |
||||
-----+-------- |
||||
| 1004.3 |
||||
(1 row) |
||||
|
||||
SELECT '' AS three, f.* FROM FLOAT4_TBL f WHERE '1004.3' > f.f1; |
||||
three | f1 |
||||
-------+------------- |
||||
| 0 |
||||
| -34.84 |
||||
| 1.23457e-20 |
||||
(3 rows) |
||||
|
||||
SELECT '' AS three, f.* FROM FLOAT4_TBL f WHERE f.f1 < '1004.3'; |
||||
three | f1 |
||||
-------+------------- |
||||
| 0 |
||||
| -34.84 |
||||
| 1.23457e-20 |
||||
(3 rows) |
||||
|
||||
SELECT '' AS four, f.* FROM FLOAT4_TBL f WHERE '1004.3' >= f.f1; |
||||
four | f1 |
||||
------+------------- |
||||
| 0 |
||||
| 1004.3 |
||||
| -34.84 |
||||
| 1.23457e-20 |
||||
(4 rows) |
||||
|
||||
SELECT '' AS four, f.* FROM FLOAT4_TBL f WHERE f.f1 <= '1004.3'; |
||||
four | f1 |
||||
------+------------- |
||||
| 0 |
||||
| 1004.3 |
||||
| -34.84 |
||||
| 1.23457e-20 |
||||
(4 rows) |
||||
|
||||
SELECT '' AS three, f.f1, f.f1 * '-10' AS x FROM FLOAT4_TBL f |
||||
WHERE f.f1 > '0.0'; |
||||
three | f1 | x |
||||
-------+-------------+-------------- |
||||
| 1004.3 | -10043 |
||||
| 1.23457e+20 | -1.23457e+21 |
||||
| 1.23457e-20 | -1.23457e-19 |
||||
(3 rows) |
||||
|
||||
SELECT '' AS three, f.f1, f.f1 + '-10' AS x FROM FLOAT4_TBL f |
||||
WHERE f.f1 > '0.0'; |
||||
three | f1 | x |
||||
-------+-------------+------------- |
||||
| 1004.3 | 994.3 |
||||
| 1.23457e+20 | 1.23457e+20 |
||||
| 1.23457e-20 | -10 |
||||
(3 rows) |
||||
|
||||
SELECT '' AS three, f.f1, f.f1 / '-10' AS x FROM FLOAT4_TBL f |
||||
WHERE f.f1 > '0.0'; |
||||
three | f1 | x |
||||
-------+-------------+-------------- |
||||
| 1004.3 | -100.43 |
||||
| 1.23457e+20 | -1.23457e+19 |
||||
| 1.23457e-20 | -1.23457e-21 |
||||
(3 rows) |
||||
|
||||
SELECT '' AS three, f.f1, f.f1 - '-10' AS x FROM FLOAT4_TBL f |
||||
WHERE f.f1 > '0.0'; |
||||
three | f1 | x |
||||
-------+-------------+------------- |
||||
| 1004.3 | 1014.3 |
||||
| 1.23457e+20 | 1.23457e+20 |
||||
| 1.23457e-20 | 10 |
||||
(3 rows) |
||||
|
||||
-- test divide by zero |
||||
SELECT '' AS bad, f.f1 / '0.0' from FLOAT4_TBL f; |
||||
ERROR: division by zero |
||||
SELECT '' AS five, * FROM FLOAT4_TBL; |
||||
five | f1 |
||||
------+------------- |
||||
| 0 |
||||
| 1004.3 |
||||
| -34.84 |
||||
| 1.23457e+20 |
||||
| 1.23457e-20 |
||||
(5 rows) |
||||
|
||||
-- test the unary float4abs operator |
||||
SELECT '' AS five, f.f1, @f.f1 AS abs_f1 FROM FLOAT4_TBL f; |
||||
five | f1 | abs_f1 |
||||
------+-------------+------------- |
||||
| 0 | 0 |
||||
| 1004.3 | 1004.3 |
||||
| -34.84 | 34.84 |
||||
| 1.23457e+20 | 1.23457e+20 |
||||
| 1.23457e-20 | 1.23457e-20 |
||||
(5 rows) |
||||
|
||||
UPDATE FLOAT4_TBL |
||||
SET f1 = FLOAT4_TBL.f1 * '-1' |
||||
WHERE FLOAT4_TBL.f1 > '0.0'; |
||||
SELECT '' AS five, * FROM FLOAT4_TBL; |
||||
five | f1 |
||||
------+-------------- |
||||
| 0 |
||||
| -34.84 |
||||
| -1004.3 |
||||
| -1.23457e+20 |
||||
| -1.23457e-20 |
||||
(5 rows) |
||||
|
||||
-- test edge-case coercions to integer |
||||
SELECT '32767.4'::float4::int2; |
||||
int2 |
||||
------- |
||||
32767 |
||||
(1 row) |
||||
|
||||
SELECT '32767.6'::float4::int2; |
||||
ERROR: smallint out of range |
||||
SELECT '-32768.4'::float4::int2; |
||||
int2 |
||||
-------- |
||||
-32768 |
||||
(1 row) |
||||
|
||||
SELECT '-32768.6'::float4::int2; |
||||
ERROR: smallint out of range |
||||
SELECT '2147483520'::float4::int4; |
||||
int4 |
||||
------------ |
||||
2147483520 |
||||
(1 row) |
||||
|
||||
SELECT '2147483647'::float4::int4; |
||||
ERROR: integer out of range |
||||
SELECT '-2147483648.5'::float4::int4; |
||||
int4 |
||||
------------- |
||||
-2147483648 |
||||
(1 row) |
||||
|
||||
SELECT '-2147483900'::float4::int4; |
||||
ERROR: integer out of range |
||||
SELECT '9223369837831520256'::float4::int8; |
||||
int8 |
||||
--------------------- |
||||
9223369837831520256 |
||||
(1 row) |
||||
|
||||
SELECT '9223372036854775807'::float4::int8; |
||||
ERROR: bigint out of range |
||||
SELECT '-9223372036854775808.5'::float4::int8; |
||||
int8 |
||||
---------------------- |
||||
-9223372036854775808 |
||||
(1 row) |
||||
|
||||
SELECT '-9223380000000000000'::float4::int8; |
||||
ERROR: bigint out of range |
||||
-- Test for correct input rounding in edge cases. |
||||
-- These lists are from Paxson 1991, excluding subnormals and |
||||
-- inputs of over 9 sig. digits. |
||||
SELECT float4send('5e-20'::float4); |
||||
float4send |
||||
------------ |
||||
\x1f6c1e4a |
||||
(1 row) |
||||
|
||||
SELECT float4send('67e14'::float4); |
||||
float4send |
||||
------------ |
||||
\x59be6cea |
||||
(1 row) |
||||
|
||||
SELECT float4send('985e15'::float4); |
||||
float4send |
||||
------------ |
||||
\x5d5ab6c4 |
||||
(1 row) |
||||
|
||||
SELECT float4send('55895e-16'::float4); |
||||
float4send |
||||
------------ |
||||
\x2cc4a9bd |
||||
(1 row) |
||||
|
||||
SELECT float4send('7038531e-32'::float4); |
||||
float4send |
||||
------------ |
||||
\x15ae43fe |
||||
(1 row) |
||||
|
||||
SELECT float4send('702990899e-20'::float4); |
||||
float4send |
||||
------------ |
||||
\x2cf757ca |
||||
(1 row) |
||||
|
||||
SELECT float4send('3e-23'::float4); |
||||
float4send |
||||
------------ |
||||
\x1a111234 |
||||
(1 row) |
||||
|
||||
SELECT float4send('57e18'::float4); |
||||
float4send |
||||
------------ |
||||
\x6045c22c |
||||
(1 row) |
||||
|
||||
SELECT float4send('789e-35'::float4); |
||||
float4send |
||||
------------ |
||||
\x0a23de70 |
||||
(1 row) |
||||
|
||||
SELECT float4send('2539e-18'::float4); |
||||
float4send |
||||
------------ |
||||
\x2736f449 |
||||
(1 row) |
||||
|
||||
SELECT float4send('76173e28'::float4); |
||||
float4send |
||||
------------ |
||||
\x7616398a |
||||
(1 row) |
||||
|
||||
SELECT float4send('887745e-11'::float4); |
||||
float4send |
||||
------------ |
||||
\x3714f05c |
||||
(1 row) |
||||
|
||||
SELECT float4send('5382571e-37'::float4); |
||||
float4send |
||||
------------ |
||||
\x0d2eaca7 |
||||
(1 row) |
||||
|
||||
SELECT float4send('82381273e-35'::float4); |
||||
float4send |
||||
------------ |
||||
\x128289d0 |
||||
(1 row) |
||||
|
||||
SELECT float4send('750486563e-38'::float4); |
||||
float4send |
||||
------------ |
||||
\x0f18377e |
||||
(1 row) |
||||
|
||||
-- Test that the smallest possible normalized input value inputs |
||||
-- correctly, either in 9-significant-digit or shortest-decimal |
||||
-- format. |
||||
-- |
||||
-- exact val is 1.1754943508... |
||||
-- shortest val is 1.1754944000 |
||||
-- midpoint to next val is 1.1754944208... |
||||
SELECT float4send('1.17549435e-38'::float4); |
||||
float4send |
||||
------------ |
||||
\x00800000 |
||||
(1 row) |
||||
|
||||
SELECT float4send('1.1754944e-38'::float4); |
||||
float4send |
||||
------------ |
||||
\x00800000 |
||||
(1 row) |
||||
|
||||
Loading…
Reference in new issue