mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 06:15:21 +00:00
std: Add a parser for hexadecimal floating point numbers
Should be good enough to unblock progress on the stage2 compiler. Unifying this parser and the regular one (and perhaps rewrite it, #2207) is left as an exercise for the reader.
This commit is contained in:
parent
86e129eff6
commit
55c58f226d
@ -1506,9 +1506,11 @@ test "parseUnsigned" {
|
||||
}
|
||||
|
||||
pub const parseFloat = @import("fmt/parse_float.zig").parseFloat;
|
||||
pub const parseHexFloat = @import("fmt/parse_hex_float.zig").parseHexFloat;
|
||||
|
||||
test "parseFloat" {
|
||||
test {
|
||||
_ = @import("fmt/parse_float.zig");
|
||||
_ = @import("fmt/parse_hex_float.zig");
|
||||
}
|
||||
|
||||
pub fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) {
|
||||
|
||||
352
lib/std/fmt/parse_hex_float.zig
Normal file
352
lib/std/fmt/parse_hex_float.zig
Normal file
@ -0,0 +1,352 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2015-2021 Zig Contributors
|
||||
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
||||
// The MIT license requires this copyright notice to be included in all copies
|
||||
// and substantial portions of the software.const std = @import("std");
|
||||
//
|
||||
// The rounding logic is inspired by LLVM's APFloat and Go's atofHex
|
||||
// implementation.
|
||||
|
||||
const std = @import("std");
|
||||
const ascii = std.ascii;
|
||||
const fmt = std.fmt;
|
||||
const math = std.math;
|
||||
const testing = std.testing;
|
||||
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub fn parseHexFloat(comptime T: type, s: []const u8) !T {
|
||||
assert(@typeInfo(T) == .Float);
|
||||
|
||||
const IntT = std.meta.Int(.unsigned, @typeInfo(T).Float.bits);
|
||||
|
||||
const mantissa_bits = math.floatMantissaBits(T);
|
||||
const exponent_bits = math.floatExponentBits(T);
|
||||
|
||||
const sign_shift = mantissa_bits + exponent_bits;
|
||||
|
||||
const exponent_bias = (1 << (exponent_bits - 1)) - 1;
|
||||
const exponent_min = 1 - exponent_bias;
|
||||
const exponent_max = exponent_bias;
|
||||
|
||||
if (s.len == 0)
|
||||
return error.InvalidCharacter;
|
||||
|
||||
if (ascii.eqlIgnoreCase(s, "nan")) {
|
||||
return math.nan(T);
|
||||
} else if (ascii.eqlIgnoreCase(s, "inf") or ascii.eqlIgnoreCase(s, "+inf")) {
|
||||
return math.inf(T);
|
||||
} else if (ascii.eqlIgnoreCase(s, "-inf")) {
|
||||
return -math.inf(T);
|
||||
}
|
||||
|
||||
var negative: bool = false;
|
||||
var exp_negative: bool = false;
|
||||
|
||||
var mantissa: u128 = 0;
|
||||
var exponent: i16 = 0;
|
||||
var frac_scale: i16 = 0;
|
||||
|
||||
const State = enum {
|
||||
MaybeSign,
|
||||
Prefix,
|
||||
LeadingIntegerDigit,
|
||||
IntegerDigit,
|
||||
MaybeDot,
|
||||
LeadingFractionDigit,
|
||||
FractionDigit,
|
||||
ExpPrefix,
|
||||
MaybeExpSign,
|
||||
ExpDigit,
|
||||
};
|
||||
|
||||
var state = State.MaybeSign;
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < s.len) {
|
||||
const c = s[i];
|
||||
|
||||
switch (state) {
|
||||
.MaybeSign => {
|
||||
state = .Prefix;
|
||||
|
||||
if (c == '+') {
|
||||
i += 1;
|
||||
} else if (c == '-') {
|
||||
negative = true;
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
.Prefix => {
|
||||
state = .LeadingIntegerDigit;
|
||||
|
||||
// Match both 0x and 0X.
|
||||
if (i + 2 > s.len or s[i] != '0' or s[i + 1] | 32 != 'x')
|
||||
return error.InvalidCharacter;
|
||||
i += 2;
|
||||
},
|
||||
.LeadingIntegerDigit => {
|
||||
if (c == '0') {
|
||||
// Skip leading zeros.
|
||||
i += 1;
|
||||
} else if (c == '_') {
|
||||
return error.InvalidCharacter;
|
||||
} else {
|
||||
state = .IntegerDigit;
|
||||
}
|
||||
},
|
||||
.IntegerDigit => {
|
||||
if (ascii.isXDigit(c)) {
|
||||
if (mantissa >= math.maxInt(u128) / 16)
|
||||
return error.Overflow;
|
||||
mantissa *%= 16;
|
||||
mantissa += try fmt.charToDigit(c, 16);
|
||||
i += 1;
|
||||
} else if (c == '_') {
|
||||
i += 1;
|
||||
} else {
|
||||
state = .MaybeDot;
|
||||
}
|
||||
},
|
||||
.MaybeDot => {
|
||||
if (c == '.') {
|
||||
state = .LeadingFractionDigit;
|
||||
i += 1;
|
||||
} else state = .ExpPrefix;
|
||||
},
|
||||
.LeadingFractionDigit => {
|
||||
if (c == '_') {
|
||||
return error.InvalidCharacter;
|
||||
} else state = .FractionDigit;
|
||||
},
|
||||
.FractionDigit => {
|
||||
if (ascii.isXDigit(c)) {
|
||||
if (mantissa < math.maxInt(u128) / 16) {
|
||||
mantissa *%= 16;
|
||||
mantissa +%= try fmt.charToDigit(c, 16);
|
||||
frac_scale += 1;
|
||||
} else if (c != '0') {
|
||||
return error.Overflow;
|
||||
}
|
||||
i += 1;
|
||||
} else if (c == '_') {
|
||||
i += 1;
|
||||
} else {
|
||||
state = .ExpPrefix;
|
||||
}
|
||||
},
|
||||
.ExpPrefix => {
|
||||
state = .MaybeExpSign;
|
||||
// Match both p and P.
|
||||
if (c | 32 != 'p')
|
||||
return error.InvalidCharacter;
|
||||
i += 1;
|
||||
},
|
||||
.MaybeExpSign => {
|
||||
state = .ExpDigit;
|
||||
|
||||
if (c == '+') {
|
||||
i += 1;
|
||||
} else if (c == '-') {
|
||||
exp_negative = true;
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
.ExpDigit => {
|
||||
if (ascii.isXDigit(c)) {
|
||||
if (exponent >= math.maxInt(i16) / 10)
|
||||
return error.Overflow;
|
||||
exponent *%= 10;
|
||||
exponent +%= try fmt.charToDigit(c, 10);
|
||||
i += 1;
|
||||
} else if (c == '_') {
|
||||
i += 1;
|
||||
} else {
|
||||
return error.InvalidCharacter;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (exp_negative)
|
||||
exponent *= -1;
|
||||
|
||||
// Bring the decimal part to the left side of the decimal dot.
|
||||
exponent -= frac_scale * 4;
|
||||
|
||||
if (mantissa == 0) {
|
||||
// Signed zero.
|
||||
return if (negative) -0.0 else 0.0;
|
||||
}
|
||||
|
||||
// Divide by 2^mantissa_bits to right-align the mantissa in the fractional
|
||||
// part.
|
||||
exponent += mantissa_bits;
|
||||
|
||||
// Keep around two extra bits to correctly round any value that doesn't fit
|
||||
// the available mantissa bits. The result LSB serves as Guard bit, the
|
||||
// following one is the Round bit and the last one is the Sticky bit,
|
||||
// computed by OR-ing all the dropped bits.
|
||||
|
||||
// Normalize by aligning the implicit one bit.
|
||||
while (mantissa >> (mantissa_bits + 2) == 0) {
|
||||
mantissa <<= 1;
|
||||
exponent -= 1;
|
||||
}
|
||||
|
||||
// Normalize again by dropping the excess precision.
|
||||
// Note that the discarded bits are folded into the Sticky bit.
|
||||
while (mantissa >> (mantissa_bits + 2 + 1) != 0) {
|
||||
mantissa = mantissa >> 1 | (mantissa & 1);
|
||||
exponent += 1;
|
||||
}
|
||||
|
||||
// Very small numbers can be possibly represented as denormals, reduce the
|
||||
// exponent as much as possible.
|
||||
while (mantissa != 0 and exponent < exponent_min - 2) {
|
||||
mantissa = mantissa >> 1 | (mantissa & 1);
|
||||
exponent += 1;
|
||||
}
|
||||
|
||||
// There are two cases to handle:
|
||||
// - We've truncated more than 0.5ULP (R=S=1), increase the mantissa.
|
||||
// - We've truncated exactly 0.5ULP (R=1 S=0), increase the mantissa if the
|
||||
// result is odd (G=1).
|
||||
// The two checks can be neatly folded as follows.
|
||||
mantissa |= @boolToInt(mantissa & 0b100 != 0);
|
||||
mantissa += 1;
|
||||
|
||||
mantissa >>= 2;
|
||||
exponent += 2;
|
||||
|
||||
if (mantissa & (1 << (mantissa_bits + 1)) != 0) {
|
||||
// Renormalize, if the exponent overflows we'll catch that below.
|
||||
mantissa >>= 1;
|
||||
exponent += 1;
|
||||
}
|
||||
|
||||
if (mantissa >> mantissa_bits == 0) {
|
||||
// This is a denormal number, the biased exponent is zero.
|
||||
exponent = -exponent_bias;
|
||||
}
|
||||
|
||||
if (exponent > exponent_max) {
|
||||
// Overflow, return +inf.
|
||||
return math.inf(T);
|
||||
}
|
||||
|
||||
// Remove the implicit bit.
|
||||
mantissa &= @as(u128, (1 << mantissa_bits) - 1);
|
||||
|
||||
const raw: IntT =
|
||||
(if (negative) @as(IntT, 1) << sign_shift else 0) |
|
||||
@as(IntT, @bitCast(u16, exponent + exponent_bias)) << mantissa_bits |
|
||||
@truncate(IntT, mantissa);
|
||||
|
||||
return @bitCast(T, raw);
|
||||
}
|
||||
|
||||
test "special" {
|
||||
testing.expect(math.isNan(try parseHexFloat(f32, "nAn")));
|
||||
testing.expect(math.isPositiveInf(try parseHexFloat(f32, "iNf")));
|
||||
testing.expect(math.isPositiveInf(try parseHexFloat(f32, "+Inf")));
|
||||
testing.expect(math.isNegativeInf(try parseHexFloat(f32, "-iNf")));
|
||||
}
|
||||
test "zero" {
|
||||
testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0"));
|
||||
testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "-0x0"));
|
||||
testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0p42"));
|
||||
testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "-0x0.00000p42"));
|
||||
testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0.00000p666"));
|
||||
}
|
||||
|
||||
test "f16" {
|
||||
const Case = struct { s: []const u8, v: f16 };
|
||||
const cases: []const Case = &[_]Case{
|
||||
.{ .s = "0x1p0", .v = 1.0 },
|
||||
.{ .s = "-0x1p-1", .v = -0.5 },
|
||||
.{ .s = "0x10p+10", .v = 16384.0 },
|
||||
.{ .s = "0x10p-10", .v = 0.015625 },
|
||||
// Max normalized value.
|
||||
.{ .s = "0x1.ffcp+15", .v = math.f16_max },
|
||||
.{ .s = "-0x1.ffcp+15", .v = -math.f16_max },
|
||||
// Min normalized value.
|
||||
.{ .s = "0x1p-14", .v = math.f16_min },
|
||||
.{ .s = "-0x1p-14", .v = -math.f16_min },
|
||||
// Min denormal value.
|
||||
.{ .s = "0x1p-24", .v = math.f16_true_min },
|
||||
.{ .s = "-0x1p-24", .v = -math.f16_true_min },
|
||||
};
|
||||
|
||||
for (cases) |case| {
|
||||
testing.expectEqual(case.v, try parseHexFloat(f16, case.s));
|
||||
}
|
||||
}
|
||||
test "f32" {
|
||||
const Case = struct { s: []const u8, v: f32 };
|
||||
const cases: []const Case = &[_]Case{
|
||||
.{ .s = "0x1p0", .v = 1.0 },
|
||||
.{ .s = "-0x1p-1", .v = -0.5 },
|
||||
.{ .s = "0x10p+10", .v = 16384.0 },
|
||||
.{ .s = "0x10p-10", .v = 0.015625 },
|
||||
.{ .s = "0x0.ffffffp128", .v = 0x0.ffffffp128 },
|
||||
.{ .s = "0x0.1234570p-125", .v = 0x0.1234570p-125 },
|
||||
// Max normalized value.
|
||||
.{ .s = "0x1.fffffeP+127", .v = math.f32_max },
|
||||
.{ .s = "-0x1.fffffeP+127", .v = -math.f32_max },
|
||||
// Min normalized value.
|
||||
.{ .s = "0x1p-126", .v = math.f32_min },
|
||||
.{ .s = "-0x1p-126", .v = -math.f32_min },
|
||||
// Min denormal value.
|
||||
.{ .s = "0x1P-149", .v = math.f32_true_min },
|
||||
.{ .s = "-0x1P-149", .v = -math.f32_true_min },
|
||||
};
|
||||
|
||||
for (cases) |case| {
|
||||
testing.expectEqual(case.v, try parseHexFloat(f32, case.s));
|
||||
}
|
||||
}
|
||||
test "f64" {
|
||||
const Case = struct { s: []const u8, v: f64 };
|
||||
const cases: []const Case = &[_]Case{
|
||||
.{ .s = "0x1p0", .v = 1.0 },
|
||||
.{ .s = "-0x1p-1", .v = -0.5 },
|
||||
.{ .s = "0x10p+10", .v = 16384.0 },
|
||||
.{ .s = "0x10p-10", .v = 0.015625 },
|
||||
// Max normalized value.
|
||||
.{ .s = "0x1.fffffffffffffp+1023", .v = math.f64_max },
|
||||
.{ .s = "-0x1.fffffffffffffp1023", .v = -math.f64_max },
|
||||
// Min normalized value.
|
||||
.{ .s = "0x1p-1022", .v = math.f64_min },
|
||||
.{ .s = "-0x1p-1022", .v = -math.f64_min },
|
||||
// Min denormalized value.
|
||||
.{ .s = "0x1p-1074", .v = math.f64_true_min },
|
||||
.{ .s = "-0x1p-1074", .v = -math.f64_true_min },
|
||||
};
|
||||
|
||||
for (cases) |case| {
|
||||
testing.expectEqual(case.v, try parseHexFloat(f64, case.s));
|
||||
}
|
||||
}
|
||||
test "f128" {
|
||||
const Case = struct { s: []const u8, v: f128 };
|
||||
const cases: []const Case = &[_]Case{
|
||||
.{ .s = "0x1p0", .v = 1.0 },
|
||||
.{ .s = "-0x1p-1", .v = -0.5 },
|
||||
.{ .s = "0x10p+10", .v = 16384.0 },
|
||||
.{ .s = "0x10p-10", .v = 0.015625 },
|
||||
// Max normalized value.
|
||||
.{ .s = "0xf.fffffffffffffffffffffffffff8p+16380", .v = math.f128_max },
|
||||
.{ .s = "-0xf.fffffffffffffffffffffffffff8p+16380", .v = -math.f128_max },
|
||||
// Min normalized value.
|
||||
.{ .s = "0x1p-16382", .v = math.f128_min },
|
||||
.{ .s = "-0x1p-16382", .v = -math.f128_min },
|
||||
// // Min denormalized value.
|
||||
.{ .s = "0x1p-16494", .v = math.f128_true_min },
|
||||
.{ .s = "-0x1p-16494", .v = -math.f128_true_min },
|
||||
};
|
||||
|
||||
for (cases) |case| {
|
||||
testing.expectEqual(@bitCast(u128, case.v), @bitCast(u128, try parseHexFloat(f128, case.s)));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user