Skip to content

Commit

Permalink
Merge pull request #221 from fastfloat/fortran
Browse files Browse the repository at this point in the history
Fortran support (with fixes)
  • Loading branch information
lemire authored Sep 15, 2023
2 parents e6b370d + 7646f81 commit 1dfad24
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 29 deletions.
117 changes: 92 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 <iostream>

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 <iostream>

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 <iostream>

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 <iostream>

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

Expand Down
12 changes: 10 additions & 2 deletions include/fast_float/ascii_number.h
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,17 @@ parsed_number_string_t<UC> 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;
Expand Down
4 changes: 4 additions & 0 deletions include/fast_float/float_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
3 changes: 1 addition & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
69 changes: 69 additions & 0 deletions tests/fortran.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Exercise the Fortran conversion option.
*/
#include <cstdlib>
#include <iostream>
#include <vector>

#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<double> expected{ 10000, 1000, 100, 10, 1, .1, .01, .001, .0001 };
const std::vector<std::string> fmt1{ "1+4", "1+3", "1+2", "1+1", "1+0", "1-1", "1-2",
"1-3", "1-4" };
const std::vector<std::string> fmt2{ "1d+4", "1d+3", "1d+2", "1d+1", "1d+0", "1d-1",
"1d-2", "1d-3", "1d-4" };
const std::vector<std::string> 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;
}
31 changes: 31 additions & 0 deletions tests/json_fmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double> expected{ -0.2, 0.02, 0.002, 1., 0., std::numeric_limits<double>::infinity() };
Expand Down Expand Up @@ -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;
}

0 comments on commit 1dfad24

Please sign in to comment.