aboutsummaryrefslogtreecommitdiff
path: root/src/port/strtof.c
blob: 7aca8e2e3df25332d3fbc467b4a0bc5cbbb8371e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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