authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-12 15:46:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-12 15:46:14-07:00
logd029e0e972dc34788e9674d0cb72e964b64e17c4
tree094c76216c8995c28cfafcace167da62e5aef00c
parentb8920bceb79756aead8635d156a6e8bffcc1ccea

std: remove one layer of redundant parse_float namespace

there are still more, though. This provides a doctest for the `parseFloat` function.

2 files changed, 67 insertions(+), 71 deletions(-)

lib/std/fmt/parse_float.zig+67-5
...@@ -1,7 +1,4 @@...@@ -1,7 +1,4 @@
1pub const parseFloat = @import("parse_float/parse_float.zig").parseFloat;1const std = @import("../std.zig");
2pub const ParseFloatError = @import("parse_float/parse_float.zig").ParseFloatError;
3
4const std = @import("std");
5const math = std.math;2const math = std.math;
6const testing = std.testing;3const testing = std.testing;
7const expect = testing.expect;4const expect = testing.expect;
...@@ -9,10 +6,75 @@ const expectEqual = testing.expectEqual;...@@ -9,10 +6,75 @@ const expectEqual = testing.expectEqual;
9const expectError = testing.expectError;6const expectError = testing.expectError;
10const approxEqAbs = std.math.approxEqAbs;7const approxEqAbs = std.math.approxEqAbs;
11const epsilon = 1e-7;8const epsilon = 1e-7;
9const parse = @import("parse_float/parse.zig");
10const convertHex = @import("parse_float/convert_hex.zig").convertHex;
11const convertFast = @import("parse_float/convert_fast.zig").convertFast;
12const convertEiselLemire = @import("parse_float/convert_eisel_lemire.zig").convertEiselLemire;
13const convertSlow = @import("parse_float/convert_slow.zig").convertSlow;
14
15pub const ParseFloatError = error{
16 InvalidCharacter,
17};
18
19pub fn parseFloat(comptime T: type, s: []const u8) ParseFloatError!T {
20 if (@typeInfo(T) != .Float) {
21 @compileError("Cannot parse a float into a non-floating point type.");
22 }
23
24 if (T == f80) {
25 @compileError("TODO support parsing float to f80");
26 }
27
28 if (s.len == 0) {
29 return error.InvalidCharacter;
30 }
31
32 var i: usize = 0;
33 const negative = s[i] == '-';
34 if (s[i] == '-' or s[i] == '+') {
35 i += 1;
36 }
37 if (s.len == i) {
38 return error.InvalidCharacter;
39 }
40
41 const n = parse.parseNumber(T, s[i..], negative) orelse {
42 return parse.parseInfOrNan(T, s[i..], negative) orelse error.InvalidCharacter;
43 };
44
45 if (n.hex) {
46 return convertHex(T, n);
47 }
48
49 if (convertFast(T, n)) |f| {
50 return f;
51 }
52
53 if (T == f16 or T == f32 or T == f64) {
54 // If significant digits were truncated, then we can have rounding error
55 // only if `mantissa + 1` produces a different result. We also avoid
56 // redundantly using the Eisel-Lemire algorithm if it was unable to
57 // correctly round on the first pass.
58 if (convertEiselLemire(T, n.exponent, n.mantissa)) |bf| {
59 if (!n.many_digits) {
60 return bf.toFloat(T, n.negative);
61 }
62 if (convertEiselLemire(T, n.exponent, n.mantissa + 1)) |bf2| {
63 if (bf.eql(bf2)) {
64 return bf.toFloat(T, n.negative);
65 }
66 }
67 }
68 }
69
70 // Unable to correctly round the float using the Eisel-Lemire algorithm.
71 // Fallback to a slower, but always correct algorithm.
72 return convertSlow(T, s[i..]).toFloat(T, negative);
73}
1274
13// See https://github.com/tiehuis/parse-number-fxx-test-data for a wider-selection of test-data.75// See https://github.com/tiehuis/parse-number-fxx-test-data for a wider-selection of test-data.
1476
15test "parseFloat" {77test parseFloat {
16 inline for ([_]type{ f16, f32, f64, f128 }) |T| {78 inline for ([_]type{ f16, f32, f64, f128 }) |T| {
17 try testing.expectError(error.InvalidCharacter, parseFloat(T, ""));79 try testing.expectError(error.InvalidCharacter, parseFloat(T, ""));
18 try testing.expectError(error.InvalidCharacter, parseFloat(T, " 1"));80 try testing.expectError(error.InvalidCharacter, parseFloat(T, " 1"));
lib/std/fmt/parse_float/parse_float.zig deleted-66
...@@ -1,66 +0,0 @@
1const std = @import("std");
2const parse = @import("parse.zig");
3const convertFast = @import("convert_fast.zig").convertFast;
4const convertEiselLemire = @import("convert_eisel_lemire.zig").convertEiselLemire;
5const convertSlow = @import("convert_slow.zig").convertSlow;
6const convertHex = @import("convert_hex.zig").convertHex;
7
8pub const ParseFloatError = error{
9 InvalidCharacter,
10};
11
12pub fn parseFloat(comptime T: type, s: []const u8) ParseFloatError!T {
13 if (@typeInfo(T) != .Float) {
14 @compileError("Cannot parse a float into a non-floating point type.");
15 }
16
17 if (T == f80) {
18 @compileError("TODO support parsing float to f80");
19 }
20
21 if (s.len == 0) {
22 return error.InvalidCharacter;
23 }
24
25 var i: usize = 0;
26 const negative = s[i] == '-';
27 if (s[i] == '-' or s[i] == '+') {
28 i += 1;
29 }
30 if (s.len == i) {
31 return error.InvalidCharacter;
32 }
33
34 const n = parse.parseNumber(T, s[i..], negative) orelse {
35 return parse.parseInfOrNan(T, s[i..], negative) orelse error.InvalidCharacter;
36 };
37
38 if (n.hex) {
39 return convertHex(T, n);
40 }
41
42 if (convertFast(T, n)) |f| {
43 return f;
44 }
45
46 if (T == f16 or T == f32 or T == f64) {
47 // If significant digits were truncated, then we can have rounding error
48 // only if `mantissa + 1` produces a different result. We also avoid
49 // redundantly using the Eisel-Lemire algorithm if it was unable to
50 // correctly round on the first pass.
51 if (convertEiselLemire(T, n.exponent, n.mantissa)) |bf| {
52 if (!n.many_digits) {
53 return bf.toFloat(T, n.negative);
54 }
55 if (convertEiselLemire(T, n.exponent, n.mantissa + 1)) |bf2| {
56 if (bf.eql(bf2)) {
57 return bf.toFloat(T, n.negative);
58 }
59 }
60 }
61 }
62
63 // Unable to correctly round the float using the Eisel-Lemire algorithm.
64 // Fallback to a slower, but always correct algorithm.
65 return convertSlow(T, s[i..]).toFloat(T, negative);
66}