fix: parse special floating point values

This commit is contained in:
lilong 2026-05-29 13:26:55 +08:00
parent a3a21b31ef
commit 35286c2af3
2 changed files with 90 additions and 0 deletions

View File

@ -27,6 +27,7 @@ THE SOFTWARE.
#ifndef CXXOPTS_HPP_INCLUDED
#define CXXOPTS_HPP_INCLUDED
#include <cerrno>
#include <cstdint>
#include <cstdlib>
#include <cstring>
@ -999,6 +1000,27 @@ check_signed_range(bool negative, U value, const std::string& text)
SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);
}
template <typename T>
void
floating_point_parser(const std::string& text, T& value, T (*parser)(const char*, char**))
{
if (text.empty())
{
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
char* end = nullptr;
errno = 0;
const auto result = parser(text.c_str(), &end);
if (end == text.c_str() || errno == ERANGE)
{
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
value = result;
}
} // namespace detail
template <typename R, typename T>
@ -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.

View File

@ -1,4 +1,5 @@
#include "catch.hpp"
#include <cmath>
#include <iostream>
#include <initializer_list>
@ -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<double>())
("long-double", "Extended precision", cxxopts::value<long double>())
("positional", "Floats", cxxopts::value<std::vector<float>>());
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<double>();
CHECK(std::isinf(parsed_double));
CHECK(!std::signbit(parsed_double));
auto parsed_long_double = result["long-double"].as<long double>();
CHECK(std::isinf(parsed_long_double));
CHECK(!std::signbit(parsed_long_double));
auto& positional = result["positional"].as<std::vector<float>>();
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<std::vector<float>>());
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()