| 1 | const std = @import("std"); |
| 2 | const math = std.math; |
| 3 | const testing = std.testing; |
| 4 | const expect = testing.expect; |
| 5 | const expectEqual = testing.expectEqual; |
| 6 | const expectError = testing.expectError; |
| 7 | const approxEqAbs = std.math.approxEqAbs; |
| 8 | const epsilon = 1e-7; |
| 9 | const parse = @import("parse_float/parse.zig"); |
| 10 | const convertHex = @import("parse_float/convert_hex.zig").convertHex; |
| 11 | const convertFast = @import("parse_float/convert_fast.zig").convertFast; |
| 12 | const convertEiselLemire = @import("parse_float/convert_eisel_lemire.zig").convertEiselLemire; |
| 13 | const convertSlow = @import("parse_float/convert_slow.zig").convertSlow; |
| 14 | |
| 15 | pub const ParseFloatError = error{ |
| 16 | InvalidCharacter, |
| 17 | }; |
| 18 | |
| 19 | pub 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 | |
| 73 | test 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 | |
| 129 | test "nan and inf" { |
| 130 | inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { |
| 131 | const Z = @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 | |
| 139 | test "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 | |
| 147 | test "#11169" { |
| 148 | try expectEqual(try parseFloat(f128, "9007199254740993.0"), 9007199254740993.0); |
| 149 | } |
| 150 | |
| 151 | test "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 | |
| 157 | test "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 | |
| 167 | test "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 | |
| 175 | test "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 | |
| 191 | test "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 | |
| 210 | test "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 | |
| 226 | test "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 | |
| 242 | test "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 | } |