hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 1849e53c71b71422a7f4b28a3a287ec7a772279e
parent 699fb637b93d19e19cbd97e593fb135685f4406d
Author: Sudipto Mallick <smlckz@disroot.org>
Date:   Thu,  8 Jul 2021 16:35:24 +0000

math/floats.ha: functions and constants related to floats

Signed-off-by: Sudipto Mallick <smlckz@disroot.org>

Diffstat:
Amath/floats.ha | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 10+++++++++-
Mstdlib.mk | 34++++++++++++++++++++++++++++++++--
Mstrconv/ftos.ha | 46+++++++++++++++++++---------------------------
4 files changed, 290 insertions(+), 30 deletions(-)

diff --git a/math/floats.ha b/math/floats.ha @@ -0,0 +1,230 @@ +// The floating point value representing Not a Number, i.e. an undefined or +// unrepresentable value. You cannot test if a number is NaN by comparing to +// this value; see [[isnan]] instead. +export def NAN: f32 = 0.0 / 0.0; + +// The floating point value representing positive infinity. Use -[[INF]] for +// negative infinity. +export def INF: f32 = 1.0 / 0.0; + +// Returns true if the given floating-point number is NaN. +export fn isnan(n: f64) bool = n != n; + +@test fn isnan() void = { + assert(isnan(NAN)); + assert(isnan(-NAN)); + assert(isnan(f64frombits(0xfffabcdef1234567))); + assert(!isnan(INF)); + assert(!isnan(1.23f32)); +}; + +// Returns true if the given floating-point number is infinite. +export fn isinf(n: f64) bool = { + const bits = f64bits(n); + const mant = bits & F64_MANTISSA_MASK; + const exp = bits >> F64_MANTISSA_BITS & F64_EXPONENT_MASK; + return exp == F64_EXPONENT_MASK && mant == 0; +}; + +@test fn isinf() void = { + assert(isinf(INF)); + assert(isinf(-INF)); + assert(!isinf(NAN)); + assert(!isinf(1.23)); + assert(!isinf(-1.23f32)); +}; + +// Returns true if the given floating-point number is normal. +export fn isnormal(n: (f32 | f64)) bool = { + return match (n) { + n: f32 => isnormalf32(n), + n: f64 => isnormalf64(n), + }; +}; + +// Returns true if the given f32 is normal. +export fn isnormalf32(n: f32) bool = { + const bits = f32bits(n); + const mant = bits & F32_MANTISSA_MASK; + const exp = bits >> F32_MANTISSA_BITS & F32_EXPONENT_MASK; + return exp != F32_EXPONENT_MASK && (exp > 0 || mant == 0); +}; + +// Returns true if the given f64 is normal. +export fn isnormalf64(n: f64) bool = { + const bits = f64bits(n); + const mant = bits & F64_MANTISSA_MASK; + const exp = bits >> F64_MANTISSA_BITS & F64_EXPONENT_MASK; + return exp != F64_EXPONENT_MASK && (exp > 0 || mant == 0); +}; + +// Returns true if the given floating-point number is subnormal. +export fn issubnormal(n: (f32 | f64)) bool = { + return match (n) { + n: f32 => issubnormalf32(n), + n: f64 => issubnormalf64(n), + }; +}; + +// Returns true if the given f32 is subnormal. +export fn issubnormalf32(n: f32) bool = { + const bits = f32bits(n); + const mant = bits & F32_MANTISSA_MASK; + const exp = bits >> F32_MANTISSA_BITS & F32_EXPONENT_MASK; + return exp == 0 && mant != 0; +}; + +// Returns true if the given f64 is subnormal. +export fn issubnormalf64(n: f64) bool = { + const bits = f64bits(n); + const mant = bits & F64_MANTISSA_MASK; + const exp = bits >> F64_MANTISSA_BITS & F64_EXPONENT_MASK; + return exp == 0 && mant != 0; +}; + +@test fn float_normality() void = { + assert(isnormal(0.0)); + assert(isnormal(1.0)); + assert(!isnormal(NAN)); + assert(!isnormal(INF)); + assert(!isnormal(1.0e-310)); + assert(!isnormal(1.0e-40f32)); + + assert(isnormalf32(1.0)); + assert(isnormalf32(0.0)); + assert(!isnormalf32(NAN)); + assert(!isnormalf32(INF)); + assert(!isnormalf32(-1.0e-40)); + assert(isnormalf32(-1.0e-50)); + + assert(isnormalf64(1.0)); + assert(isnormalf64(0.0)); + assert(!isnormalf64(NAN)); + assert(!isnormalf64(INF)); + assert(!isnormalf64(-1.0e-320)); + assert(isnormalf64(-1.0e-330)); + + assert(issubnormal(1.0e-320)); + assert(issubnormal(1.0e-42f32)); + assert(!issubnormal(NAN)); + assert(!issubnormal(INF)); + assert(!issubnormal(1.0)); + assert(!issubnormal(0.0)); + + assert(issubnormalf32(1.0e-45)); + assert(issubnormalf32(-1.0e-39)); + assert(!issubnormalf32(-NAN)); + assert(!issubnormalf32(-INF)); + assert(!issubnormalf32(0.0)); + assert(!issubnormalf32(-1.0e-49)); + + assert(issubnormalf64(5.0e-324)); + assert(issubnormalf64(-2.0e-310)); + assert(!issubnormalf64(-NAN)); + assert(!issubnormalf64(-INF)); + assert(!issubnormalf64(-1.0e-400)); + assert(!issubnormalf64(0.0)); +}; + +// Returns the binary representation of the given f64. +export fn f64bits(n: f64) u64 = *(&n: *u64); + +// Returns the binary representation of the given f32. +export fn f32bits(n: f32) u32 = *(&n: *u32); + +// Returns f64 with the given binary representation. +export fn f64frombits(n: u64) f64 = *(&n: *f64); + +// Returns f32 with the given binary representation. +export fn f32frombits(n: u32) f32 = *(&n: *f32); + +@test fn floatbits() void = { + const a: [_]f64 = [INF, -INF, 0.0, 1.0, -1.0, 123456789.0, + F64_MIN, F64_MIN_NORMAL, F64_MAX_NORMAL]; + for (let i = 0z; i < len(a); i += 1) { + assert(f64frombits(f64bits(a[i])) == a[i]); + }; + const a: [_]f32 = [INF, -INF, 0.0, 1.0, -1.0, -123456.0, + F32_MIN, F32_MIN_NORMAL, F32_MAX_NORMAL]; + for (let i = 0z; i < len(a); i += 1) { + assert(f32frombits(f32bits(a[i])) == a[i]); + }; +}; + +// The number of bits in the significand of the binary representation of f64. +export def F64_MANTISSA_BITS: u64 = 52; + +// The number of bits in the exponent of the binary representation of f64. +export def F64_EXPONENT_BITS: u64 = 11; + +// The bias of the exponent of the binary representation of f64. Subtract this +// from the exponent in the binary representation to get the actual exponent. +export def F64_EXPONENT_BIAS: u16 = 1023; + +// The number of bits in the significand of the binary representation of f32. +export def F32_MANTISSA_BITS: u64 = 23; + +// The number of bits in the exponent of the binary representation of f32. +export def F32_EXPONENT_BITS: u64 = 8; + +// The bias of the exponent of the binary representation of f32. Subtract this +// from the exponent in the binary representation to get the actual exponent. +export def F32_EXPONENT_BIAS: u16 = 127; + +def F64_MANTISSA_MASK: u64 = (1 << F64_MANTISSA_BITS) - 1; +def F64_EXPONENT_MASK: u64 = (1 << F64_EXPONENT_BITS) - 1; + +def F32_MANTISSA_MASK: u64 = (1 << F32_MANTISSA_BITS) - 1; +def F32_EXPONENT_MASK: u64 = (1 << F32_EXPONENT_BITS) - 1; + +// The largest representable f64 value which is less than Infinity. +export def F64_MAX_NORMAL: f64 = 1.7976931348623157e+308; + +// The smallest representable normal f64 value. +export def F64_MIN_NORMAL: f64 = 2.2250738585072014e-308; + +// THe smallest (subnormal) f64 value greater than zero. +export def F64_MIN: f64 = 5.0e-324; + +// The largest representable f32 value which is less than Infinity. +export def F32_MAX_NORMAL: f32 = 3.4028234e+38; + +// The smallest representable normal f32 value. +export def F32_MIN_NORMAL: f32 = 1.1754944e-38; + +// The smallest (subnormal) f32 value greater than zero. +export def F32_MIN: f32 = 1.0e-45; + +// Contains information about the structure of a specific floating point number +// type. +export type floatinfo = struct { + // Bits in significand. + mantbits: u64, + // Bits in exponent. + expbits: u64, + // Bias of exponent. + expbias: int, + // Mask for mantissa. + mantmask: u64, + // Mask for exponent. + expmask: u64, +}; + +// A [[floatinfo]] structure defining the structure of the f64 type. +export const f64info: floatinfo = floatinfo { + mantbits = 52, + expbits = 11, + expbias = 1023, + mantmask = (1 << 52) - 1, + expmask = (1 << 11) - 1, +}; + +// A [[floatinfo]] structure defining the structure of the f32 type. +export const f32info: floatinfo = floatinfo { + mantbits = 23, + expbits = 8, + expbias = 127, + mantmask = (1 << 23) - 1, + expmask = (1 << 8) - 1, +}; + diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -541,6 +541,13 @@ linux_vdso() { gen_ssa linux::vdso linux strings format::elf } +math() { + printf '# math\n' + gen_srcs math \ + floats.ha + gen_ssa math +} + net() { printf '# net\n' gen_srcs net \ @@ -704,7 +711,7 @@ strconv() { +test/stou.ha \ +test/stoi.ha fi - gen_ssa strconv types strings ascii + gen_ssa strconv types strings ascii math } strings() { @@ -846,6 +853,7 @@ linux linux::signalfd linux::io_uring linux::vdso +math net net::dial net::dns diff --git a/stdlib.mk b/stdlib.mk @@ -241,6 +241,10 @@ hare_stdlib_deps+=$(stdlib_linux_io_uring) stdlib_linux_vdso=$(HARECACHE)/linux/vdso/linux_vdso.o hare_stdlib_deps+=$(stdlib_linux_vdso) +# gen_lib math +stdlib_math=$(HARECACHE)/math/math.o +hare_stdlib_deps+=$(stdlib_math) + # gen_lib net stdlib_net=$(HARECACHE)/net/net.o hare_stdlib_deps+=$(stdlib_net) @@ -819,6 +823,17 @@ $(HARECACHE)/linux/vdso/linux_vdso.ssa: $(stdlib_linux_vdso_srcs) $(stdlib_rt) $ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nlinux::vdso \ -t$(HARECACHE)/linux/vdso/linux_vdso.td $(stdlib_linux_vdso_srcs) +# math +# math +stdlib_math_srcs= \ + $(STDLIB)/math/floats.ha + +$(HARECACHE)/math/math.ssa: $(stdlib_math_srcs) $(stdlib_rt) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/math + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nmath \ + -t$(HARECACHE)/math/math.td $(stdlib_math_srcs) + # net # net stdlib_net_srcs= \ @@ -999,7 +1014,7 @@ stdlib_strconv_srcs= \ $(STDLIB)/strconv/ftos.ha \ $(STDLIB)/strconv/stof.ha -$(HARECACHE)/strconv/strconv.ssa: $(stdlib_strconv_srcs) $(stdlib_rt) $(stdlib_types) $(stdlib_strings) $(stdlib_ascii) +$(HARECACHE)/strconv/strconv.ssa: $(stdlib_strconv_srcs) $(stdlib_rt) $(stdlib_types) $(stdlib_strings) $(stdlib_ascii) $(stdlib_math) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/strconv @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nstrconv \ @@ -1392,6 +1407,10 @@ hare_testlib_deps+=$(testlib_linux_io_uring) testlib_linux_vdso=$(TESTCACHE)/linux/vdso/linux_vdso.o hare_testlib_deps+=$(testlib_linux_vdso) +# gen_lib math +testlib_math=$(TESTCACHE)/math/math.o +hare_testlib_deps+=$(testlib_math) + # gen_lib net testlib_net=$(TESTCACHE)/net/net.o hare_testlib_deps+=$(testlib_net) @@ -1990,6 +2009,17 @@ $(TESTCACHE)/linux/vdso/linux_vdso.ssa: $(testlib_linux_vdso_srcs) $(testlib_rt) @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nlinux::vdso \ -t$(TESTCACHE)/linux/vdso/linux_vdso.td $(testlib_linux_vdso_srcs) +# math +# math +testlib_math_srcs= \ + $(STDLIB)/math/floats.ha + +$(TESTCACHE)/math/math.ssa: $(testlib_math_srcs) $(testlib_rt) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/math + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nmath \ + -t$(TESTCACHE)/math/math.td $(testlib_math_srcs) + # net # net testlib_net_srcs= \ @@ -2174,7 +2204,7 @@ testlib_strconv_srcs= \ $(STDLIB)/strconv/+test/stou.ha \ $(STDLIB)/strconv/+test/stoi.ha -$(TESTCACHE)/strconv/strconv.ssa: $(testlib_strconv_srcs) $(testlib_rt) $(testlib_types) $(testlib_strings) $(testlib_ascii) +$(TESTCACHE)/strconv/strconv.ssa: $(testlib_strconv_srcs) $(testlib_rt) $(testlib_types) $(testlib_strings) $(testlib_ascii) $(testlib_math) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/strconv @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nstrconv \ diff --git a/strconv/ftos.ha b/strconv/ftos.ha @@ -3,11 +3,9 @@ // This Hare implementation is translated from the original // C implementation here: https://github.com/ulfjack/ryu +use math; use types; -fn f64bits(a: f64) u64 = *(&a: *u64); // XXX: ARCH -fn f32bits(a: f32) u32 = *(&a: *u32); // XXX: ARCH - type r128 = struct { hi: u64, lo: u64, @@ -285,19 +283,15 @@ type decf64 = struct { exponent: i32, }; -def F64_MANTISSA_BITS: u64 = 52; -def F64_EXPONENT_BITS: u64 = 11; -def F64_EXPONENT_BIAS: u16 = 1023; - fn f64todecf64(mantissa: u64, exponent: u32) decf64 = { - let e2 = (F64_EXPONENT_BIAS + F64_MANTISSA_BITS + 2): i32; + let e2 = (math::F64_EXPONENT_BIAS + math::F64_MANTISSA_BITS + 2): i32; let m2: u64 = 0; if (exponent == 0) { e2 = 1 - e2; m2 = mantissa; } else { e2 = (exponent: i32) - e2; - m2 = (1u32 << F64_MANTISSA_BITS) | mantissa; + m2 = (1u64 << math::F64_MANTISSA_BITS) | mantissa; }; const accept_bounds = (m2 & 1) == 0; const mv = 4 * m2; @@ -407,19 +401,15 @@ type decf32 = struct { exponent: i32, }; -def F32_MANTISSA_BITS: u32 = 23; -def F32_EXPONENT_BITS: u32 = 8; -def F32_EXPONENT_BIAS: u16 = 127; - fn f32todecf32(mantissa: u32, exponent: u32) decf32 = { - let e2 = (F32_EXPONENT_BIAS + F32_MANTISSA_BITS + 2): i32; + let e2 = (math::F32_EXPONENT_BIAS + math::F32_MANTISSA_BITS + 2): i32; let m2: u32 = 0; if (exponent == 0) { e2 = 1 - e2; m2 = mantissa; } else { e2 = (exponent: i32) - e2; - m2 = (1u32 << F32_MANTISSA_BITS) | mantissa; + m2 = (1u32 << math::F32_MANTISSA_BITS: u32) | mantissa; }; const accept_bounds = (m2 & 1) == 0; const mv = 4 * m2, mp = mv + 2; @@ -629,14 +619,15 @@ export fn f64tos(n: f64) const str = { // sign and the maximum of three digits for exponent. // (1 + 1 + 1 + 16 + 1 + 1 + 3) = 24 static let buf: [24]u8 = [0...]; - const bits = f64bits(n); - const sign = (bits >> (F64_MANTISSA_BITS + F64_EXPONENT_BITS)): size; - const mantissa = bits & ((1u64 << F64_MANTISSA_BITS) - 1); - const exponent = ((bits >> F64_MANTISSA_BITS) & - (1u64 << F64_EXPONENT_BITS) - 1): u32; + const bits = math::f64bits(n); + const sign = (bits >> (math::F64_MANTISSA_BITS + + math::F64_EXPONENT_BITS)): size; // bits >> 63 + const mantissa = bits & ((1u64 << math::F64_MANTISSA_BITS) - 1); + const exponent = ((bits >> math::F64_MANTISSA_BITS) & + (1u64 << math::F64_EXPONENT_BITS) - 1): u32; if (mantissa == 0 && exponent == 0) { return "0"; - } else if (exponent == ((1 << F64_EXPONENT_BITS) - 1)) { + } else if (exponent == ((1 << math::F64_EXPONENT_BITS) - 1)) { if (mantissa != 0) { return "NaN"; }; @@ -662,14 +653,15 @@ export fn f32tos(n: f32) const str = { // the maximum of two digits for exponent. // (1 + 1 + 1 + 7 + 1 + 1 + 2) = 14 static let buf: [16]u8 = [0...]; - const bits = f32bits(n); - const sign = bits >> (F32_MANTISSA_BITS + F32_EXPONENT_BITS); - const mantissa = bits & ((1u32 << F32_MANTISSA_BITS) - 1); - const exponent = (bits >> F32_MANTISSA_BITS) & - ((1u32 << F32_EXPONENT_BITS) - 1); + const bits = math::f32bits(n); + const sign = (bits >> (math::F32_MANTISSA_BITS + + math::F32_EXPONENT_BITS)): size; // bits >> 31 + const mantissa = bits & ((1u32 << math::F32_MANTISSA_BITS) - 1): u32; + const exponent = (bits >> math::F32_MANTISSA_BITS): u32 & + ((1u32 << math::F32_EXPONENT_BITS) - 1): u32; if (mantissa == 0 && exponent == 0) { return "0"; - } else if (exponent == ((1 << F32_EXPONENT_BITS) - 1)) { + } else if (exponent == ((1 << math::F32_EXPONENT_BITS) - 1): u32) { if (mantissa != 0) { return "NaN"; };