Skip to content

Commit 1dfad24

Browse files
authored
Merge pull request #221 from fastfloat/fortran
Fortran support (with fixes)
2 parents e6b370d + 7646f81 commit 1dfad24

File tree

6 files changed

+207
-29
lines changed

6 files changed

+207
-29
lines changed

README.md

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,34 @@ int main() {
5555
}
5656
```
5757

58+
You can parse delimited numbers:
59+
```C++
60+
const std::string input = "234532.3426362,7869234.9823,324562.645";
61+
double result;
62+
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
63+
if(answer.ec != std::errc()) {
64+
// check error
65+
}
66+
// we have result == 234532.3426362.
67+
if(answer.ptr[0] != ',') {
68+
// unexpected delimiter
69+
}
70+
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
71+
if(answer.ec != std::errc()) {
72+
// check error
73+
}
74+
// we have result == 7869234.9823.
75+
if(answer.ptr[0] != ',') {
76+
// unexpected delimiter
77+
}
78+
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
79+
if(answer.ec != std::errc()) {
80+
// check error
81+
}
82+
// we have result == 324562.645.
83+
```
84+
85+
5886
5987
Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of
6088
the type `fast_float::chars_format`. It is a bitset value: we check whether
@@ -115,7 +143,7 @@ int main() {
115143
}
116144
```
117145

118-
## Using commas as decimal separator
146+
## Advanced options: using commas as decimal separator, JSON and Fortran
119147

120148

