feat: Allow parsing Inf,NaN in float, double and long double

This commit is contained in:
Nitin Kumar 2026-03-14 22:47:04 +05:30
parent 25f319275e
commit c834684f53
2 changed files with 184 additions and 0 deletions

View File

@ -580,6 +580,16 @@ class incorrect_argument_type : public parsing
)
{
}
explicit incorrect_argument_type
(
const std::string& arg,
const std::string& message
)
: parsing(
"Argument " + LQUOTE + arg + RQUOTE + " failed to parse becuause " + message
)
{
}
};
} // namespace exceptions
@ -604,6 +614,25 @@ void throw_or_mimic(const std::string& text)
#endif
}
template <typename T>
void throw_or_mimic(const std::string& text, const std::string& msg)
{
static_assert(std::is_base_of<std::exception, T>::value,
"throw_or_mimic only works on std::exception and "
"deriving classes");
#ifndef CXXOPTS_NO_EXCEPTIONS
// If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
throw T{text, msg};
#else
// Otherwise manually instantiate the exception, print what() to stderr,
// and exit
T exception{text, msg};
std::cerr << exception.what() << std::endl;
std::exit(EXIT_FAILURE);
#endif
}
using OptionNames = std::vector<std::string>;
namespace values {
@ -1120,6 +1149,60 @@ parse_value(const std::string& text, bool& value)
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
inline
void
fparse(const std::string& text, float& value){
value = std::stof(text);
}
inline
void
fparse(const std::string& text, double& value){
value = std::stod(text);
}
inline
void
fparse(const std::string& text, long double& value){
value = std::stold(text);
}
template<typename T>
inline
void
fparse_with_exception(const std::string& text, T& value)
{
try{
fparse(text, value);
} catch(const std::out_of_range&) {
throw_or_mimic<exceptions::incorrect_argument_type>(text, "out of range");
} catch(const std::invalid_argument&) {
throw_or_mimic<exceptions::incorrect_argument_type>(text, "invalid arguement");
}
}
inline
void
parse_value(const std::string& text, float& value)
{
fparse_with_exception<float>(text, value);
}
inline
void
parse_value(const std::string& text, double& value)
{
fparse_with_exception<double>(text, value);
}
inline
void
parse_value(const std::string& text, long double& value)
{
fparse_with_exception<long double>(text, value);
}
inline
void
parse_value(const std::string& text, std::string& value)

View File

@ -1,5 +1,6 @@
#include "catch.hpp"
#include <iostream>
#include <cmath>
#include <initializer_list>
@ -934,6 +935,106 @@ TEST_CASE("Floats", "[options]")
CHECK(positional[3] == -1.5e6);
}
TEST_CASE("Floating special values and parse failures", "[options]")
{
cxxopts::Options options("floating_special_values", "floating special values and parse failures");
options.add_options()
("f,float", "Float", cxxopts::value<float>())
("d,double", "Double", cxxopts::value<double>())
("l,long-double", "Long double", cxxopts::value<long double>());
enum class expected_result {
pos_inf,
neg_inf,
nan,
parse_error
};
struct testcase {
std::string name;
Argv argv;
expected_result expected;
} tests[] = {
{
"parses infinity",
Argv{"floating_special_values", "--float", "infinity", "--double", "infinity", "--long-double", "infinity"},
expected_result::pos_inf,
},
{
"parses negative inf",
Argv{"floating_special_values", "--float", "-INF", "--double", "-inf", "--long-double", "-Inf"},
expected_result::neg_inf,
},
{
"parses nan",
Argv{"floating_special_values", "--float", "nan", "--double", "nan", "--long-double", "nan"},
expected_result::nan,
},
{
"rejects invalid token for float",
Argv{"floating_special_values", "--float", "adsfas"},
expected_result::parse_error,
},
{
"rejects invalid token for double",
Argv{"floating_special_values", "--double", "adsfas"},
expected_result::parse_error,
},
{
"rejects invalid token for long double",
Argv{"floating_special_values", "--long-double", "adsfas"},
expected_result::parse_error,
},
{
"rejects out of range token for float",
Argv{"floating_special_values", "--float", "1000000000000000000000000000000000000000"},
expected_result::parse_error,
},
};
for (const auto& tc : tests) {
SECTION(tc.name) {
if (tc.expected == expected_result::parse_error) {
CHECK_THROWS_AS(options.parse(tc.argv.argc(), tc.argv.argv()), cxxopts::exceptions::incorrect_argument_type);
continue;
}
auto result = options.parse(tc.argv.argc(), tc.argv.argv());
auto check_value = [&](long double value) {
switch (tc.expected) {
case expected_result::pos_inf:
CHECK(std::isinf(value));
CHECK(value > 0.0L);
break;
case expected_result::neg_inf:
CHECK(std::isinf(value));
CHECK(value < 0.0L);
break;
case expected_result::nan:
CHECK(std::isnan(value));
break;
case expected_result::parse_error:
// Handled above
break;
}
};
check_value(result["float"].as<float>());
check_value(result["double"].as<double>());
check_value(result["long-double"].as<long double>());
}
}
SECTION("parses finite values") {
Argv argv{"floating_special_values", "--float", "1.25", "--double", "-2.5", "--long-double", "3.75"};
auto result = options.parse(argv.argc(), argv.argv());
CHECK(result["float"].as<float>() == 1.25f);
CHECK(result["double"].as<double>() == -2.5);
CHECK(result["long-double"].as<long double>() == 3.75L);
}
}
TEST_CASE("Invalid integers", "[integer]") {
cxxopts::Options options("invalid_integers", "rejects invalid integers");
options.add_options()