master
  1const std = @import("std");
  2const math = std.math;
  3const testing = std.testing;
  4const expect = testing.expect;
  5const expectEqual = testing.expectEqual;
  6const expectError = testing.expectError;
  7const approxEqAbs = std.math.approxEqAbs;
  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 (s.len == 0) {
 25        return error.InvalidCharacter;
 26    }
 27
 28    var i: usize = 0;
 29    const negative = s[i] == '-';
 30    if (s[i] == '-' or s[i] == '+') {
 31        i += 1;
 32    }
 33    if (s.len == i) {
 34        return error.InvalidCharacter;
 35    }
 36
 37    const n = parse.parseNumber(T, s[i..], negative) orelse {
 38        return parse.parseInfOrNan(T, s[i..], negative) orelse error.InvalidCharacter;
 39    };
 40
 41    if (n.hex) {
 42        return convertHex(T, n);
 43    }
 44
 45    if (convertFast(T, n)) |f| {
 46        return f;
 47    }
 48
 49    if (T == f16 or T == f32 or T == f64) {
 50        // If significant digits were truncated, then we can have rounding error
 51        // only if `mantissa + 1` produces a different result. We also avoid
 52        // redundantly using the Eisel-Lemire algorithm if it was unable to
 53        // correctly round on the first pass.
 54        if (convertEiselLemire(T, n.exponent, n.mantissa)) |bf| {
 55            if (!n.many_digits) {
 56                return bf.toFloat(T, n.negative);
 57            }
 58            if (convertEiselLemire(T, n.exponent, n.mantissa + 1)) |bf2| {
 59                if (bf.eql(bf2)) {
 60                    return bf.toFloat(T, n.negative);
 61                }
 62            }
 63        }
 64    }
 65
 66    // Unable to correctly round the float using the Eisel-Lemire algorithm.
 67    // Fallback to a slower, but always correct algorithm.
 68    return convertSlow(T, s[i..]).toFloat(T, negative);
 69}
 70
 71// See https://github.com/tiehuis/parse-number-fxx-test-data for a wider-selection of test-data.
 72
 73test parseFloat {
 74    inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
 75        try testing.expectError(error.InvalidCharacter, parseFloat(T, ""));
 76        try testing.expectError(error.InvalidCharacter, parseFloat(T, "   1"));
 77        try testing.expectError(error.InvalidCharacter, parseFloat(T, "1abc"));
 78        try testing.expectError(error.InvalidCharacter, parseFloat(T, "+"));
 79        try testing.expectError(error.InvalidCharacter, parseFloat(T, "-"));
 80
 81        try expectEqual(try parseFloat(T, "0"), 0.0);
 82        try expectEqual(try parseFloat(T, "0"), 0.0);
 83        try expectEqual(try parseFloat(T, "+0"), 0.0);
 84        try expectEqual(try parseFloat(T, "-0"), 0.0);
 85
 86        try expectEqual(try parseFloat(T, "0e0"), 0);
 87        try expectEqual(try parseFloat(T, "2e3"), 2000.0);
 88        try expectEqual(try parseFloat(T, "1e0"), 1.0);
 89        try expectEqual(try parseFloat(T, "-2e3"), -2000.0);
 90        try expectEqual(try parseFloat(T, "-1e0"), -1.0);
 91        try expectEqual(try parseFloat(T, "1.234e3"), 1234);
 92
 93        try expect(approxEqAbs(T, try parseFloat(T, "3.141"), 3.141, epsilon));
 94        try expect(approxEqAbs(T, try parseFloat(T, "-3.141"), -3.141, epsilon));
 95
 96        try expectEqual(try parseFloat(T, "1e-5000"), 0);
 97        try expectEqual(try parseFloat(T, "1e+5000"), std.math.inf(T));
 98
 99        try expectEqual(try parseFloat(T, "0.4e0066999999999999999999999999999999999999999999999999999"), std.math.inf(T));
100        try expect(approxEqAbs(T, try parseFloat(T, "0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0"), @as(T, 123456.789000e10), epsilon));
101
102        // underscore rule is simple and reduces to "can only occur between two digits" and multiple are not supported.
103        try expectError(error.InvalidCharacter, parseFloat(T, "0123456.789000e_0010")); // cannot occur immediately after exponent
104        try expectError(error.InvalidCharacter, parseFloat(T, "_0123456.789000e0010")); // cannot occur before any digits
105        try expectError(error.InvalidCharacter, parseFloat(T, "0__123456.789000e_0010")); // cannot occur twice in a row
106        try expectError(error.InvalidCharacter, parseFloat(T, "0123456_.789000e0010")); // cannot occur before decimal point
107        try expectError(error.InvalidCharacter, parseFloat(T, "0123456.789000e0010_")); // cannot occur at end of number
108
109        try expect(approxEqAbs(T, try parseFloat(T, "1e-2"), 0.01, epsilon));
110        try expect(approxEqAbs(T, try parseFloat(T, "1234e-2"), 12.34, epsilon));
111
112        try expect(approxEqAbs(T, try parseFloat(T, "1."), 1, epsilon));
113        try expect(approxEqAbs(T, try parseFloat(T, "0."), 0, epsilon));
114        try expect(approxEqAbs(T, try parseFloat(T, ".1"), 0.1, epsilon));
115        try expect(approxEqAbs(T, try parseFloat(T, ".0"), 0, epsilon));
116        try expect(approxEqAbs(T, try parseFloat(T, ".1e-1"), 0.01, epsilon));
117
118        try expectError(error.InvalidCharacter, parseFloat(T, ".")); // At least one digit is required.
119        try expectError(error.InvalidCharacter, parseFloat(T, ".e1")); // At least one digit is required.
120        try expectError(error.InvalidCharacter, parseFloat(T, "0.e")); // At least one digit is required.
121
122        try expect(approxEqAbs(T, try parseFloat(T, "123142.1"), 123142.1, epsilon));
123        try expect(approxEqAbs(T, try parseFloat(T, "-123142.1124"), @as(T, -123142.1124), epsilon));
124        try expect(approxEqAbs(T, try parseFloat(T, "0.7062146892655368"), @as(T, 0.7062146892655368), epsilon));
125        try expect(approxEqAbs(T, try parseFloat(T, "2.71828182845904523536"), @as(T, 2.718281828459045), epsilon));
126    }
127}
128
129test "nan and inf" {
130    inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
131        const Z = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
132
133        try expectEqual(@as(Z, @bitCast(try parseFloat(T, "nAn"))), @as(Z, @bitCast(std.math.nan(T))));
134        try expectEqual(try parseFloat(T, "inF"), std.math.inf(T));
135        try expectEqual(try parseFloat(T, "-INF"), -std.math.inf(T));
136    }
137}
138
139test "largest normals" {
140    try expectEqual(@as(u16, @bitCast(try parseFloat(f16, "65504"))), 0x7bff);
141    try expectEqual(@as(u32, @bitCast(try parseFloat(f32, "3.4028234664E38"))), 0x7f7f_ffff);
142    try expectEqual(@as(u64, @bitCast(try parseFloat(f64, "1.7976931348623157E308"))), 0x7fef_ffff_ffff_ffff);
143    try expectEqual(@as(u80, @bitCast(try parseFloat(f80, "1.189731495357231765E4932"))), 0x7ffe_ffff_ffff_ffff_ffff);
144    try expectEqual(@as(u128, @bitCast(try parseFloat(f128, "1.1897314953572317650857593266280070162E4932"))), 0x7ffe_ffff_ffff_ffff_ffff_ffff_ffff_ffff);
145}
146
147test "#11169" {
148    try expectEqual(try parseFloat(f128, "9007199254740993.0"), 9007199254740993.0);
149}
150
151test "many_digits hex" {
152    const a: f32 = try parseFloat(f32, "0xffffffffffffffff.0p0");
153    const b: f32 = @floatCast(try parseFloat(f128, "0xffffffffffffffff.0p0"));
154    try std.testing.expectEqual(a, b);
155}
156
157test "hex.special" {
158    try testing.expect(math.isNan(try parseFloat(f32, "nAn")));
159    try testing.expect(math.isPositiveInf(try parseFloat(f32, "iNf")));
160    try testing.expect(math.isPositiveInf(try parseFloat(f32, "+Inf")));
161    try testing.expect(math.isNegativeInf(try parseFloat(f32, "-iNf")));
162
163    try testing.expect(math.isPositiveInf(try parseFloat(f32, "0x9999p9999")));
164    try testing.expect(math.isNegativeInf(try parseFloat(f32, "-0x9999p9999")));
165}
166
167test "hex.zero" {
168    try testing.expectEqual(@as(f32, 0.0), try parseFloat(f32, "0x0"));
169    try testing.expectEqual(@as(f32, 0.0), try parseFloat(f32, "-0x0"));
170    try testing.expectEqual(@as(f32, 0.0), try parseFloat(f32, "0x0p42"));
171    try testing.expectEqual(@as(f32, 0.0), try parseFloat(f32, "-0x0.00000p42"));
172    try testing.expectEqual(@as(f32, 0.0), try parseFloat(f32, "0x0.00000p666"));
173}
174
175test "hex.f16" {
176    try testing.expectEqual(try parseFloat(f16, "0x1p0"), 1.0);
177    try testing.expectEqual(try parseFloat(f16, "-0x1p-1"), -0.5);
178    try testing.expectEqual(try parseFloat(f16, "0x10p+10"), 16384.0);
179    try testing.expectEqual(try parseFloat(f16, "0x10p-10"), 0.015625);
180    // Max normalized value.
181    try testing.expectEqual(try parseFloat(f16, "0x1.ffcp+15"), math.floatMax(f16));
182    try testing.expectEqual(try parseFloat(f16, "-0x1.ffcp+15"), -math.floatMax(f16));
183    // Min normalized value.
184    try testing.expectEqual(try parseFloat(f16, "0x1p-14"), math.floatMin(f16));
185    try testing.expectEqual(try parseFloat(f16, "-0x1p-14"), -math.floatMin(f16));
186    // Min denormal value.
187    try testing.expectEqual(try parseFloat(f16, "0x1p-24"), math.floatTrueMin(f16));
188    try testing.expectEqual(try parseFloat(f16, "-0x1p-24"), -math.floatTrueMin(f16));
189}
190
191test "hex.f32" {
192    try testing.expectError(error.InvalidCharacter, parseFloat(f32, "0x"));
193    try testing.expectEqual(try parseFloat(f32, "0x1p0"), 1.0);
194    try testing.expectEqual(try parseFloat(f32, "-0x1p-1"), -0.5);
195    try testing.expectEqual(try parseFloat(f32, "0x10p+10"), 16384.0);
196    try testing.expectEqual(try parseFloat(f32, "0x10p-10"), 0.015625);
197    try testing.expectEqual(try parseFloat(f32, "0x0.ffffffp128"), 0x0.ffffffp128);
198    try testing.expectEqual(try parseFloat(f32, "0x0.1234570p-125"), 0x0.1234570p-125);
199    // Max normalized value.
200    try testing.expectEqual(try parseFloat(f32, "0x1.fffffeP+127"), math.floatMax(f32));
201    try testing.expectEqual(try parseFloat(f32, "-0x1.fffffeP+127"), -math.floatMax(f32));
202    // Min normalized value.
203    try testing.expectEqual(try parseFloat(f32, "0x1p-126"), math.floatMin(f32));
204    try testing.expectEqual(try parseFloat(f32, "-0x1p-126"), -math.floatMin(f32));
205    // Min denormal value.
206    try testing.expectEqual(try parseFloat(f32, "0x1P-149"), math.floatTrueMin(f32));
207    try testing.expectEqual(try parseFloat(f32, "-0x1P-149"), -math.floatTrueMin(f32));
208}
209
210test "hex.f64" {
211    try testing.expectEqual(try parseFloat(f64, "0x1p0"), 1.0);
212    try testing.expectEqual(try parseFloat(f64, "-0x1p-1"), -0.5);
213    try testing.expectEqual(try parseFloat(f64, "0x10p+10"), 16384.0);
214    try testing.expectEqual(try parseFloat(f64, "0x10p-10"), 0.015625);
215    // Max normalized value.
216    try testing.expectEqual(try parseFloat(f64, "0x1.fffffffffffffp+1023"), math.floatMax(f64));
217    try testing.expectEqual(try parseFloat(f64, "-0x1.fffffffffffffp1023"), -math.floatMax(f64));
218    // Min normalized value.
219    try testing.expectEqual(try parseFloat(f64, "0x1p-1022"), math.floatMin(f64));
220    try testing.expectEqual(try parseFloat(f64, "-0x1p-1022"), -math.floatMin(f64));
221    // Min denormalized value.
222    try testing.expectEqual(try parseFloat(f64, "0x1p-1074"), math.floatTrueMin(f64));
223    try testing.expectEqual(try parseFloat(f64, "-0x1p-1074"), -math.floatTrueMin(f64));
224}
225
226test "hex.f80" {
227    try testing.expectEqual(try parseFloat(f80, "0x1p0"), 1.0);
228    try testing.expectEqual(try parseFloat(f80, "-0x1p-1"), -0.5);
229    try testing.expectEqual(try parseFloat(f80, "0x10p+10"), 16384.0);
230    try testing.expectEqual(try parseFloat(f80, "0x10p-10"), 0.015625);
231    // Max normalized value.
232    try testing.expectEqual(try parseFloat(f80, "0xf.fffffffffffffff7p+16380"), math.floatMax(f80));
233    try testing.expectEqual(try parseFloat(f80, "-0xf.fffffffffffffff7p+16380"), -math.floatMax(f80));
234    // Min normalized value.
235    try testing.expectEqual(try parseFloat(f80, "0x1p-16382"), math.floatMin(f80));
236    try testing.expectEqual(try parseFloat(f80, "-0x1p-16382"), -math.floatMin(f80));
237    // Min denormalized value.
238    try testing.expectEqual(try parseFloat(f80, "0x1p-16445"), math.floatTrueMin(f80));
239    try testing.expectEqual(try parseFloat(f80, "-0x1p-16445"), -math.floatTrueMin(f80));
240}
241
242test "hex.f128" {
243    try testing.expectEqual(try parseFloat(f128, "0x1p0"), 1.0);
244    try testing.expectEqual(try parseFloat(f128, "-0x1p-1"), -0.5);
245    try testing.expectEqual(try parseFloat(f128, "0x10p+10"), 16384.0);
246    try testing.expectEqual(try parseFloat(f128, "0x10p-10"), 0.015625);
247    // Max normalized value.
248    try testing.expectEqual(try parseFloat(f128, "0xf.fffffffffffffffffffffffffff8p+16380"), math.floatMax(f128));
249    try testing.expectEqual(try parseFloat(f128, "-0xf.fffffffffffffffffffffffffff8p+16380"), -math.floatMax(f128));
250    // Min normalized value.
251    try testing.expectEqual(try parseFloat(f128, "0x1p-16382"), math.floatMin(f128));
252    try testing.expectEqual(try parseFloat(f128, "-0x1p-16382"), -math.floatMin(f128));
253    // Min denormalized value.
254    try testing.expectEqual(try parseFloat(f128, "0x1p-16494"), math.floatTrueMin(f128));
255    try testing.expectEqual(try parseFloat(f128, "-0x1p-16494"), -math.floatTrueMin(f128));
256    // ensure round-to-even
257    try testing.expectEqual(try parseFloat(f128, "0x1.edcb34a235253948765432134674fp-1"), 0x1.edcb34a235253948765432134674fp-1);
258}