121149
The C++ standard stipulate that `from_chars` has to be locale-independent. In
@@ -140,33 +168,72 @@ int main() {
140168
}
141169
```
142170

143-
You can parse delimited numbers:
171+
You can also parse Fortran-like inputs:
172+
144173
```C++
145-
const std::string input = "234532.3426362,7869234.9823,324562.645";
146-
double result;
147-
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
148-
if(answer.ec != std::errc()) {
149-
// check error
150-
}
151-
// we have result == 234532.3426362.
152-
if(answer.ptr[0] != ',') {
153-
// unexpected delimiter
154-
}
155-
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
156-
if(answer.ec != std::errc()) {
157-
// check error
158-
}
159-
// we have result == 7869234.9823.
160-
if(answer.ptr[0] != ',') {
161-
// unexpected delimiter
162-
}
163-
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
164-
if(answer.ec != std::errc()) {
165-
// check error
166-
}
167-
// we have result == 324562.645.
174+
#include "fast_float/fast_float.h"
175+
#include <iostream>
176+
177+
int main() {
178+
const std::string input = "1d+4";
179+
double result;
180+
fast_float::parse_options options{ fast_float::chars_format::fortran };
181+
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
182+
if((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
183+
std::cout << "parsed the number " << result << std::endl;
184+
return EXIT_SUCCESS;
185+
}
168186
```
169187

188+
You may also enforce the JSON format ([RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259#section-6)):
189+
190+
191+
```C++
192+
#include "fast_float/fast_float.h"
193+
#include <iostream>
194+
195+
int main() {
196+
const std::string input = "+.1"; // not valid
197+
double result;
198+
fast_float::parse_options options{ fast_float::chars_format::json };
199+
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
200+
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
201+
return EXIT_SUCCESS;
202+
}
203+
```
204+
205+
By default the JSON format does not allow `inf`:
206+
207+
```C++
208+
209+
#include "fast_float/fast_float.h"
210+
#include <iostream>
211+
212+
int main() {
213+
const std::string input = "inf"; // not valid in JSON
214+
double result;
215+
fast_float::parse_options options{ fast_float::chars_format::json };
216+
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
217+
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
218+
}
219+
```
220+
221+
222+
You can allow it with a non-standard `json_or_infnan` variant:
223+
224+
```C++
225+
#include "fast_float/fast_float.h"
226+
#include <iostream>
227+
228+
int main() {
229+
const std::string input = "inf"; // not valid in JSON but we allow it with json_or_infnan
230+
double result;
231+
fast_float::parse_options options{ fast_float::chars_format::json_or_infnan };
232+
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
233+
if(answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; }
234+
return EXIT_SUCCESS;
235+
}
236+
``````
170237

171238
## Relation With Other Work
172239

include/fast_float/ascii_number.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,17 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
347347
return answer;
348348
}
349349
int64_t exp_number = 0; // explicit exponential part
350-
if ((fmt & chars_format::scientific) && (p != pend) && ((UC('e') == *p) || (UC('E') == *p))) {
350+
if ( ((fmt & chars_format::scientific) &&
351+
(p != pend) &&
352+
((UC('e') == *p) || (UC('E') == *p)))
353+
||
354+
((fmt & FASTFLOAT_FORTRANFMT) &&
355+
(p != pend) &&
356+
((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || (UC('D') == *p)))) {
351357
UC const * location_of_e = p;
352-
++p;
358+
if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || (UC('D') == *p)) {
359+
++p;
360+
}
353361
bool neg_exp = false;
354362
if ((p != pend) && (UC('-') == *p)) {
355363
neg_exp = true;

include/fast_float/float_common.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@
1313
namespace fast_float {
1414

1515
#define FASTFLOAT_JSONFMT (1 << 5)
16+
#define FASTFLOAT_FORTRANFMT (1 << 6)
1617

1718
enum chars_format {
1819
scientific = 1 << 0,
1920
fixed = 1 << 2,
2021
hex = 1 << 3,
2122
no_infnan = 1 << 4,
23+
// RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6
2224
json = FASTFLOAT_JSONFMT | fixed | scientific | no_infnan,
25+
// Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed.
2326
json_or_infnan = FASTFLOAT_JSONFMT | fixed | scientific,
27+
fortran = FASTFLOAT_FORTRANFMT | fixed | scientific,
2428
general = fixed | scientific
2529
};
2630

tests/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ fast_float_add_cpp_test(powersoffive_hardround)
7373
fast_float_add_cpp_test(string_test)
7474

7575
fast_float_add_cpp_test(json_fmt)
76-
77-
76+
fast_float_add_cpp_test(fortran)
7877

7978
option(FASTFLOAT_EXHAUSTIVE "Exhaustive tests" OFF)
8079

tests/fortran.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Exercise the Fortran conversion option.
3+
*/
4+
#include <cstdlib>
5+
#include <iostream>
6+
#include <vector>
7+
8+
#define FASTFLOAT_ALLOWS_LEADING_PLUS
9+
10+
#include "fast_float/fast_float.h"
11+
12+
13+
int main_readme() {
14+
const std::string input = "1d+4";
15+
double result;
16+
fast_float::parse_options options{ fast_float::chars_format::fortran };
17+
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
18+
if((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n" << result <<"\n"; return EXIT_FAILURE; }
19+
std::cout << "parsed the number " << result << std::endl;
20+
return EXIT_SUCCESS;
21+
}
22+
23+
int main ()
24+
{
25+
const std::vector<double> expected{ 10000, 1000, 100, 10, 1, .1, .01, .001, .0001 };
26+
const std::vector<std::string> fmt1{ "1+4", "1+3", "1+2", "1+1", "1+0", "1-1", "1-2",
27+
"1-3", "1-4" };
28+
const std::vector<std::string> fmt2{ "1d+4", "1d+3", "1d+2", "1d+1", "1d+0", "1d-1",
29+
"1d-2", "1d-3", "1d-4" };
30+
const std::vector<std::string> fmt3{ "+1+4", "+1+3", "+1+2", "+1+1", "+1+0", "+1-1",
31+
"+1-2", "+1-3", "+1-4" };
32+
const fast_float::parse_options options{ fast_float::chars_format::fortran };
33+
34+
for ( auto const& f : fmt1 ) {
35+
auto d{ std::distance( &fmt1[0], &f ) };
36+
double result;
37+
auto answer{ fast_float::from_chars_advanced( f.data(), f.data()+f.size(), result,
38+
options ) };
39+
if ( answer.ec != std::errc() || result != expected[std::size_t(d)] ) {
40+
std::cerr << "parsing failure on " << f << std::endl;
41+
return EXIT_FAILURE;
42+
}
43+
}
44+
45+
for ( auto const& f : fmt2 ) {
46+
auto d{ std::distance( &fmt2[0], &f ) };
47+
double result;
48+
auto answer{ fast_float::from_chars_advanced( f.data(), f.data()+f.size(), result,
49+
options ) };
50+
if ( answer.ec != std::errc() || result != expected[std::size_t(d)] ) {
51+
std::cerr << "parsing failure on " << f << std::endl;
52+
return EXIT_FAILURE;
53+
}
54+
}
55+
56+
for ( auto const& f : fmt3 ) {
57+
auto d{ std::distance( &fmt3[0], &f ) };
58+
double result;
59+
auto answer{ fast_float::from_chars_advanced( f.data(), f.data()+f.size(), result,
60+
options ) };
61+
if ( answer.ec != std::errc() || result != expected[std::size_t(d)] ) {
62+
std::cerr << "parsing failure on " << f << std::endl;
63+
return EXIT_FAILURE;
64+
}
65+
}
66+
if(main_readme() != EXIT_SUCCESS) { return EXIT_FAILURE; }
67+
68+
return EXIT_SUCCESS;
69+
}

tests/json_fmt.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,34 @@
88

99
#include "fast_float/fast_float.h"
1010

11+
int main_readme() {
12+
const std::string input = "+.1"; // not valid
13+
double result;
14+
fast_float::parse_options options{ fast_float::chars_format::json };
15+
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
16+
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
17+
return EXIT_SUCCESS;
18+
}
19+
20+
21+
int main_readme2() {
22+
const std::string input = "inf"; // not valid in JSON
23+
double result;
24+
fast_float::parse_options options{ fast_float::chars_format::json };
25+
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
26+
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
27+
return EXIT_SUCCESS;
28+
}
29+
30+
int main_readme3() {
31+
const std::string input = "inf"; // not valid in JSON but we allow it with json_or_infnan
32+
double result;
33+
fast_float::parse_options options{ fast_float::chars_format::json_or_infnan };
34+
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
35+
if(answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; }
36+
return EXIT_SUCCESS;
37+
}
38+
1139
int main()
1240
{
1341
const std::vector<double> expected{ -0.2, 0.02, 0.002, 1., 0., std::numeric_limits<double>::infinity() };
@@ -36,5 +64,8 @@ int main()
3664
}
3765
}
3866

67+
if(main_readme() != EXIT_SUCCESS) { return EXIT_FAILURE; }
68+
if(main_readme2() != EXIT_SUCCESS) { return EXIT_FAILURE; }
69+
3970
return EXIT_SUCCESS;
4071
}

0 commit comments

Comments
 (0)