diff --git a/README.md b/README.md index f12dea55..e09883e5 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,34 @@ int main() { } ``` +You can parse delimited numbers: +```C++ + const std::string input = "234532.3426362,7869234.9823,324562.645"; + double result; + auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); + if(answer.ec != std::errc()) { + // check error + } + // we have result == 234532.3426362. + if(answer.ptr[0] != ',') { + // unexpected delimiter + } + answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result); + if(answer.ec != std::errc()) { + // check error + } + // we have result == 7869234.9823. + if(answer.ptr[0] != ',') { + // unexpected delimiter + } + answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result); + if(answer.ec != std::errc()) { + // check error + } + // we have result == 324562.645. +``` + + Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of the type `fast_float::chars_format`. It is a bitset value: we check whether @@ -115,7 +143,7 @@ int main() { } ``` -## Using commas as decimal separator +## Advanced options: using commas as decimal separator, JSON and Fortran The C++ standard stipulate that `from_chars` has to be locale-independent. In @@ -140,33 +168,72 @@ int main() { } ``` -You can parse delimited numbers: +You can also parse Fortran-like inputs: + ```C++ - const std::string input = "234532.3426362,7869234.9823,324562.645"; - double result; - auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); - if(answer.ec != std::errc()) { - // check error - } - // we have result == 234532.3426362. - if(answer.ptr[0] != ',') { - // unexpected delimiter - } - answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result); - if(answer.ec != std::errc()) { - // check error - } - // we have result == 7869234.9823. - if(answer.ptr[0] != ',') { - // unexpected delimiter - } - answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result); - if(answer.ec != std::errc()) { - // check error - } - // we have result == 324562.645. +#include "fast_float/fast_float.h" +#include + +int main() { + const std::string input = "1d+4"; + double result; + fast_float::parse_options options{ fast_float::chars_format::fortran }; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } + std::cout << "parsed the number " << result << std::endl; + return EXIT_SUCCESS; +} ``` +You may also enforce the JSON format ([RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259#section-6)): + + +```C++ +#include "fast_float/fast_float.h" +#include + +int main() { + const std::string input = "+.1"; // not valid + double result; + fast_float::parse_options options{ fast_float::chars_format::json }; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; } + return EXIT_SUCCESS; +} +``` + +By default the JSON format does not allow `inf`: + +```C++ + +#include "fast_float/fast_float.h" +#include + +int main() { + const std::string input = "inf"; // not valid in JSON + double result; + fast_float::parse_options options{ fast_float::chars_format::json }; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; } +} +``` + + +You can allow it with a non-standard `json_or_infnan` variant: + +```C++ +#include "fast_float/fast_float.h" +#include + +int main() { + const std::string input = "inf"; // not valid in JSON but we allow it with json_or_infnan + double result; + fast_float::parse_options options{ fast_float::chars_format::json_or_infnan }; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if(answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; } + return EXIT_SUCCESS; +} +`````` ## Relation With Other Work diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index 9653889a..d18e3d53 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -347,9 +347,17 @@ parsed_number_string_t parse_number_string(UC const *p, UC const * pend, par return answer; } int64_t exp_number = 0; // explicit exponential part - if ((fmt & chars_format::scientific) && (p != pend) && ((UC('e') == *p) || (UC('E') == *p))) { + if ( ((fmt & chars_format::scientific) && + (p != pend) && + ((UC('e') == *p) || (UC('E') == *p))) + || + ((fmt & FASTFLOAT_FORTRANFMT) && + (p != pend) && + ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || (UC('D') == *p)))) { UC const * location_of_e = p; - ++p; + if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || (UC('D') == *p)) { + ++p; + } bool neg_exp = false; if ((p != pend) && (UC('-') == *p)) { neg_exp = true; diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 2ad9e8c2..bee88215 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -13,14 +13,18 @@ namespace fast_float { #define FASTFLOAT_JSONFMT (1 << 5) +#define FASTFLOAT_FORTRANFMT (1 << 6) enum chars_format { scientific = 1 << 0, fixed = 1 << 2, hex = 1 << 3, no_infnan = 1 << 4, + // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 json = FASTFLOAT_JSONFMT | fixed | scientific | no_infnan, + // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. json_or_infnan = FASTFLOAT_JSONFMT | fixed | scientific, + fortran = FASTFLOAT_FORTRANFMT | fixed | scientific, general = fixed | scientific }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e58bea8e..0739a007 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -73,8 +73,7 @@ fast_float_add_cpp_test(powersoffive_hardround) fast_float_add_cpp_test(string_test) fast_float_add_cpp_test(json_fmt) - - +fast_float_add_cpp_test(fortran) option(FASTFLOAT_EXHAUSTIVE "Exhaustive tests" OFF) diff --git a/tests/fortran.cpp b/tests/fortran.cpp new file mode 100644 index 00000000..ec3f90ba --- /dev/null +++ b/tests/fortran.cpp @@ -0,0 +1,69 @@ +/* + * Exercise the Fortran conversion option. + */ +#include +#include +#include + +#define FASTFLOAT_ALLOWS_LEADING_PLUS + +#include "fast_float/fast_float.h" + + +int main_readme() { + const std::string input = "1d+4"; + double result; + fast_float::parse_options options{ fast_float::chars_format::fortran }; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n" << result <<"\n"; return EXIT_FAILURE; } + std::cout << "parsed the number " << result << std::endl; + return EXIT_SUCCESS; +} + +int main () +{ + const std::vector expected{ 10000, 1000, 100, 10, 1, .1, .01, .001, .0001 }; + const std::vector fmt1{ "1+4", "1+3", "1+2", "1+1", "1+0", "1-1", "1-2", + "1-3", "1-4" }; + const std::vector fmt2{ "1d+4", "1d+3", "1d+2", "1d+1", "1d+0", "1d-1", + "1d-2", "1d-3", "1d-4" }; + const std::vector fmt3{ "+1+4", "+1+3", "+1+2", "+1+1", "+1+0", "+1-1", + "+1-2", "+1-3", "+1-4" }; + const fast_float::parse_options options{ fast_float::chars_format::fortran }; + + for ( auto const& f : fmt1 ) { + auto d{ std::distance( &fmt1[0], &f ) }; + double result; + auto answer{ fast_float::from_chars_advanced( f.data(), f.data()+f.size(), result, + options ) }; + if ( answer.ec != std::errc() || result != expected[std::size_t(d)] ) { + std::cerr << "parsing failure on " << f << std::endl; + return EXIT_FAILURE; + } + } + + for ( auto const& f : fmt2 ) { + auto d{ std::distance( &fmt2[0], &f ) }; + double result; + auto answer{ fast_float::from_chars_advanced( f.data(), f.data()+f.size(), result, + options ) }; + if ( answer.ec != std::errc() || result != expected[std::size_t(d)] ) { + std::cerr << "parsing failure on " << f << std::endl; + return EXIT_FAILURE; + } + } + + for ( auto const& f : fmt3 ) { + auto d{ std::distance( &fmt3[0], &f ) }; + double result; + auto answer{ fast_float::from_chars_advanced( f.data(), f.data()+f.size(), result, + options ) }; + if ( answer.ec != std::errc() || result != expected[std::size_t(d)] ) { + std::cerr << "parsing failure on " << f << std::endl; + return EXIT_FAILURE; + } + } + if(main_readme() != EXIT_SUCCESS) { return EXIT_FAILURE; } + + return EXIT_SUCCESS; +} diff --git a/tests/json_fmt.cpp b/tests/json_fmt.cpp index bdd32d94..2cd01e22 100644 --- a/tests/json_fmt.cpp +++ b/tests/json_fmt.cpp @@ -8,6 +8,34 @@ #include "fast_float/fast_float.h" +int main_readme() { + const std::string input = "+.1"; // not valid + double result; + fast_float::parse_options options{ fast_float::chars_format::json }; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; } + return EXIT_SUCCESS; +} + + +int main_readme2() { + const std::string input = "inf"; // not valid in JSON + double result; + fast_float::parse_options options{ fast_float::chars_format::json }; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; } + return EXIT_SUCCESS; +} + +int main_readme3() { + const std::string input = "inf"; // not valid in JSON but we allow it with json_or_infnan + double result; + fast_float::parse_options options{ fast_float::chars_format::json_or_infnan }; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if(answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; } + return EXIT_SUCCESS; +} + int main() { const std::vector expected{ -0.2, 0.02, 0.002, 1., 0., std::numeric_limits::infinity() }; @@ -36,5 +64,8 @@ int main() } } + if(main_readme() != EXIT_SUCCESS) { return EXIT_FAILURE; } + if(main_readme2() != EXIT_SUCCESS) { return EXIT_FAILURE; } + return EXIT_SUCCESS; } \ No newline at end of file