| ... | @@ -0,0 +1,352 @@ |
| 1 | // SPDX-License-Identifier: MIT |
| 2 | // Copyright (c) 2015-2021 Zig Contributors |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. |
| 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | // and substantial portions of the software.const std = @import("std"); |
| 6 | // |
| 7 | // The rounding logic is inspired by LLVM's APFloat and Go's atofHex |
| 8 | // implementation. |
| 9 | |
| 10 | const std = @import("std"); |
| 11 | const ascii = std.ascii; |
| 12 | const fmt = std.fmt; |
| 13 | const math = std.math; |
| 14 | const testing = std.testing; |
| 15 | |
| 16 | const assert = std.debug.assert; |
| 17 | |
| 18 | pub fn parseHexFloat(comptime T: type, s: []const u8) !T { |
| 19 | assert(@typeInfo(T) == .Float); |
| 20 | |
| 21 | const IntT = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); |
| 22 | |
| 23 | const mantissa_bits = math.floatMantissaBits(T); |
| 24 | const exponent_bits = math.floatExponentBits(T); |
| 25 | |
| 26 | const sign_shift = mantissa_bits + exponent_bits; |
| 27 | |
| 28 | const exponent_bias = (1 << (exponent_bits - 1)) - 1; |
| 29 | const exponent_min = 1 - exponent_bias; |
| 30 | const exponent_max = exponent_bias; |
| 31 | |
| 32 | if (s.len == 0) |
| 33 | return error.InvalidCharacter; |
| 34 | |
| 35 | if (ascii.eqlIgnoreCase(s, "nan")) { |
| 36 | return math.nan(T); |
| 37 | } else if (ascii.eqlIgnoreCase(s, "inf") or ascii.eqlIgnoreCase(s, "+inf")) { |
| 38 | return math.inf(T); |
| 39 | } else if (ascii.eqlIgnoreCase(s, "-inf")) { |
| 40 | return -math.inf(T); |
| 41 | } |
| 42 | |
| 43 | var negative: bool = false; |
| 44 | var exp_negative: bool = false; |
| 45 | |
| 46 | var mantissa: u128 = 0; |
| 47 | var exponent: i16 = 0; |
| 48 | var frac_scale: i16 = 0; |
| 49 | |
| 50 | const State = enum { |
| 51 | MaybeSign, |
| 52 | Prefix, |
| 53 | LeadingIntegerDigit, |
| 54 | IntegerDigit, |
| 55 | MaybeDot, |
| 56 | LeadingFractionDigit, |
| 57 | FractionDigit, |
| 58 | ExpPrefix, |
| 59 | MaybeExpSign, |
| 60 | ExpDigit, |
| 61 | }; |
| 62 | |
| 63 | var state = State.MaybeSign; |
| 64 | |
| 65 | var i: usize = 0; |
| 66 | while (i < s.len) { |
| 67 | const c = s[i]; |
| 68 | |
| 69 | switch (state) { |
| 70 | .MaybeSign => { |
| 71 | state = .Prefix; |
| 72 | |
| 73 | if (c == '+') { |
| 74 | i += 1; |
| 75 | } else if (c == '-') { |
| 76 | negative = true; |
| 77 | i += 1; |
| 78 | } |
| 79 | }, |
| 80 | .Prefix => { |
| 81 | state = .LeadingIntegerDigit; |
| 82 | |
| 83 | // Match both 0x and 0X. |
| 84 | if (i + 2 > s.len or s[i] != '0' or s[i + 1] | 32 != 'x') |
| 85 | return error.InvalidCharacter; |
| 86 | i += 2; |
| 87 | }, |
| 88 | .LeadingIntegerDigit => { |
| 89 | if (c == '0') { |
| 90 | // Skip leading zeros. |
| 91 | i += 1; |
| 92 | } else if (c == '_') { |
| 93 | return error.InvalidCharacter; |
| 94 | } else { |
| 95 | state = .IntegerDigit; |
| 96 | } |
| 97 | }, |
| 98 | .IntegerDigit => { |
| 99 | if (ascii.isXDigit(c)) { |
| 100 | if (mantissa >= math.maxInt(u128) / 16) |
| 101 | return error.Overflow; |
| 102 | mantissa *%= 16; |
| 103 | mantissa += try fmt.charToDigit(c, 16); |
| 104 | i += 1; |
| 105 | } else if (c == '_') { |
| 106 | i += 1; |
| 107 | } else { |
| 108 | state = .MaybeDot; |
| 109 | } |
| 110 | }, |
| 111 | .MaybeDot => { |
| 112 | if (c == '.') { |
| 113 | state = .LeadingFractionDigit; |
| 114 | i += 1; |
| 115 | } else state = .ExpPrefix; |
| 116 | }, |
| 117 | .LeadingFractionDigit => { |
| 118 | if (c == '_') { |
| 119 | return error.InvalidCharacter; |
| 120 | } else state = .FractionDigit; |
| 121 | }, |
| 122 | .FractionDigit => { |
| 123 | if (ascii.isXDigit(c)) { |
| 124 | if (mantissa < math.maxInt(u128) / 16) { |
| 125 | mantissa *%= 16; |
| 126 | mantissa +%= try fmt.charToDigit(c, 16); |
| 127 | frac_scale += 1; |
| 128 | } else if (c != '0') { |
| 129 | return error.Overflow; |
| 130 | } |
| 131 | i += 1; |
| 132 | } else if (c == '_') { |
| 133 | i += 1; |
| 134 | } else { |
| 135 | state = .ExpPrefix; |
| 136 | } |
| 137 | }, |
| 138 | .ExpPrefix => { |
| 139 | state = .MaybeExpSign; |
| 140 | // Match both p and P. |
| 141 | if (c | 32 != 'p') |
| 142 | return error.InvalidCharacter; |
| 143 | i += 1; |
| 144 | }, |
| 145 | .MaybeExpSign => { |
| 146 | state = .ExpDigit; |
| 147 | |
| 148 | if (c == '+') { |
| 149 | i += 1; |
| 150 | } else if (c == '-') { |
| 151 | exp_negative = true; |
| 152 | i += 1; |
| 153 | } |
| 154 | }, |
| 155 | .ExpDigit => { |
| 156 | if (ascii.isXDigit(c)) { |
| 157 | if (exponent >= math.maxInt(i16) / 10) |
| 158 | return error.Overflow; |
| 159 | exponent *%= 10; |
| 160 | exponent +%= try fmt.charToDigit(c, 10); |
| 161 | i += 1; |
| 162 | } else if (c == '_') { |
| 163 | i += 1; |
| 164 | } else { |
| 165 | return error.InvalidCharacter; |
| 166 | } |
| 167 | }, |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | if (exp_negative) |
| 172 | exponent *= -1; |
| 173 | |
| 174 | // Bring the decimal part to the left side of the decimal dot. |
| 175 | exponent -= frac_scale * 4; |
| 176 | |
| 177 | if (mantissa == 0) { |
| 178 | // Signed zero. |
| 179 | return if (negative) -0.0 else 0.0; |
| 180 | } |
| 181 | |
| 182 | // Divide by 2^mantissa_bits to right-align the mantissa in the fractional |
| 183 | // part. |
| 184 | exponent += mantissa_bits; |
| 185 | |
| 186 | // Keep around two extra bits to correctly round any value that doesn't fit |
| 187 | // the available mantissa bits. The result LSB serves as Guard bit, the |
| 188 | // following one is the Round bit and the last one is the Sticky bit, |
| 189 | // computed by OR-ing all the dropped bits. |
| 190 | |
| 191 | // Normalize by aligning the implicit one bit. |
| 192 | while (mantissa >> (mantissa_bits + 2) == 0) { |
| 193 | mantissa <<= 1; |
| 194 | exponent -= 1; |
| 195 | } |
| 196 | |
| 197 | // Normalize again by dropping the excess precision. |
| 198 | // Note that the discarded bits are folded into the Sticky bit. |
| 199 | while (mantissa >> (mantissa_bits + 2 + 1) != 0) { |
| 200 | mantissa = mantissa >> 1 | (mantissa & 1); |
| 201 | exponent += 1; |
| 202 | } |
| 203 | |
| 204 | // Very small numbers can be possibly represented as denormals, reduce the |
| 205 | // exponent as much as possible. |
| 206 | while (mantissa != 0 and exponent < exponent_min - 2) { |
| 207 | mantissa = mantissa >> 1 | (mantissa & 1); |
| 208 | exponent += 1; |
| 209 | } |
| 210 | |
| 211 | // There are two cases to handle: |
| 212 | // - We've truncated more than 0.5ULP (R=S=1), increase the mantissa. |
| 213 | // - We've truncated exactly 0.5ULP (R=1 S=0), increase the mantissa if the |
| 214 | // result is odd (G=1). |
| 215 | // The two checks can be neatly folded as follows. |
| 216 | mantissa |= @boolToInt(mantissa & 0b100 != 0); |
| 217 | mantissa += 1; |
| 218 | |
| 219 | mantissa >>= 2; |
| 220 | exponent += 2; |
| 221 | |
| 222 | if (mantissa & (1 << (mantissa_bits + 1)) != 0) { |
| 223 | // Renormalize, if the exponent overflows we'll catch that below. |
| 224 | mantissa >>= 1; |
| 225 | exponent += 1; |
| 226 | } |
| 227 | |
| 228 | if (mantissa >> mantissa_bits == 0) { |
| 229 | // This is a denormal number, the biased exponent is zero. |
| 230 | exponent = -exponent_bias; |
| 231 | } |
| 232 | |
| 233 | if (exponent > exponent_max) { |
| 234 | // Overflow, return +inf. |
| 235 | return math.inf(T); |
| 236 | } |
| 237 | |
| 238 | // Remove the implicit bit. |
| 239 | mantissa &= @as(u128, (1 << mantissa_bits) - 1); |
| 240 | |
| 241 | const raw: IntT = |
| 242 | (if (negative) @as(IntT, 1) << sign_shift else 0) | |
| 243 | @as(IntT, @bitCast(u16, exponent + exponent_bias)) << mantissa_bits | |
| 244 | @truncate(IntT, mantissa); |
| 245 | |
| 246 | return @bitCast(T, raw); |
| 247 | } |
| 248 | |
| 249 | test "special" { |
| 250 | testing.expect(math.isNan(try parseHexFloat(f32, "nAn"))); |
| 251 | testing.expect(math.isPositiveInf(try parseHexFloat(f32, "iNf"))); |
| 252 | testing.expect(math.isPositiveInf(try parseHexFloat(f32, "+Inf"))); |
| 253 | testing.expect(math.isNegativeInf(try parseHexFloat(f32, "-iNf"))); |
| 254 | } |
| 255 | test "zero" { |
| 256 | testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0")); |
| 257 | testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "-0x0")); |
| 258 | testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0p42")); |
| 259 | testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "-0x0.00000p42")); |
| 260 | testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0.00000p666")); |
| 261 | } |
| 262 | |
| 263 | test "f16" { |
| 264 | const Case = struct { s: []const u8, v: f16 }; |
| 265 | const cases: []const Case = &[_]Case{ |
| 266 | .{ .s = "0x1p0", .v = 1.0 }, |
| 267 | .{ .s = "-0x1p-1", .v = -0.5 }, |
| 268 | .{ .s = "0x10p+10", .v = 16384.0 }, |
| 269 | .{ .s = "0x10p-10", .v = 0.015625 }, |
| 270 | // Max normalized value. |
| 271 | .{ .s = "0x1.ffcp+15", .v = math.f16_max }, |
| 272 | .{ .s = "-0x1.ffcp+15", .v = -math.f16_max }, |
| 273 | // Min normalized value. |
| 274 | .{ .s = "0x1p-14", .v = math.f16_min }, |
| 275 | .{ .s = "-0x1p-14", .v = -math.f16_min }, |
| 276 | // Min denormal value. |
| 277 | .{ .s = "0x1p-24", .v = math.f16_true_min }, |
| 278 | .{ .s = "-0x1p-24", .v = -math.f16_true_min }, |
| 279 | }; |
| 280 | |
| 281 | for (cases) |case| { |
| 282 | testing.expectEqual(case.v, try parseHexFloat(f16, case.s)); |
| 283 | } |
| 284 | } |
| 285 | test "f32" { |
| 286 | const Case = struct { s: []const u8, v: f32 }; |
| 287 | const cases: []const Case = &[_]Case{ |
| 288 | .{ .s = "0x1p0", .v = 1.0 }, |
| 289 | .{ .s = "-0x1p-1", .v = -0.5 }, |
| 290 | .{ .s = "0x10p+10", .v = 16384.0 }, |
| 291 | .{ .s = "0x10p-10", .v = 0.015625 }, |
| 292 | .{ .s = "0x0.ffffffp128", .v = 0x0.ffffffp128 }, |
| 293 | .{ .s = "0x0.1234570p-125", .v = 0x0.1234570p-125 }, |
| 294 | // Max normalized value. |
| 295 | .{ .s = "0x1.fffffeP+127", .v = math.f32_max }, |
| 296 | .{ .s = "-0x1.fffffeP+127", .v = -math.f32_max }, |
| 297 | // Min normalized value. |
| 298 | .{ .s = "0x1p-126", .v = math.f32_min }, |
| 299 | .{ .s = "-0x1p-126", .v = -math.f32_min }, |
| 300 | // Min denormal value. |
| 301 | .{ .s = "0x1P-149", .v = math.f32_true_min }, |
| 302 | .{ .s = "-0x1P-149", .v = -math.f32_true_min }, |
| 303 | }; |
| 304 | |
| 305 | for (cases) |case| { |
| 306 | testing.expectEqual(case.v, try parseHexFloat(f32, case.s)); |
| 307 | } |
| 308 | } |
| 309 | test "f64" { |
| 310 | const Case = struct { s: []const u8, v: f64 }; |
| 311 | const cases: []const Case = &[_]Case{ |
| 312 | .{ .s = "0x1p0", .v = 1.0 }, |
| 313 | .{ .s = "-0x1p-1", .v = -0.5 }, |
| 314 | .{ .s = "0x10p+10", .v = 16384.0 }, |
| 315 | .{ .s = "0x10p-10", .v = 0.015625 }, |
| 316 | // Max normalized value. |
| 317 | .{ .s = "0x1.fffffffffffffp+1023", .v = math.f64_max }, |
| 318 | .{ .s = "-0x1.fffffffffffffp1023", .v = -math.f64_max }, |
| 319 | // Min normalized value. |
| 320 | .{ .s = "0x1p-1022", .v = math.f64_min }, |
| 321 | .{ .s = "-0x1p-1022", .v = -math.f64_min }, |
| 322 | // Min denormalized value. |
| 323 | .{ .s = "0x1p-1074", .v = math.f64_true_min }, |
| 324 | .{ .s = "-0x1p-1074", .v = -math.f64_true_min }, |
| 325 | }; |
| 326 | |
| 327 | for (cases) |case| { |
| 328 | testing.expectEqual(case.v, try parseHexFloat(f64, case.s)); |
| 329 | } |
| 330 | } |
| 331 | test "f128" { |
| 332 | const Case = struct { s: []const u8, v: f128 }; |
| 333 | const cases: []const Case = &[_]Case{ |
| 334 | .{ .s = "0x1p0", .v = 1.0 }, |
| 335 | .{ .s = "-0x1p-1", .v = -0.5 }, |
| 336 | .{ .s = "0x10p+10", .v = 16384.0 }, |
| 337 | .{ .s = "0x10p-10", .v = 0.015625 }, |
| 338 | // Max normalized value. |
| 339 | .{ .s = "0xf.fffffffffffffffffffffffffff8p+16380", .v = math.f128_max }, |
| 340 | .{ .s = "-0xf.fffffffffffffffffffffffffff8p+16380", .v = -math.f128_max }, |
| 341 | // Min normalized value. |
| 342 | .{ .s = "0x1p-16382", .v = math.f128_min }, |
| 343 | .{ .s = "-0x1p-16382", .v = -math.f128_min }, |
| 344 | // // Min denormalized value. |
| 345 | .{ .s = "0x1p-16494", .v = math.f128_true_min }, |
| 346 | .{ .s = "-0x1p-16494", .v = -math.f128_true_min }, |
| 347 | }; |
| 348 | |
| 349 | for (cases) |case| { |
| 350 | testing.expectEqual(@bitCast(u128, case.v), @bitCast(u128, try parseHexFloat(f128, case.s))); |
| 351 | } |
| 352 | } |