diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 891e78a..dfc0976 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -27,6 +27,7 @@ THE SOFTWARE. #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED +#include #include #include #include @@ -999,6 +1000,27 @@ check_signed_range(bool negative, U value, const std::string& text) SignedCheck::is_signed>()(negative, value, text); } +template +void +floating_point_parser(const std::string& text, T& value, T (*parser)(const char*, char**)) +{ + if (text.empty()) + { + throw_or_mimic(text); + } + + char* end = nullptr; + errno = 0; + const auto result = parser(text.c_str(), &end); + + if (end == text.c_str() || errno == ERANGE) + { + throw_or_mimic(text); + } + + value = result; +} + } // namespace detail template @@ -1132,6 +1154,27 @@ parse_value(const std::string& text, std::string& value) value = text; } +inline +void +parse_value(const std::string& text, float& value) +{ + detail::floating_point_parser(text, value, std::strtof); +} + +inline +void +parse_value(const std::string& text, double& value) +{ + detail::floating_point_parser(text, value, std::strtod); +} + +inline +void +parse_value(const std::string& text, long double& value) +{ + detail::floating_point_parser(text, value, std::strtold); +} + // The fallback parser. It uses the stringstream parser to parse all types // that have not been overloaded explicitly. It has to be placed in the // source code before all other more specialized templates. diff --git a/test/options.cpp b/test/options.cpp index fb495ed..cda65c1 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include #include #include @@ -977,6 +978,52 @@ TEST_CASE("Floats", "[options]") CHECK(positional[3] == -1.5e6); } +TEST_CASE("Special floating point values", "[options]") +{ + cxxopts::Options options("parses_special_floats", "parses special floating point values"); + options.add_options() + ("double", "Double precision", cxxopts::value()) + ("long-double", "Extended precision", cxxopts::value()) + ("positional", "Floats", cxxopts::value>()); + + Argv av({"special_floats", "--double", "inf", "--long-double", "infinity", "--", "-inf", "nan"}); + + auto** argv = av.argv(); + auto argc = av.argc(); + + options.parse_positional("positional"); + auto result = options.parse(argc, argv); + + auto parsed_double = result["double"].as(); + CHECK(std::isinf(parsed_double)); + CHECK(!std::signbit(parsed_double)); + + auto parsed_long_double = result["long-double"].as(); + CHECK(std::isinf(parsed_long_double)); + CHECK(!std::signbit(parsed_long_double)); + + auto& positional = result["positional"].as>(); + REQUIRE(positional.size() == 2); + CHECK(std::isinf(positional[0])); + CHECK(std::signbit(positional[0])); + CHECK(std::isnan(positional[1])); +} + +TEST_CASE("Invalid floats", "[options]") +{ + cxxopts::Options options("invalid_floats", "rejects invalid floats"); + options.add_options() + ("positional", "Floats", cxxopts::value>()); + + Argv av({"floats", "--", "abc"}); + + auto** argv = av.argv(); + auto argc = av.argc(); + + options.parse_positional("positional"); + CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type); +} + TEST_CASE("Invalid integers", "[integer]") { cxxopts::Options options("invalid_integers", "rejects invalid integers"); options.add_options()