damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 */