From bfee511d78afb329f7873821758a5d5c4f8adbe4 Mon Sep 17 00:00:00 2001 From: Adam Lugowski Date: Tue, 28 Mar 2023 22:13:56 -0700 Subject: [PATCH 1/2] Set errc::result_out_of_range on over/underflow Best-effort values are still returned, such as 0 for underflow and infinity for overflow, but now the returned ec is set to std::errc::result_out_of_range instead of std::errc(). --- README.md | 2 +- include/fast_float/parse_number.h | 4 ++ tests/basictest.cpp | 96 ++++++++++++++++--------------- tests/string_test.cpp | 3 +- 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 63c2c8c..10cd01b 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/char Furthermore, we have the following restrictions: * We only support `float` and `double` types at this time. * We only support the decimal format: we do not support hexadecimal strings. -* For values that are either very large or very small (e.g., `1e9999`), we represent it using the infinity or negative infinity value. +* For values that are either very large or very small (e.g., `1e9999`), we represent it using the infinity or negative infinity value and the returned `ec` is set to `std::errc::result_out_of_range`. We support Visual Studio, macOS, Linux, freeBSD. We support big and little endian. We support 32-bit and 64-bit systems. diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index d16a25d..6e4f6eb 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -214,6 +214,10 @@ from_chars_result from_chars_advanced(const char *first, const char *last, // then we need to go the long way around again. This is very uncommon. if(am.power2 < 0) { am = digit_comp(pns, am); } to_float(pns.negative, am, value); + // Test for over/underflow. + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format::infinite_power()) { + answer.ec = std::errc::result_out_of_range; + } return answer; } diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 3ed10f0..ff55920 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -286,11 +286,11 @@ bool check_file(std::string file_name) { // Parse as 32-bit float float parsed_32; auto fast_float_r32 = fast_float::from_chars(number_string, end_of_string, parsed_32); - if(fast_float_r32.ec != std::errc()) { std::cerr << "parsing failure\n"; return false; } + if(fast_float_r32.ec != std::errc() && fast_float_r32.ec != std::errc::result_out_of_range) {std::cerr << "32-bit fast_float parsing failure for: " + str + "\n"; return false; } // Parse as 64-bit float double parsed_64; auto fast_float_r64 = fast_float::from_chars(number_string, end_of_string, parsed_64); - if(fast_float_r64.ec != std::errc()) { std::cerr << "parsing failure\n"; return false; } + if(fast_float_r64.ec != std::errc() && fast_float_r32.ec != std::errc::result_out_of_range) { std::cerr << "64-bit fast_float parsing failure: " + str + "\n"; return false; } // Convert the floats to unsigned ints. uint32_t float32_parsed; uint64_t float64_parsed; @@ -592,8 +592,9 @@ TEST_CASE("powers_of_ten") { REQUIRE(n < sizeof(buf)); // if false, fails the test and exits double actual; auto result = fast_float::from_chars(buf, buf + 1000, actual); - CHECK_MESSAGE(result.ec == std::errc(), " I could not parse " << buf); double expected = ((i >= -307) ? testing_power_of_ten[i + 307] : std::pow(10, i)); + auto expected_ec = (expected == 0 || std::isinf(expected)) ? std::errc::result_out_of_range : std::errc(); + CHECK_MESSAGE(result.ec == expected_ec, " I could not parse " << buf); CHECK_MESSAGE(actual == expected, "String '" << buf << "'parsed to " << actual); } } @@ -643,7 +644,7 @@ enum class Diag { runtime, comptime }; template constexpr void check_basic_test_result(std::string_view str, fast_float::from_chars_result result, - T actual, T expected) { + T actual, T expected, std::errc expected_ec) { if constexpr (diag == Diag::runtime) { INFO( "str=" << str << "\n" @@ -689,7 +690,7 @@ constexpr void check_basic_test_result(std::string_view str, return x != x; }; - FASTFLOAT_CHECK_EQ(result.ec, std::errc()); + FASTFLOAT_CHECK_EQ(result.ec, expected_ec); FASTFLOAT_CHECK_EQ(result.ptr, str.data() + str.size()); FASTFLOAT_CHECK_EQ(copysign(1, actual), copysign(1, expected)); FASTFLOAT_CHECK_EQ(isnan(actual), isnan(expected)); @@ -699,17 +700,24 @@ constexpr void check_basic_test_result(std::string_view str, } template -constexpr void basic_test(std::string_view str, T expected) { +constexpr void basic_test(std::string_view str, T expected, std::errc expected_ec = std::errc()) { T actual; auto result = fast_float::from_chars(str.data(), str.data() + str.size(), actual); - check_basic_test_result(str, result, actual, expected); + check_basic_test_result(str, result, actual, expected, expected_ec); } template constexpr void basic_test(std::string_view str, T expected, fast_float::parse_options options) { T actual; auto result = fast_float::from_chars_advanced(str.data(), str.data() + str.size(), actual, options); - check_basic_test_result(str, result, actual, expected); + check_basic_test_result(str, result, actual, expected, std::errc()); +} + +template +constexpr void basic_test(std::string_view str, T expected, std::errc expected_ec, fast_float::parse_options options) { + T actual; + auto result = fast_float::from_chars_advanced(str.data(), str.data() + str.size(), actual, options); + check_basic_test_result(str, result, actual, expected, expected_ec); } void basic_test(float val) { @@ -725,29 +733,27 @@ void basic_test(float val) { } } -#define verify_runtime(lhs, rhs) \ +#define verify_runtime(...) \ do { \ - INFO(lhs); \ - basic_test(lhs, rhs); \ + basic_test(__VA_ARGS__); \ } while (false) -#define verify_comptime(lhs, rhs) \ +#define verify_comptime(...) \ do { \ constexpr int verify_comptime_var = \ - (basic_test(lhs, rhs), 0); \ + (basic_test(__VA_ARGS__), 0); \ (void)verify_comptime_var; \ } while (false) -#define verify_options_runtime(lhs, rhs) \ +#define verify_options_runtime(...) \ do { \ - INFO(lhs); \ - basic_test(lhs, rhs, options); \ + basic_test(__VA_ARGS__, options); \ } while (false) -#define verify_options_comptime(lhs, rhs) \ +#define verify_options_comptime(...) \ do { \ constexpr int verify_options_comptime_var = \ - (basic_test(lhs, rhs, options), 0); \ + (basic_test(__VA_ARGS__, options), 0); \ (void)verify_options_comptime_var; \ } while (false) @@ -756,16 +762,16 @@ void basic_test(float val) { #error "from_chars must be constexpr for constexpr tests" #endif -#define verify(lhs, rhs) \ +#define verify(...) \ do { \ - verify_runtime(lhs, rhs); \ - verify_comptime(lhs, rhs); \ + verify_runtime(__VA_ARGS__); \ + verify_comptime(__VA_ARGS__); \ } while (false) -#define verify_options(lhs, rhs) \ +#define verify_options(...) \ do { \ - verify_options_runtime(lhs, rhs); \ - verify_options_comptime(lhs, rhs); \ + verify_options_runtime(__VA_ARGS__); \ + verify_options_comptime(__VA_ARGS__); \ } while (false) #else @@ -784,19 +790,19 @@ TEST_CASE("64bit.inf") { verify("-infinity", -std::numeric_limits::infinity()); verify("inf", std::numeric_limits::infinity()); verify("-inf", -std::numeric_limits::infinity()); - verify("1234456789012345678901234567890e9999999999999999999999999999", std::numeric_limits::infinity()); - verify("-2139879401095466344511101915470454744.9813888656856943E+272", -std::numeric_limits::infinity()); - verify("1.8e308", std::numeric_limits::infinity()); - verify("1.832312213213213232132132143451234453123412321321312e308", std::numeric_limits::infinity()); - verify("2e30000000000000000", std::numeric_limits::infinity()); - verify("2e3000", std::numeric_limits::infinity()); - verify("1.9e308", std::numeric_limits::infinity()); + verify("1234456789012345678901234567890e9999999999999999999999999999", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify("-2139879401095466344511101915470454744.9813888656856943E+272", -std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify("1.8e308", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify("1.832312213213213232132132143451234453123412321321312e308", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify("2e30000000000000000", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify("2e3000", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify("1.9e308", std::numeric_limits::infinity(), std::errc::result_out_of_range); } TEST_CASE("64bit.general") { verify("22250738585072012e-324",0x1p-1022); /* limit between normal and subnormal*/ verify("-22250738585072012e-324",-0x1p-1022); /* limit between normal and subnormal*/ - verify("-1e-999",-0.0); + verify("-1e-999", -0.0, std::errc::result_out_of_range); verify("-2.2222222222223e-322",-0x1.68p-1069); verify("9007199254740993.0", 0x1p+53); verify("860228122.6654514319E+90", 0x1.92bb20990715fp+328); @@ -852,7 +858,7 @@ TEST_CASE("64bit.general") { verify("45035996.273704985", 45035996.273704985); verify("0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044501477170144022721148195934182639518696390927032912960468522194496444440421538910330590478162701758282983178260792422137401728773891892910553144148156412434867599762821265346585071045737627442980259622449029037796981144446145705102663115100318287949527959668236039986479250965780342141637013812613333119898765515451440315261253813266652951306000184917766328660755595837392240989947807556594098101021612198814605258742579179000071675999344145086087205681577915435923018910334964869420614052182892431445797605163650903606514140377217442262561590244668525767372446430075513332450079650686719491377688478005309963967709758965844137894433796621993967316936280457084866613206797017728916080020698679408551343728867675409720757232455434770912461317493580281734466552734375", 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044501477170144022721148195934182639518696390927032912960468522194496444440421538910330590478162701758282983178260792422137401728773891892910553144148156412434867599762821265346585071045737627442980259622449029037796981144446145705102663115100318287949527959668236039986479250965780342141637013812613333119898765515451440315261253813266652951306000184917766328660755595837392240989947807556594098101021612198814605258742579179000071675999344145086087205681577915435923018910334964869420614052182892431445797605163650903606514140377217442262561590244668525767372446430075513332450079650686719491377688478005309963967709758965844137894433796621993967316936280457084866613206797017728916080020698679408551343728867675409720757232455434770912461317493580281734466552734375); verify("0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072008890245868760858598876504231122409594654935248025624400092282356951787758888037591552642309780950434312085877387158357291821993020294379224223559819827501242041788969571311791082261043971979604000454897391938079198936081525613113376149842043271751033627391549782731594143828136275113838604094249464942286316695429105080201815926642134996606517803095075913058719846423906068637102005108723282784678843631944515866135041223479014792369585208321597621066375401613736583044193603714778355306682834535634005074073040135602968046375918583163124224521599262546494300836851861719422417646455137135420132217031370496583210154654068035397417906022589503023501937519773030945763173210852507299305089761582519159720757232455434770912461317493580281734466552734375", 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072008890245868760858598876504231122409594654935248025624400092282356951787758888037591552642309780950434312085877387158357291821993020294379224223559819827501242041788969571311791082261043971979604000454897391938079198936081525613113376149842043271751033627391549782731594143828136275113838604094249464942286316695429105080201815926642134996606517803095075913058719846423906068637102005108723282784678843631944515866135041223479014792369585208321597621066375401613736583044193603714778355306682834535634005074073040135602968046375918583163124224521599262546494300836851861719422417646455137135420132217031370496583210154654068035397417906022589503023501937519773030945763173210852507299305089761582519159720757232455434770912461317493580281734466552734375); - verify("1438456663141390273526118207642235581183227845246331231162636653790368152091394196930365828634687637948157940776599182791387527135353034738357134110310609455693900824193549772792016543182680519740580354365467985440183598701312257624545562331397018329928613196125590274187720073914818062530830316533158098624984118889298281371812288789537310599037529113415438738954894752124724983067241108764488346454376699018673078404751121414804937224240805993123816932326223683090770561597570457793932985826162604255884529134126396282202126526253389383421806727954588525596114379801269094096329805054803089299736996870951258573010877404407451953846698609198213926882692078557033228265259305481198526059813164469187586693257335779522020407645498684263339921905227556616698129967412891282231685504660671277927198290009824680186319750978665734576683784255802269708917361719466043175201158849097881370477111850171579869056016061666173029059588433776015644439705050377554277696143928278093453792803846252715966016733222646442382892123940052441346822429721593884378212558701004356924243030059517489346646577724622498919752597382095222500311124181823512251071356181769376577651390028297796156208815375089159128394945710515861334486267101797497111125909272505194792870889617179758703442608016143343262159998149700606597792535574457560429226974273443630323818747730771316763398572110874959981923732463076884528677392654150010269822239401993427482376513231389212353583573566376915572650916866553612366187378959554983566712767093372906030188976220169058025354973622211666504549316958271880975697143546564469806791358707318873075708383345004090151974068325838177531266954177406661392229801349994695941509935655355652985723782153570084089560139142231.738475042362596875449154552392299548947138162081694168675340677843807613129780449323363759027012972466987370921816813162658754726545121090545507240267000456594786540949605260722461937870630634874991729398208026467698131898691830012167897399682179601734569071423681e-733", std::numeric_limits::infinity()); + verify("1438456663141390273526118207642235581183227845246331231162636653790368152091394196930365828634687637948157940776599182791387527135353034738357134110310609455693900824193549772792016543182680519740580354365467985440183598701312257624545562331397018329928613196125590274187720073914818062530830316533158098624984118889298281371812288789537310599037529113415438738954894752124724983067241108764488346454376699018673078404751121414804937224240805993123816932326223683090770561597570457793932985826162604255884529134126396282202126526253389383421806727954588525596114379801269094096329805054803089299736996870951258573010877404407451953846698609198213926882692078557033228265259305481198526059813164469187586693257335779522020407645498684263339921905227556616698129967412891282231685504660671277927198290009824680186319750978665734576683784255802269708917361719466043175201158849097881370477111850171579869056016061666173029059588433776015644439705050377554277696143928278093453792803846252715966016733222646442382892123940052441346822429721593884378212558701004356924243030059517489346646577724622498919752597382095222500311124181823512251071356181769376577651390028297796156208815375089159128394945710515861334486267101797497111125909272505194792870889617179758703442608016143343262159998149700606597792535574457560429226974273443630323818747730771316763398572110874959981923732463076884528677392654150010269822239401993427482376513231389212353583573566376915572650916866553612366187378959554983566712767093372906030188976220169058025354973622211666504549316958271880975697143546564469806791358707318873075708383345004090151974068325838177531266954177406661392229801349994695941509935655355652985723782153570084089560139142231.738475042362596875449154552392299548947138162081694168675340677843807613129780449323363759027012972466987370921816813162658754726545121090545507240267000456594786540949605260722461937870630634874991729398208026467698131898691830012167897399682179601734569071423681e-733", std::numeric_limits::infinity(), std::errc::result_out_of_range); verify("-2240084132271013504.131248280843119943687942846658579428", -0x1.f1660a65b00bfp+60); } @@ -864,11 +870,11 @@ TEST_CASE("64bit.decimal_point") { }(); // infinities - verify_options("1,8e308", std::numeric_limits::infinity()); - verify_options("1,832312213213213232132132143451234453123412321321312e308", std::numeric_limits::infinity()); - verify_options("2e30000000000000000", std::numeric_limits::infinity()); - verify_options("2e3000", std::numeric_limits::infinity()); - verify_options("1,9e308", std::numeric_limits::infinity()); + verify_options("1,8e308", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify_options("1,832312213213213232132132143451234453123412321321312e308", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify_options("2e30000000000000000", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify_options("2e3000", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify_options("1,9e308", std::numeric_limits::infinity(), std::errc::result_out_of_range); // finites verify_options("-2,2222222222223e-322",-0x1.68p-1069); @@ -928,13 +934,13 @@ TEST_CASE("32bit.inf") { verify("-infinity", -std::numeric_limits::infinity()); verify("inf", std::numeric_limits::infinity()); verify("-inf", -std::numeric_limits::infinity()); - verify("1234456789012345678901234567890e9999999999999999999999999999", std::numeric_limits::infinity()); - verify("2e3000", std::numeric_limits::infinity()); - verify("3.5028234666e38", std::numeric_limits::infinity()); + verify("1234456789012345678901234567890e9999999999999999999999999999", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify("2e3000", std::numeric_limits::infinity(), std::errc::result_out_of_range); + verify("3.5028234666e38", std::numeric_limits::infinity(), std::errc::result_out_of_range); } TEST_CASE("32bit.general") { - verify("-1e-999",-0.0f); + verify("-1e-999", -0.0f, std::errc::result_out_of_range); verify("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125", 0x1.2ced3p+0f); verify("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125e-38", 0x1.fffff8p-127f); verify_runtime(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655), 0x1.2ced3p+0f); @@ -1000,7 +1006,7 @@ TEST_CASE("32bit.general") { verify("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679f); verify("2.3509887016445750159374730744444913556373311135441750430175034126e-38", 2.3509887016445750159374730744444913556373311135441750430175034126e-38f); verify("1", 1.f); - verify("7.0060e-46", 0.f); + verify("7.0060e-46", 0.f, std::errc::result_out_of_range); verify("3.4028234664e38", 0x1.fffffep+127f); verify("3.4028234665e38", 0x1.fffffep+127f); verify("3.4028234666e38", 0x1.fffffep+127f); @@ -1018,7 +1024,7 @@ TEST_CASE("32bit.decimal_point") { }(); // infinity - verify_options("3,5028234666e38", std::numeric_limits::infinity()); + verify_options("3,5028234666e38", std::numeric_limits::infinity(), std::errc::result_out_of_range); // finites verify_options("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125", 0x1.2ced3p+0f); @@ -1043,7 +1049,7 @@ TEST_CASE("32bit.decimal_point") { verify_options("3,1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679f); verify_options("2,3509887016445750159374730744444913556373311135441750430175034126e-38", 2.3509887016445750159374730744444913556373311135441750430175034126e-38f); verify_options("1", 1.f); - verify_options("7,0060e-46", 0.f); + verify_options("7,0060e-46", 0.f, std::errc::result_out_of_range); verify_options("3,4028234664e38", 0x1.fffffep+127f); verify_options("3,4028234665e38", 0x1.fffffep+127f); verify_options("3,4028234666e38", 0x1.fffffep+127f); diff --git a/tests/string_test.cpp b/tests/string_test.cpp index 78da51e..4329dae 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -62,6 +62,7 @@ template bool test() { std::string input = "0.1 1e1000 100000 3.14159265359 -1e-500 001 1e01 1e0000001 -inf"; std::vector answers = {T(0.1), std::numeric_limits::infinity(), 100000, T(3.14159265359), -0.0, 1, 10, 10, -std::numeric_limits::infinity()}; + std::vector expected_ec = {std::errc(), std::errc::result_out_of_range, std::errc(), std::errc(), std::errc::result_out_of_range, std::errc(), std::errc(), std::errc(), std::errc()}; const char * begin = input.data(); const char * end = input.data() + input.size(); for(size_t i = 0; i < answers.size(); i++) { @@ -69,7 +70,7 @@ bool test() { while((begin < end) && (std::isspace(*begin))) { begin++; } auto result = fast_float::from_chars(begin, end, result_value); - if (result.ec != std::errc()) { + if (result.ec != expected_ec[i]) { printf("parsing %.*s\n", int(end - begin), begin); std::cerr << " I could not parse " << std::endl; return false; From 37127b022fc96669d0ee57e79565335b2e2d576f Mon Sep 17 00:00:00 2001 From: Adam Lugowski Date: Thu, 30 Mar 2023 13:14:35 -0700 Subject: [PATCH 2/2] Add subnormal numbers to powers of ten table On some platforms std::pow returns 0 instead of a subnormal number with `-ffast-math -O2` compiler options. --- tests/basictest.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/basictest.cpp b/tests/basictest.cpp index ff55920..d677028 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -509,6 +509,9 @@ TEST_CASE("test_fixed_only") { static const double testing_power_of_ten[] = { + 1e-323, 1e-322, 1e-321, 1e-320, 1e-319, 1e-318, 1e-317, 1e-316, 1e-315, + 1e-314, 1e-313, 1e-312, 1e-311, 1e-310, 1e-309, 1e-308, + 1e-307, 1e-306, 1e-305, 1e-304, 1e-303, 1e-302, 1e-301, 1e-300, 1e-299, 1e-298, 1e-297, 1e-296, 1e-295, 1e-294, 1e-293, 1e-292, 1e-291, 1e-290, 1e-289, 1e-288, 1e-287, 1e-286, 1e-285, 1e-284, 1e-283, 1e-282, 1e-281, @@ -592,7 +595,7 @@ TEST_CASE("powers_of_ten") { REQUIRE(n < sizeof(buf)); // if false, fails the test and exits double actual; auto result = fast_float::from_chars(buf, buf + 1000, actual); - double expected = ((i >= -307) ? testing_power_of_ten[i + 307] : std::pow(10, i)); + double expected = ((i >= -323) ? testing_power_of_ten[i + 323] : std::pow(10, i)); auto expected_ec = (expected == 0 || std::isinf(expected)) ? std::errc::result_out_of_range : std::errc(); CHECK_MESSAGE(result.ec == expected_ec, " I could not parse " << buf); CHECK_MESSAGE(actual == expected, "String '" << buf << "'parsed to " << actual);