pparsefp.h (5948B)
1 #ifndef PPARSEFP_H 2 #define PPARSEFP_H 3 4 #ifdef __cplusplus 5 extern "C" { 6 #endif 7 8 #include <string.h> /* memcpy */ 9 10 /* 11 * Parses a float or double number and returns the length parsed if 12 * successful. The length argument is of limited value due to dependency 13 * on `strtod` - buf[len] must be accessible and must not be part of 14 * a valid number, including hex float numbers.. 15 * 16 * Unlike strtod, whitespace is not parsed. 17 * 18 * May return: 19 * - null on error, 20 * - buffer start if first character does not start a number, 21 * - or end of parse on success. 22 * 23 */ 24 25 #define PDIAGNOSTIC_IGNORE_UNUSED_FUNCTION 26 #include "pdiagnostic_push.h" 27 28 /* 29 * isinf is needed in order to stay compatible with strtod's 30 * over/underflow handling but isinf has some portability issues. 31 * 32 * Use the parse_double/float_is_range_error instead of isinf directly. 33 * This ensures optimizations can be added when not using strtod. 34 * 35 * On gcc, clang and msvc we can use isinf or equivalent directly. 36 * Other compilers such as xlc may require linking with -lm which may not 37 * be convienent so a default isinf is provided. If isinf is available 38 * and there is a noticable performance issue, define 39 * `PORTABLE_USE_ISINF`. This flag also affects isnan. 40 */ 41 #if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) || defined(PORTABLE_USE_ISINF) 42 #include <math.h> 43 #if defined(_MSC_VER) && !defined(isinf) 44 #include <float.h> 45 #define isnan _isnan 46 #define isinf(x) (!_finite(x)) 47 #endif 48 /* 49 * clang-3 through clang-8 but not clang-9 issues incorrect precision 50 * loss warning with -Wconversion flag when cast is absent. 51 */ 52 #if defined(__clang__) 53 #if __clang_major__ >= 3 && __clang_major__ <= 8 54 #define parse_double_isinf(x) isinf((float)x) 55 #define parse_double_isnan(x) isnan((float)x) 56 #endif 57 #endif 58 #if !defined(parse_double_isinf) 59 #define parse_double_isinf isinf 60 #endif 61 #define parse_float_isinf isinf 62 63 #else 64 65 #ifndef UINT8_MAX 66 #include <stdint.h> 67 #endif 68 69 /* Avoid linking with libmath but depends on float/double being IEEE754 */ 70 static inline int parse_double_isinf(const double x) 71 { 72 uint64_t u64x; 73 74 memcpy(&u64x, &x, sizeof(u64x)); 75 return (u64x & 0x7fffffff00000000ULL) == 0x7ff0000000000000ULL; 76 } 77 78 static inline int parse_float_isinf(float x) 79 { 80 uint32_t u32x; 81 82 memcpy(&u32x, &x, sizeof(u32x)); 83 return (u32x & 0x7fffffff) == 0x7f800000; 84 } 85 86 #endif 87 88 #if !defined(parse_double_isnan) 89 #define parse_double_isnan isnan 90 #endif 91 #if !defined(parse_float_isnan) 92 #define parse_float_isnan isnan 93 #endif 94 95 /* Returns 0 when in range, 1 on overflow, and -1 on underflow. */ 96 static inline int parse_double_is_range_error(double x) 97 { 98 return parse_double_isinf(x) ? (x < 0.0 ? -1 : 1) : 0; 99 } 100 101 static inline int parse_float_is_range_error(float x) 102 { 103 return parse_float_isinf(x) ? (x < 0.0f ? -1 : 1) : 0; 104 } 105 106 #ifndef PORTABLE_USE_GRISU3 107 #define PORTABLE_USE_GRISU3 1 108 #endif 109 110 #if PORTABLE_USE_GRISU3 111 #include "grisu3_parse.h" 112 #endif 113 114 #ifdef grisu3_parse_double_is_defined 115 static inline const char *parse_double(const char *buf, size_t len, double *result) 116 { 117 return grisu3_parse_double(buf, len, result); 118 } 119 #else 120 #include <stdio.h> 121 static inline const char *parse_double(const char *buf, size_t len, double *result) 122 { 123 char *end; 124 125 (void)len; 126 *result = strtod(buf, &end); 127 return end; 128 } 129 #endif 130 131 static inline const char *parse_float(const char *buf, size_t len, float *result) 132 { 133 const char *end; 134 double v; 135 union { uint32_t u32; float f32; } inf; 136 inf.u32 = 0x7f800000; 137 138 end = parse_double(buf, len, &v); 139 *result = (float)v; 140 if (parse_float_isinf(*result)) { 141 *result = v < 0 ? -inf.f32 : inf.f32; 142 return buf; 143 } 144 return end; 145 } 146 147 /* Inspired by https://bitbashing.io/comparing-floats.html */ 148 149 /* Return signed ULP distance or INT64_MAX if any value is nan. */ 150 static inline int64_t parse_double_compare(const double x, const double y) 151 { 152 int64_t i64x, i64y; 153 154 if (x == y) return 0; 155 if (parse_double_isnan(x)) return INT64_MAX; 156 if (parse_double_isnan(y)) return INT64_MAX; 157 memcpy(&i64x, &x, sizeof(i64x)); 158 memcpy(&i64y, &y, sizeof(i64y)); 159 if ((i64x < 0) != (i64y < 0)) return INT64_MAX; 160 return i64x - i64y; 161 } 162 163 /* Same as double, but INT32_MAX if nan. */ 164 static inline int32_t parse_float_compare(const float x, const float y) 165 { 166 int32_t i32x, i32y; 167 168 if (x == y) return 0; 169 if (parse_float_isnan(x)) return INT32_MAX; 170 if (parse_float_isnan(y)) return INT32_MAX; 171 memcpy(&i32x, &x, sizeof(i32x)); 172 memcpy(&i32y, &y, sizeof(i32y)); 173 if ((i32x < 0) != (i32y < 0)) return INT32_MAX; 174 return i32x - i32y; 175 } 176 177 /* 178 * Returns the absolute distance in floating point ULP (representational bit difference). 179 * Uses signed return value so that INT64_MAX and INT32_MAX indicates NaN similar to 180 * the compare function. 181 */ 182 static inline int64_t parse_double_dist(const double x, const double y) 183 { 184 uint64_t m64; 185 int64_t i64; 186 187 i64 = parse_double_compare(x, y); 188 /* Absolute integer value of compare. */ 189 m64 = (uint64_t)-(i64 < 0); 190 return (int64_t)(((uint64_t)i64 + m64) ^ m64); 191 } 192 193 /* Same as double, but INT32_MAX if NaN. */ 194 static inline int32_t parse_float_dist(const float x, const float y) 195 { 196 uint32_t m32; 197 int32_t i32; 198 199 i32 = parse_float_compare(x, y); 200 /* Absolute integer value of compare. */ 201 m32 = (uint32_t)-(i32 < 0); 202 return (int32_t)(((uint32_t)i32 + m32) ^ m32); 203 } 204 205 /* 206 * Returns 1 if no value is NaN, and the difference is at most one ULP (1 bit), and the 207 * sign is the same, and 0 otherwise. 208 */ 209 static inline int parse_double_is_equal(const double x, const double y) 210 { 211 return parse_double_dist(x, y) >> 1 == 0; 212 } 213 214 /* Same as double, but at lower precision. */ 215 static inline int parse_float_is_equal(const float x, const float y) 216 { 217 return parse_float_dist(x, y) >> 1 == 0; 218 } 219 220 #include "pdiagnostic_pop.h" 221 222 #ifdef __cplusplus 223 } 224 #endif 225 226 #endif /* PPARSEFP_H */