diff --git a/README.md b/README.md index 51018e7..41ee661 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,28 @@ if (dconv::atod (probe, value, significand, exponent, negative, ec) == -1 && ec It returns `0` and the converted value for a real number, or `-1` with `Errc::NotDouble` for an integer without fraction nor exponent whose magnitude fits in 64 bits, or `-1` with `Errc::InvalidNumber` when parsing fails. `significand`, `exponent` and `negative` hold what was read until the end or the error. +By default `atod` reads the general format of `std::from_chars`: an optional `-`, digits with an optional point (leading zeros, `1.` and `.5` are accepted), an optional exponent, and `inf`, `infinity` or `nan` in any case. +Leading whitespaces, a leading `+` and hexadecimal numbers are rejected. A value too large for a double is read as infinity. + +A conversion mode, given as a template argument, restricts what is accepted: + +```cpp +#include + +double value; +const char* end = dconv::atod ("-2.22507e-308", value); +``` + +| Mode | Accepted | +| :--- | :--- | +| `AtodMode::General` | general format (default). | +| `AtodMode::Json` | JSON numbers only (RFC 8259): no leading zeros, digits on both sides of the point, no `inf` nor `nan`. | +| `AtodMode::ParseNanAndInf` | JSON numbers, `inf`, `infinity` and `nan`. | +| `AtodMode::ParseRelaxedNumber` | JSON numbers, leading zeros and missing digits around the point. | + +Modes are bit flags: `AtodMode::General` is `AtodMode::ParseNanAndInf | AtodMode::ParseRelaxedNumber`. +In general format, an exponent mark not followed by digits (`1e+`) or a truncated `infinity` (`infinit`) is left unread when parsing a buffer; a stream cannot be rewound, so the conversion fails there. + `dtoa` needs a buffer of at least 25 bytes (26 bytes for `grisu::dtoa`) and returns the end of the written characters. `atod` returns the end of the parsed characters, or `nullptr` on failure. The probe and stream overloads return `false` on failure and stop right after the number on success. diff --git a/VERSION b/VERSION index 0495c4a..227cea2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.3 +2.0.0 diff --git a/dconv/include/dconv/atod.hpp b/dconv/include/dconv/atod.hpp index 9f51ac2..530b1f9 100644 --- a/dconv/include/dconv/atod.hpp +++ b/dconv/include/dconv/atod.hpp @@ -45,6 +45,93 @@ namespace dconv { + /** + * @brief string to double conversion mode. + */ + enum class AtodMode : uint32_t + { + Json = 0, /**< JSON number grammar. */ + ParseNanAndInf = 1L << 0, /**< parse nan, inf and infinity. */ + ParseRelaxedNumber = 1L << 1, /**< parse leading zeros and missing digits around the point. */ + General = ParseNanAndInf | ParseRelaxedNumber, /**< general format. */ + }; + + /** + * @brief perform binary AND on AtodMode. + * @param a bitset. + * @param b other bitset. + * @return bitset result of binary AND on AtodMode. + */ + constexpr AtodMode operator& (AtodMode a, AtodMode b) noexcept + { + return AtodMode (static_cast (a) & static_cast (b)); + } + + /** + * @brief perform binary OR on AtodMode. + * @param a bitset. + * @param b other bitset. + * @return bitset result of binary OR on AtodMode. + */ + constexpr AtodMode operator| (AtodMode a, AtodMode b) noexcept + { + return AtodMode (static_cast (a) | static_cast (b)); + } + + /** + * @brief perform binary XOR on AtodMode. + * @param a bitset. + * @param b other bitset. + * @return bitset result of binary XOR on AtodMode. + */ + constexpr AtodMode operator^ (AtodMode a, AtodMode b) noexcept + { + return AtodMode (static_cast (a) ^ static_cast (b)); + } + + /** + * @brief perform binary NOT on AtodMode. + * @param a bitset. + * @return bitset result of binary NOT on AtodMode. + */ + constexpr AtodMode operator~(AtodMode a) noexcept + { + return AtodMode (~static_cast (a)); + } + + /** + * @brief perform binary AND assignment on AtodMode. + * @param a bitset. + * @param b other bitset. + * @return reference to updated bitset. + */ + constexpr const AtodMode& operator&= (AtodMode& a, AtodMode b) noexcept + { + return a = a & b; + } + + /** + * @brief perform binary OR assignment on AtodMode. + * @param a bitset. + * @param b other bitset. + * @return reference to updated bitset. + */ + constexpr const AtodMode& operator|= (AtodMode& a, AtodMode b) noexcept + { + return a = a | b; + } + + /** + * @brief perform binary XOR assignment on AtodMode. + * @param a bitset. + * @param b other bitset. + * @return reference to updated bitset. + */ + constexpr const AtodMode& operator^= (AtodMode& a, AtodMode b) noexcept + { + return a = a ^ b; + } + namespace details { inline constexpr bool isDigit (char c) noexcept @@ -114,9 +201,9 @@ namespace dconv * @param pending integer digit already read beyond the significand, or -1. * @return true on success, false otherwise. */ - template - __attribute__ ((cold, noinline)) bool atodLong (Probe& probe, bool negative, uint64_t significand, - int64_t& scale, bool fraction, double& value, int pending = -1) + template + DCONV_COLD bool atodLong (Probe& probe, bool negative, uint64_t significand, int64_t& scale, bool fraction, + double& value, int pending = -1) { static constexpr int maxDigits = 800; char text[maxDigits + 32]; @@ -148,7 +235,7 @@ namespace dconv if (probe.getIf ('.')) { - if (DCONV_UNLIKELY (!isDigit (probe.peek ()))) + if (DCONV_UNLIKELY (!Relaxed && !isDigit (probe.peek ()))) { return false; } @@ -169,9 +256,21 @@ namespace dconv } int64_t exponent = 0; - if (!readExponent (probe, exponent)) + if constexpr (Relaxed && std::is_same_v) { - return false; + const char* mark = probe.tell (); + if (!readExponent (probe, exponent)) + { + probe.seek (mark); + exponent = 0; + } + } + else + { + if (!readExponent (probe, exponent)) + { + return false; + } } scale += exponent; @@ -195,7 +294,7 @@ namespace dconv } /** - * @brief string to double conversion that stops on integers. + * @brief string to double conversion in general format that stops on integers. * @param probe probe to read from. * @param value converted value. * @param significand integer value when the error is NotDouble, first significant digits read otherwise. @@ -203,14 +302,10 @@ namespace dconv * @param negative sign read. * @return Errc::Success on success, the error otherwise. */ - template > - inline Errc atod (Probe& probe, double& value, uint64_t& significand, int64_t& exponent, bool& negative) + template + inline Errc atodRelaxed (Probe& probe, double& value, uint64_t& significand, int64_t& exponent, bool& negative) { - const char* beg = nullptr; - if constexpr (Rewind) - { - beg = probe.tell (); - } + constexpr bool special = (Mode & AtodMode::ParseNanAndInf) != AtodMode::Json; significand = 0; exponent = 0; @@ -219,63 +314,70 @@ namespace dconv int64_t scale = 0; size_t digits = 0; - if (probe.getIf ('0')) + const bool zero = probe.getIf ('0'); + if (zero) { - if (DCONV_UNLIKELY (isDigit (probe.peek ()))) + while (probe.getIf ('0')) { - return Errc::InvalidNumber; } } - else if (DCONV_LIKELY (isDigit (probe.peek ()))) + + if (isDigit (probe.peek ())) { - if constexpr (Rewind) + digits = probe.readDigits (u, 19); + if (DCONV_UNLIKELY (isDigit (probe.peek ()))) { - digits = probe.readDigits (u); + significand = u; + const int pending = probe.get () - '0'; + const int next = probe.peek (); + if (!isDigit (next) && (next != '.') && (next != 'e') && (next != 'E') && + (u <= (std::numeric_limits::max () - pending) / 10)) + { + significand = (u * 10) + pending; + return Errc::NotDouble; + } + if (!atodLong (probe, negative, u, exponent, false, value, pending)) + { + return Errc::InvalidNumber; + } + return Errc::Success; } - else + significand = u; + } + else if (!zero && (probe.peek () != '.')) + { + if (special && probe.getIfNoCase ('i') && probe.getIfNoCase ('n') && probe.getIfNoCase ('f')) { - digits = probe.readDigits (u, 19); - if (DCONV_UNLIKELY (isDigit (probe.peek ()))) + if constexpr (std::is_same_v) { - significand = u; - const int pending = probe.get () - '0'; - const int next = probe.peek (); - if (!isDigit (next) && (next != '.') && (next != 'e') && (next != 'E') && - (u <= (std::numeric_limits::max () - pending) / 10)) + const char* mark = probe.tell (); + if (!(probe.getIfNoCase ('i') && probe.getIfNoCase ('n') && probe.getIfNoCase ('i') && + probe.getIfNoCase ('t') && probe.getIfNoCase ('y'))) { - significand = (u * 10) + pending; - return Errc::NotDouble; + probe.seek (mark); } - if (!atodLong (probe, negative, u, exponent, false, value, pending)) + } + else + { + if (probe.getIfNoCase ('i')) { - return Errc::InvalidNumber; + if (!(probe.getIfNoCase ('n') && probe.getIfNoCase ('i') && probe.getIfNoCase ('t') && + probe.getIfNoCase ('y'))) + { + return Errc::InvalidNumber; + } } - return Errc::Success; } + value = + negative ? -std::numeric_limits::infinity () : std::numeric_limits::infinity (); + return Errc::Success; } - significand = u; - } - else if (probe.getIfNoCase ('i') && probe.getIfNoCase ('n') && probe.getIfNoCase ('f')) - { - if (probe.getIfNoCase ('i')) + else if (special && probe.getIfNoCase ('n') && probe.getIfNoCase ('a') && probe.getIfNoCase ('n')) { - if (!(probe.getIfNoCase ('n') && probe.getIfNoCase ('i') && probe.getIfNoCase ('t') && - probe.getIfNoCase ('y'))) - { - return Errc::InvalidNumber; - } + value = negative ? -std::numeric_limits::quiet_NaN () + : std::numeric_limits::quiet_NaN (); + return Errc::Success; } - value = negative ? -std::numeric_limits::infinity () : std::numeric_limits::infinity (); - return Errc::Success; - } - else if (probe.getIfNoCase ('n') && probe.getIfNoCase ('a') && probe.getIfNoCase ('n')) - { - value = - negative ? -std::numeric_limits::quiet_NaN () : std::numeric_limits::quiet_NaN (); - return Errc::Success; - } - else - { return Errc::InvalidNumber; } @@ -283,7 +385,7 @@ namespace dconv if (probe.getIf ('.')) { real = true; - if (DCONV_UNLIKELY (!isDigit (probe.peek ()))) + if (DCONV_UNLIKELY (!isDigit (probe.peek ())) && !zero && (digits == 0)) { return Errc::InvalidNumber; } @@ -296,42 +398,44 @@ namespace dconv } } - size_t count = probe.readDigits (u, Rewind ? std::numeric_limits::max () : 19 - digits); + size_t count = probe.readDigits (u, 19 - digits); significand = u; digits += count; scale -= static_cast (count); exponent = scale; - if constexpr (!Rewind) + if (DCONV_UNLIKELY (isDigit (probe.peek ()))) { - if (DCONV_UNLIKELY (isDigit (probe.peek ()))) + if (!atodLong (probe, negative, u, exponent, true, value)) { - if (!atodLong (probe, negative, u, exponent, true, value)) - { - return Errc::InvalidNumber; - } - return Errc::Success; + return Errc::InvalidNumber; } + return Errc::Success; } } - if constexpr (Rewind) + bool scientific = (probe.peek () == 'e') || (probe.peek () == 'E'); + int64_t power = 0; + + if constexpr (std::is_same_v) { - if (DCONV_UNLIKELY (digits > 19)) + const char* mark = probe.tell (); + if (DCONV_UNLIKELY (!readExponent (probe, power))) { - probe.seek (beg); - return atod (probe, value, significand, exponent, negative); + probe.seek (mark); + power = 0; + scientific = false; } } - - real |= (probe.peek () == 'e') || (probe.peek () == 'E'); - int64_t power = 0; - - if (!readExponent (probe, power)) + else { - return Errc::InvalidNumber; + if (!readExponent (probe, power)) + { + return Errc::InvalidNumber; + } } + real |= scientific; if (!real) { return Errc::NotDouble; @@ -343,19 +447,246 @@ namespace dconv return Errc::Success; } + /** + * @brief parse again a number the fast path does not handle. + * @param probe probe to read from, positioned on the start of the number. + * @param value converted value. + * @param significand integer value when the error is NotDouble, first significant digits read otherwise. + * @param exponent decimal exponent read. + * @param negative sign read. + * @return Errc::Success on success, the error otherwise. + */ + template + DCONV_COLD Errc atodRewind (Probe& probe, double& value, uint64_t& significand, int64_t& exponent, + bool& negative); + + /** + * @brief string to double conversion that stops on integers. + * @param probe probe to read from. + * @param value converted value. + * @param significand integer value when the error is NotDouble, first significant digits read otherwise. + * @param exponent decimal exponent read. + * @param negative sign read. + * @return Errc::Success on success, the error otherwise. + */ + template > + inline Errc atod (Probe& probe, double& value, uint64_t& significand, int64_t& exponent, bool& negative) + { + constexpr bool relaxed = (Mode & AtodMode::ParseRelaxedNumber) != AtodMode::Json; + + if constexpr (relaxed && !Rewind) + { + return atodRelaxed (probe, value, significand, exponent, negative); + } + + const char* beg = nullptr; + if constexpr (Rewind) + { + beg = probe.tell (); + } + + do + { + significand = 0; + exponent = 0; + negative = probe.getIf ('-'); + uint64_t u = 0; + int64_t scale = 0; + size_t digits = 0; + + if (probe.getIf ('0')) + { + if (DCONV_UNLIKELY (isDigit (probe.peek ()))) + { + if constexpr (relaxed && Rewind) + { + break; + } + else + { + return Errc::InvalidNumber; + } + } + } + else if (DCONV_LIKELY (isDigit (probe.peek ()))) + { + if constexpr (Rewind) + { + digits = probe.readDigits (u); + } + else + { + digits = probe.readDigits (u, 19); + if (DCONV_UNLIKELY (isDigit (probe.peek ()))) + { + significand = u; + const int pending = probe.get () - '0'; + const int next = probe.peek (); + if (!isDigit (next) && (next != '.') && (next != 'e') && (next != 'E') && + (u <= (std::numeric_limits::max () - pending) / 10)) + { + significand = (u * 10) + pending; + return Errc::NotDouble; + } + if (!atodLong (probe, negative, u, exponent, false, value, pending)) + { + return Errc::InvalidNumber; + } + return Errc::Success; + } + } + significand = u; + } + else if (((Mode & AtodMode::ParseNanAndInf) != AtodMode::Json) && probe.getIfNoCase ('i') && + probe.getIfNoCase ('n') && probe.getIfNoCase ('f')) + { + if (probe.getIfNoCase ('i')) + { + if (!(probe.getIfNoCase ('n') && probe.getIfNoCase ('i') && probe.getIfNoCase ('t') && + probe.getIfNoCase ('y'))) + { + if constexpr (relaxed && Rewind) + { + break; + } + else + { + return Errc::InvalidNumber; + } + } + } + value = + negative ? -std::numeric_limits::infinity () : std::numeric_limits::infinity (); + return Errc::Success; + } + else if (((Mode & AtodMode::ParseNanAndInf) != AtodMode::Json) && probe.getIfNoCase ('n') && + probe.getIfNoCase ('a') && probe.getIfNoCase ('n')) + { + value = negative ? -std::numeric_limits::quiet_NaN () + : std::numeric_limits::quiet_NaN (); + return Errc::Success; + } + else + { + if constexpr (relaxed && Rewind) + { + break; + } + else + { + return Errc::InvalidNumber; + } + } + + bool real = false; + if (probe.getIf ('.')) + { + real = true; + if (DCONV_UNLIKELY (!isDigit (probe.peek ()))) + { + if constexpr (relaxed && Rewind) + { + break; + } + else + { + return Errc::InvalidNumber; + } + } + + if (digits == 0) + { + while (probe.getIf ('0')) + { + --scale; + } + } + + size_t count = probe.readDigits (u, Rewind ? std::numeric_limits::max () : 19 - digits); + significand = u; + digits += count; + scale -= static_cast (count); + exponent = scale; + + if constexpr (!Rewind) + { + if (DCONV_UNLIKELY (isDigit (probe.peek ()))) + { + if (!atodLong (probe, negative, u, exponent, true, value)) + { + return Errc::InvalidNumber; + } + return Errc::Success; + } + } + } + + if constexpr (Rewind) + { + if (DCONV_UNLIKELY (digits > 19)) + { + break; + } + } + + real |= (probe.peek () == 'e') || (probe.peek () == 'E'); + int64_t power = 0; + + if (!readExponent (probe, power)) + { + if constexpr (relaxed && Rewind) + { + break; + } + else + { + return Errc::InvalidNumber; + } + } + + if (!real) + { + return Errc::NotDouble; + } + + exponent = scale + power; + value = eisellemire::compute (u, exponent); + value = negative ? -value : value; + return Errc::Success; + } + while (false); + + if constexpr (Rewind) + { + probe.seek (beg); + return atodRewind (probe, value, significand, exponent, negative); + } + else + { + return Errc::InvalidNumber; // LCOV_EXCL_LINE + } + } + + template + DCONV_COLD Errc atodRewind (Probe& probe, double& value, uint64_t& significand, int64_t& exponent, + bool& negative) + { + return atod (probe, value, significand, exponent, negative); + } + /** * @brief string to double conversion. * @param probe probe to read from. * @param value converted value. * @return true on success, false otherwise. */ - template + template inline bool atod (Probe& probe, double& value) { uint64_t significand; int64_t exponent; bool negative; - const Errc err = atod (probe, value, significand, exponent, negative); + const Errc err = atod (probe, value, significand, exponent, negative); if (err == Errc::NotDouble) { value = negative ? -static_cast (significand) : static_cast (significand); @@ -375,10 +706,11 @@ namespace dconv * @param ec error code set on failure. * @return 0 on success, -1 on failure. */ + template inline int atod (StreamProbe& probe, double& value, uint64_t& significand, int64_t& exponent, bool& negative, std::error_code& ec) { - const Errc err = details::atod (probe, value, significand, exponent, negative); + const Errc err = details::atod (probe, value, significand, exponent, negative); if (err != Errc::Success) { ec = make_error_code (err); @@ -393,9 +725,10 @@ namespace dconv * @param value converted value. * @return true on success, false otherwise. */ + template inline bool atod (StreamProbe& probe, double& value) { - return details::atod (probe, value); + return details::atod (probe, value); } /** @@ -404,10 +737,11 @@ namespace dconv * @param value converted value. * @return true on success, false otherwise. */ + template inline bool atod (std::istream& in, double& value) { StreamProbe probe (in); - return details::atod (probe, value); + return details::atod (probe, value); } /** @@ -420,10 +754,11 @@ namespace dconv * @param ec error code set on failure. * @return 0 on success, -1 on failure. */ + template inline int atod (StringProbe& probe, double& value, uint64_t& significand, int64_t& exponent, bool& negative, std::error_code& ec) { - const Errc err = details::atod (probe, value, significand, exponent, negative); + const Errc err = details::atod (probe, value, significand, exponent, negative); if (err != Errc::Success) { ec = make_error_code (err); @@ -438,9 +773,10 @@ namespace dconv * @param value converted value. * @return true on success, false otherwise. */ + template inline bool atod (StringProbe& probe, double& value) { - return details::atod (probe, value); + return details::atod (probe, value); } /** @@ -449,10 +785,11 @@ namespace dconv * @param value converted value. * @return end position on success, nullptr on failure. */ + template inline const char* atod (const char* str, double& value) { StringProbe probe (str); - return details::atod (probe, value) ? probe.tell () : nullptr; + return details::atod (probe, value) ? probe.tell () : nullptr; } /** @@ -462,10 +799,11 @@ namespace dconv * @param value converted value. * @return end position on success, nullptr on failure. */ + template inline const char* atod (const char* str, size_t length, double& value) { StringProbe probe (str, length); - return details::atod (probe, value) ? probe.tell () : nullptr; + return details::atod (probe, value) ? probe.tell () : nullptr; } /** @@ -475,10 +813,11 @@ namespace dconv * @param value converted value. * @return end position on success, nullptr on failure. */ + template inline const char* atod (const char* first, const char* last, double& value) { StringProbe probe (first, last); - return details::atod (probe, value) ? probe.tell () : nullptr; + return details::atod (probe, value) ? probe.tell () : nullptr; } /** @@ -487,10 +826,11 @@ namespace dconv * @param value converted value. * @return end position on success, nullptr on failure. */ + template inline const char* atod (std::string_view str, double& value) { StringProbe probe (str.data (), str.size ()); - return details::atod (probe, value) ? probe.tell () : nullptr; + return details::atod (probe, value) ? probe.tell () : nullptr; } /** @@ -499,6 +839,7 @@ namespace dconv * @param value converted value. * @return end position on success, nullptr on failure. */ + template const char* atod (std::string&& str, double& value) = delete; } diff --git a/dconv/include/dconv/probe.hpp b/dconv/include/dconv/probe.hpp index b9090dc..93535aa 100644 --- a/dconv/include/dconv/probe.hpp +++ b/dconv/include/dconv/probe.hpp @@ -36,6 +36,7 @@ #define DCONV_LIKELY(x) __builtin_expect ((x), 1) #define DCONV_UNLIKELY(x) __builtin_expect ((x), 0) +#define DCONV_COLD __attribute__ ((cold, noinline)) namespace dconv { diff --git a/dconv/tests/atod_test.cpp b/dconv/tests/atod_test.cpp index 57adee8..8a3899b 100644 --- a/dconv/tests/atod_test.cpp +++ b/dconv/tests/atod_test.cpp @@ -71,19 +71,29 @@ TEST (atod, atod) { double value = 0.0; - EXPECT_EQ (dconv::atod ("003", value), nullptr); + EXPECT_EQ (dconv::atod ("003", value), nullptr); + ASSERT_NE (dconv::atod ("003", value), nullptr); + EXPECT_EQ (value, 3.0); - EXPECT_EQ (dconv::atod ("Infinaty", value), nullptr); + EXPECT_EQ (dconv::atod < dconv::AtodMode::Json | dconv::AtodMode::ParseNanAndInf > ("Infinaty", value), nullptr); + ASSERT_NE (dconv::atod ("Infinaty", value), nullptr); + EXPECT_TRUE (!std::signbit (value) && std::isinf (value)); - EXPECT_EQ (dconv::atod ("-Infinaty", value), nullptr); + EXPECT_EQ (dconv::atod < dconv::AtodMode::Json | dconv::AtodMode::ParseNanAndInf > ("-Infinaty", value), nullptr); + ASSERT_NE (dconv::atod ("-Infinaty", value), nullptr); + EXPECT_TRUE (std::signbit (value) && std::isinf (value)); EXPECT_EQ (dconv::atod ("not valid", value), nullptr); - EXPECT_EQ (dconv::atod ("1ee+10", value), nullptr); + EXPECT_EQ (dconv::atod ("1ee+10", value), nullptr); - EXPECT_EQ (dconv::atod ("1ee+10", value), nullptr); + EXPECT_EQ (dconv::atod ("1ee+10", value), nullptr); + ASSERT_NE (dconv::atod ("1ee+10", value), nullptr); + EXPECT_EQ (value, 1.0); - EXPECT_EQ (dconv::atod ("1.e+10", value), nullptr); + EXPECT_EQ (dconv::atod ("1.e+10", value), nullptr); + ASSERT_NE (dconv::atod ("1.e+10", value), nullptr); + EXPECT_EQ (value, 1e10); ASSERT_NE (dconv::atod ("infinity", value), nullptr); EXPECT_TRUE (!std::signbit (value) && std::isinf (value)); @@ -429,7 +439,13 @@ TEST (atod, atod) std::istringstream longInvalid ("12345678901234567890.x"); dconv::StreamProbe longInvalidProbe (longInvalid); - ASSERT_FALSE (dconv::atod (longInvalidProbe, value)); + ASSERT_FALSE (dconv::atod (longInvalidProbe, value)); + + std::istringstream longPoint ("12345678901234567890.x"); + dconv::StreamProbe longPointProbe (longPoint); + ASSERT_TRUE (dconv::atod (longPointProbe, value)); + EXPECT_EQ (value, 12345678901234567890.0); + EXPECT_EQ (longPointProbe.peek (), 'x'); std::istringstream longExponent ("12345678901234567890e+x"); ASSERT_FALSE (dconv::atod (longExponent, value)); @@ -449,9 +465,18 @@ TEST (atod, atod) EXPECT_EQ (value, 0.25); EXPECT_EQ (in.peek (), ' '); - EXPECT_EQ (dconv::atod ("12345678901234567890.", value), nullptr); - EXPECT_EQ (dconv::atod ("12345678901234567890e", value), nullptr); - EXPECT_EQ (dconv::atod ("0.12345678901234567890e+", value), nullptr); + EXPECT_EQ (dconv::atod ("12345678901234567890.", value), nullptr); + EXPECT_EQ (dconv::atod ("12345678901234567890e", value), nullptr); + EXPECT_EQ (dconv::atod ("0.12345678901234567890e+", value), nullptr); + + ASSERT_NE (dconv::atod ("12345678901234567890.", value), nullptr); + EXPECT_EQ (value, 12345678901234567890.0); + + ASSERT_NE (dconv::atod ("12345678901234567890e", value), nullptr); + EXPECT_EQ (value, 12345678901234567890.0); + + ASSERT_NE (dconv::atod ("0.12345678901234567890e+", value), nullptr); + EXPECT_EQ (value, 0.12345678901234567890); std::string longest = "9007199254740993." + std::string (900, '0'); ASSERT_NE (dconv::atod (longest.c_str (), value), nullptr); @@ -571,19 +596,35 @@ TEST (atod, atod) str = "01"; dconv::StringProbe leadingZeroProbe (str); - ASSERT_EQ (dconv::atod (leadingZeroProbe, value, significand, exponent, negative, ec), -1); + ASSERT_EQ (dconv::atod (leadingZeroProbe, value, significand, exponent, negative, ec), -1); EXPECT_EQ (ec, dconv::Errc::InvalidNumber); + dconv::StringProbe relaxedZeroProbe (str); + ASSERT_EQ (dconv::atod (relaxedZeroProbe, value, significand, exponent, negative, ec), -1); + EXPECT_EQ (ec, dconv::Errc::NotDouble); + EXPECT_EQ (significand, 1u); + EXPECT_FALSE (negative); + str = "1."; dconv::StringProbe dotProbe (str); - ASSERT_EQ (dconv::atod (dotProbe, value, significand, exponent, negative, ec), -1); + ASSERT_EQ (dconv::atod (dotProbe, value, significand, exponent, negative, ec), -1); EXPECT_EQ (ec, dconv::Errc::InvalidNumber); + dconv::StringProbe relaxedDotProbe (str); + ASSERT_EQ (dconv::atod (relaxedDotProbe, value, significand, exponent, negative, ec), 0); + EXPECT_EQ (value, 1.0); + str = "1e+"; dconv::StringProbe exponentSignProbe (str); - ASSERT_EQ (dconv::atod (exponentSignProbe, value, significand, exponent, negative, ec), -1); + ASSERT_EQ (dconv::atod (exponentSignProbe, value, significand, exponent, negative, ec), -1); EXPECT_EQ (ec, dconv::Errc::InvalidNumber); + dconv::StringProbe relaxedExponentProbe (str); + ASSERT_EQ (dconv::atod (relaxedExponentProbe, value, significand, exponent, negative, ec), -1); + EXPECT_EQ (ec, dconv::Errc::NotDouble); + EXPECT_EQ (significand, 1u); + EXPECT_EQ (relaxedExponentProbe.peek (), 'e'); + str = "-"; dconv::StringProbe signProbe (str); ASSERT_EQ (dconv::atod (signProbe, value, significand, exponent, negative, ec), -1); @@ -591,24 +632,45 @@ TEST (atod, atod) str = "infinit"; dconv::StringProbe infinityProbe (str); - ASSERT_EQ (dconv::atod (infinityProbe, value, significand, exponent, negative, ec), -1); + ASSERT_EQ (dconv::atod < dconv::AtodMode::Json | + dconv::AtodMode::ParseNanAndInf > (infinityProbe, value, significand, exponent, negative, ec), + -1); EXPECT_EQ (ec, dconv::Errc::InvalidNumber); + dconv::StringProbe relaxedInfinityProbe (str); + ASSERT_EQ (dconv::atod (relaxedInfinityProbe, value, significand, exponent, negative, ec), 0); + EXPECT_EQ (value, std::numeric_limits::infinity ()); + EXPECT_EQ (relaxedInfinityProbe.peek (), 'i'); + str = "12345678901234567890."; dconv::StringProbe longDotProbe (str); - ASSERT_EQ (dconv::atod (longDotProbe, value, significand, exponent, negative, ec), -1); + ASSERT_EQ (dconv::atod (longDotProbe, value, significand, exponent, negative, ec), -1); EXPECT_EQ (ec, dconv::Errc::InvalidNumber); + dconv::StringProbe relaxedLongDotProbe (str); + ASSERT_EQ (dconv::atod (relaxedLongDotProbe, value, significand, exponent, negative, ec), 0); + EXPECT_EQ (value, 12345678901234567890.0); + str = "0.12345678901234567890e"; dconv::StringProbe longExponentProbe (str); - ASSERT_EQ (dconv::atod (longExponentProbe, value, significand, exponent, negative, ec), -1); + ASSERT_EQ (dconv::atod (longExponentProbe, value, significand, exponent, negative, ec), -1); EXPECT_EQ (ec, dconv::Errc::InvalidNumber); + dconv::StringProbe relaxedLongExponentProbe (str); + ASSERT_EQ (dconv::atod (relaxedLongExponentProbe, value, significand, exponent, negative, ec), 0); + EXPECT_EQ (value, 0.12345678901234567890); + EXPECT_EQ (relaxedLongExponentProbe.peek (), 'e'); + std::istringstream longDotStreamIn ("12345678901234567890."); dconv::StreamProbe longDotStreamProbe (longDotStreamIn); - ASSERT_EQ (dconv::atod (longDotStreamProbe, value, significand, exponent, negative, ec), -1); + ASSERT_EQ (dconv::atod (longDotStreamProbe, value, significand, exponent, negative, ec), -1); EXPECT_EQ (ec, dconv::Errc::InvalidNumber); + std::istringstream relaxedLongDotStreamIn ("12345678901234567890."); + dconv::StreamProbe relaxedLongDotStreamProbe (relaxedLongDotStreamIn); + ASSERT_EQ (dconv::atod (relaxedLongDotStreamProbe, value, significand, exponent, negative, ec), 0); + EXPECT_EQ (value, 12345678901234567890.0); + str = "-infinity"; dconv::StringProbe infinityValueProbe (str); ASSERT_EQ (dconv::atod (infinityValueProbe, value, significand, exponent, negative, ec), 0); @@ -618,6 +680,236 @@ TEST (atod, atod) dconv::StringProbe nanProbe (str); ASSERT_EQ (dconv::atod (nanProbe, value, significand, exponent, negative, ec), 0); EXPECT_TRUE (std::isnan (value)); + + EXPECT_EQ (dconv::atod ("inf", value), nullptr); + EXPECT_EQ (dconv::atod ("-inf", value), nullptr); + EXPECT_EQ (dconv::atod ("nan", value), nullptr); + EXPECT_EQ (dconv::atod (".5", value), nullptr); + + ASSERT_NE (dconv::atod < dconv::AtodMode::Json | dconv::AtodMode::ParseNanAndInf > ("inf", value), nullptr); + EXPECT_TRUE (!std::signbit (value) && std::isinf (value)); + ASSERT_NE (dconv::atod < dconv::AtodMode::Json | dconv::AtodMode::ParseNanAndInf > ("nan", value), nullptr); + EXPECT_TRUE (std::isnan (value)); + EXPECT_EQ (dconv::atod < dconv::AtodMode::Json | dconv::AtodMode::ParseNanAndInf > ("1.", value), nullptr); + + EXPECT_EQ (dconv::atod ("inf", value), nullptr); + EXPECT_EQ (dconv::atod ("nan", value), nullptr); + ASSERT_NE (dconv::atod ("007", value), nullptr); + EXPECT_EQ (value, 7.0); + + ASSERT_NE (dconv::atod (".5", value), nullptr); + EXPECT_EQ (value, 0.5); + + ASSERT_NE (dconv::atod ("-.5", value), nullptr); + EXPECT_EQ (value, -0.5); + + EXPECT_EQ (dconv::atod (".", value), nullptr); + + EXPECT_EQ (dconv::atod ("-.", value), nullptr); + + ASSERT_NE (dconv::atod ("1.", value), nullptr); + EXPECT_EQ (value, 1.0); + + ASSERT_NE (dconv::atod ("0.", value), nullptr); + EXPECT_EQ (value, 0.0); + + ASSERT_NE (dconv::atod ("1.e5", value), nullptr); + EXPECT_EQ (value, 1e5); + + ASSERT_NE (dconv::atod ("-007.50", value), nullptr); + EXPECT_EQ (value, -7.5); + + str = "00"; + EXPECT_EQ (dconv::atod (str, value), str + 2); + EXPECT_EQ (value, 0.0); + + str = "00.5"; + EXPECT_EQ (dconv::atod (str, value), str + 4); + EXPECT_EQ (value, 0.5); + + str = "00e2"; + EXPECT_EQ (dconv::atod (str, value), str + 4); + EXPECT_EQ (value, 0.0); + + str = "00x"; + EXPECT_EQ (dconv::atod (str, value), str + 2); + EXPECT_EQ (value, 0.0); + + str = "0inf"; + EXPECT_EQ (dconv::atod (str, value), str + 1); + EXPECT_EQ (value, 0.0); + + str = "1e"; + EXPECT_EQ (dconv::atod (str, value), str + 1); + EXPECT_EQ (value, 1.0); + + str = "1.5e-x"; + EXPECT_EQ (dconv::atod (str, value), str + 3); + EXPECT_EQ (value, 1.5); + + str = "infinit"; + EXPECT_EQ (dconv::atod (str, value), str + 3); + EXPECT_EQ (value, std::numeric_limits::infinity ()); + + str = "12345678901234567890e+"; + EXPECT_EQ (dconv::atod (str, value), str + 20); + EXPECT_EQ (value, 12345678901234567890.0); + + str = "0.12345678901234567890123E-"; + EXPECT_EQ (dconv::atod (str, value), str + 25); + EXPECT_EQ (value, 0.12345678901234567890123); + + str = "007.12345678901234567890123"; + EXPECT_EQ (dconv::atod (str, value), str + 27); + EXPECT_EQ (value, 7.12345678901234567890123); + + std::istringstream relaxedZeros ("007"); + ASSERT_TRUE (dconv::atod (relaxedZeros, value)); + EXPECT_EQ (value, 7.0); + + std::istringstream relaxedZeroFraction ("00.5"); + ASSERT_TRUE (dconv::atod (relaxedZeroFraction, value)); + EXPECT_EQ (value, 0.5); + + std::istringstream relaxedPoint (".5"); + ASSERT_TRUE (dconv::atod (relaxedPoint, value)); + EXPECT_EQ (value, 0.5); + + std::istringstream relaxedTrailingPoint ("1."); + ASSERT_TRUE (dconv::atod (relaxedTrailingPoint, value)); + EXPECT_EQ (value, 1.0); + + std::istringstream relaxedLonePoint ("."); + EXPECT_FALSE (dconv::atod (relaxedLonePoint, value)); + + std::istringstream relaxedExponent ("1e+"); + EXPECT_FALSE (dconv::atod (relaxedExponent, value)); + + std::istringstream relaxedInfinity ("infinit"); + EXPECT_FALSE (dconv::atod (relaxedInfinity, value)); + + std::istringstream relaxedInfinityValue ("-Infinity"); + ASSERT_TRUE (dconv::atod (relaxedInfinityValue, value)); + EXPECT_EQ (value, -std::numeric_limits::infinity ()); + + std::istringstream relaxedNan ("nan"); + ASSERT_TRUE (dconv::atod (relaxedNan, value)); + EXPECT_TRUE (std::isnan (value)); + + std::istringstream relaxedInvalid ("x"); + EXPECT_FALSE (dconv::atod (relaxedInvalid, value)); + + std::istringstream relaxedLongFraction ("0.12345678901234567890123"); + ASSERT_TRUE (dconv::atod (relaxedLongFraction, value)); + EXPECT_EQ (value, 0.12345678901234567890123); + + std::istringstream relaxedFractionZeros ("0.005"); + ASSERT_TRUE (dconv::atod (relaxedFractionZeros, value)); + EXPECT_EQ (value, 0.005); + + std::istringstream relaxedLongExponent ("0.12345678901234567890123e+"); + EXPECT_FALSE (dconv::atod (relaxedLongExponent, value)); + + std::istringstream strictMaxIn ("18446744073709551615"); + dconv::StreamProbe strictMaxProbe (strictMaxIn); + ASSERT_EQ (dconv::atod (strictMaxProbe, value, significand, exponent, negative, ec), -1); + EXPECT_EQ (ec, dconv::Errc::NotDouble); + EXPECT_EQ (significand, 18446744073709551615u); + + std::istringstream strictLongIn ("12345678901234567890.5"); + dconv::StreamProbe strictLongProbe (strictLongIn); + ASSERT_EQ (dconv::atod (strictLongProbe, value, significand, exponent, negative, ec), 0); + EXPECT_EQ (value, 12345678901234567890.5); + + std::istringstream strictLongFractionIn ("0.12345678901234567890123"); + dconv::StreamProbe strictLongFractionProbe (strictLongFractionIn); + ASSERT_EQ (dconv::atod (strictLongFractionProbe, value, significand, exponent, negative, ec), + 0); + EXPECT_EQ (value, 0.12345678901234567890123); + + std::istringstream strictStream ("inf"); + EXPECT_FALSE (dconv::atod (strictStream, value)); + + std::istringstream strictZeros ("007"); + dconv::StreamProbe strictZerosProbe (strictZeros); + ASSERT_EQ (dconv::atod (strictZerosProbe, value, significand, exponent, negative, ec), -1); + EXPECT_EQ (ec, dconv::Errc::InvalidNumber); +} + +/** + * @brief and operator test. + */ +TEST (AtodMode, bitwiseAnd) +{ + dconv::AtodMode mode = dconv::AtodMode::General; + + EXPECT_EQ (mode & dconv::AtodMode::ParseNanAndInf, dconv::AtodMode::ParseNanAndInf); + EXPECT_EQ (dconv::AtodMode::Json & mode, dconv::AtodMode::Json); +} + +/** + * @brief or operator test. + */ +TEST (AtodMode, bitwiseOr) +{ + dconv::AtodMode mode = dconv::AtodMode::ParseNanAndInf; + + EXPECT_EQ (mode | dconv::AtodMode::ParseRelaxedNumber, dconv::AtodMode::General); + EXPECT_EQ (mode | dconv::AtodMode::Json, dconv::AtodMode::ParseNanAndInf); +} + +/** + * @brief xor operator test. + */ +TEST (AtodMode, bitwiseXor) +{ + dconv::AtodMode mode = dconv::AtodMode::General; + + EXPECT_EQ (mode ^ dconv::AtodMode::ParseNanAndInf, dconv::AtodMode::ParseRelaxedNumber); + EXPECT_EQ (mode ^ mode, dconv::AtodMode::Json); +} + +/** + * @brief not operator test. + */ +TEST (AtodMode, bitwiseNot) +{ + dconv::AtodMode mode = dconv::AtodMode::ParseNanAndInf; + + EXPECT_EQ (~mode & dconv::AtodMode::General, dconv::AtodMode::ParseRelaxedNumber); +} + +/** + * @brief and assignment operator test. + */ +TEST (AtodMode, bitwiseAndAssign) +{ + dconv::AtodMode mode = dconv::AtodMode::General; + + mode &= dconv::AtodMode::ParseRelaxedNumber; + EXPECT_EQ (mode, dconv::AtodMode::ParseRelaxedNumber); +} + +/** + * @brief or assignment operator test. + */ +TEST (AtodMode, bitwiseOrAssign) +{ + dconv::AtodMode mode = dconv::AtodMode::Json; + + mode |= dconv::AtodMode::ParseNanAndInf; + EXPECT_EQ (mode, dconv::AtodMode::ParseNanAndInf); +} + +/** + * @brief xor assignment operator test. + */ +TEST (AtodMode, bitwiseXorAssign) +{ + dconv::AtodMode mode = dconv::AtodMode::General; + + mode ^= dconv::AtodMode::ParseNanAndInf; + EXPECT_EQ (mode, dconv::AtodMode::ParseRelaxedNumber); } /** diff --git a/doxyfile b/doxyfile index 6939ab0..aecc159 100644 --- a/doxyfile +++ b/doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "dconv" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.2 +PROJECT_NUMBER = 2.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a