diff --git a/headers/gensym/defs.hpp b/headers/gensym/defs.hpp index 705de0f0..4a3c5610 100644 --- a/headers/gensym/defs.hpp +++ b/headers/gensym/defs.hpp @@ -12,6 +12,7 @@ using Id = int; using Addr = unsigned int; using IntData = int64_t; using UIntData = unsigned long long int; +using FloatData = long double; using Fd = int; using status_t = unsigned short; diff --git a/headers/gensym/value_ops.hpp b/headers/gensym/value_ops.hpp index 20adb828..bc325a51 100644 --- a/headers/gensym/value_ops.hpp +++ b/headers/gensym/value_ops.hpp @@ -265,26 +265,51 @@ inline char proj_IntV_char(const PtrVal& v) { return static_cast(proj_IntV(intV)); } +/* Concrete floating-point values are kept in their native representation; + * `bw` selects which member of the union is the live one. + */ +union FloatV_val { + float f32; + double f64; + long double f80; + FloatV_val(FloatData f, size_t bw) { + switch (bw) { + case 32: f32 = f; break; + case 64: f64 = f; break; + case 80: f80 = f; break; + default: ABORT("Making a float of unsupported bitwidth " << bw); + } + } +}; + struct FloatV : Value { - long double f; + FloatV_val f; size_t bw; - FloatV(long double f, size_t bw=32) : f(f), bw(bw) { + FloatV(FloatData f, size_t bw=32) : f(f, bw), bw(bw) { hash_combine(hash(), std::string("floatv")); - hash_combine(hash(), f); + hash_combine(hash(), as_ld()); hash_combine(hash(), bw); } - FloatV(const FloatV& v): FloatV(v.f, v.bw) {} + FloatV(const FloatV& v) : FloatV(v.as_ld(), v.bw) {} std::string toString() const override { std::ostringstream ss; - ss << "FloatV(f=" << f << ", bw=" << bw << ")"; + ss << "FloatV(f=" << as_ld() << ", bw=" << bw << ")"; return ss.str(); } virtual bool is_conc() const override { return true; } virtual size_t get_bw() const override { return bw; } + FloatData as_ld() const { + switch (bw) { + case 32: return f.f32; + case 64: return f.f64; + case 80: return f.f80; + } + ABORT("Reading a float of unsupported bitwidth " << bw); + } virtual bool compare(const Value* v) const override { auto that = static_cast(v); - if (this->f != that->f) return false; + if (this->as_ld() != that->as_ld()) return false; return this->bw == that->bw; } virtual List to_bytes() { @@ -295,23 +320,19 @@ struct FloatV : Value { } }; -inline PtrVal make_FloatV(long double f) { - auto ret = make_simple(f); - return hashconsing(ret); -} - -inline PtrVal make_FloatV(long double f, size_t bw) { +inline PtrVal make_FloatV(FloatData f, size_t bw=32) { auto ret = make_simple(f, bw); return hashconsing(ret); } inline PtrVal make_FloatV_fp80(std::array buf) { - //std::cout << "*(long double*)buf.data(): " << *(long double*) buf.data() << std::endl; - return make_FloatV(*(long double*) buf.data(), 80); + long double f = 0; + std::memcpy(&f, buf.data(), buf.size()); + return make_FloatV(f, 80); } -inline long double proj_FloatV(PtrVal v) { - return v->to_FloatV()->f; +inline FloatData proj_FloatV(const PtrVal& v) { + return v->to_FloatV()->as_ld(); } inline PtrVal ui_tofp(PtrVal v) { @@ -323,13 +344,12 @@ inline PtrVal ui_tofp(PtrVal v) { inline PtrVal fp_toui(PtrVal v, size_t bw) { auto fp = v->to_FloatV(); ASSERT(fp != nullptr, "value passed to fp_toui is not a FloatV"); - return make_IntV((uint64_t)fp->f, bw); + return make_IntV((uint64_t)fp->as_ld(), bw); } inline PtrVal fp_tosi(const PtrVal& v, size_t bw) { - auto fp = v->to_FloatV(); - ASSERT(fp != nullptr, "value passed to fp_tosi is not a FloatV"); - return make_IntV(fp->f, bw); + ASSERT(v->to_FloatV() != nullptr, "value passed to fp_tosi is not a FloatV"); + return make_IntV(proj_FloatV(v), bw); } inline PtrVal si_tofp(const PtrVal& v) { @@ -892,52 +912,52 @@ inline PtrVal float_op_2(fOP op, const PtrVal& v1, const PtrVal& v2) { } if (f1 && f2) { + auto apply = [&](auto x, auto y) -> PtrVal { switch (op) { - case fOP::op_fadd: return make_FloatV(f1->f + f2->f, MAX(f1->bw, f2->bw)); - case fOP::op_fsub: return make_FloatV(f1->f - f2->f, MAX(f1->bw, f2->bw)); - case fOP::op_fmul: return make_FloatV(f1->f * f2->f, MAX(f1->bw, f2->bw)); - case fOP::op_fdiv: return make_FloatV(f1->f / f2->f, MAX(f1->bw, f2->bw)); - case fOP::op_oeq: return make_IntV(f1->f == f2->f, 1); - case fOP::op_ogt: return make_IntV(f1->f > f2->f, 1); - case fOP::op_oge: return make_IntV(f1->f >= f2->f, 1); - case fOP::op_olt: return make_IntV(f1->f < f2->f, 1); - case fOP::op_ole: return make_IntV(f1->f <= f2->f, 1); - case fOP::op_one: return make_IntV(f1->f != f2->f, 1); - case fOP::op_ueq: return make_IntV(f1->f == f2->f, 1); - case fOP::op_ugt: return make_IntV(f1->f > f2->f, 1); - case fOP::op_uge: return make_IntV(f1->f >= f2->f, 1); - case fOP::op_ult: return make_IntV(f1->f < f2->f, 1); - case fOP::op_ule: return make_IntV(f1->f <= f2->f, 1); - case fOP::op_une: return make_IntV(f1->f != f2->f, 1); + case fOP::op_fadd: return make_FloatV(x + y, bw1); + case fOP::op_fsub: return make_FloatV(x - y, bw1); + case fOP::op_fmul: return make_FloatV(x * y, bw1); + case fOP::op_fdiv: return make_FloatV(x / y, bw1); + case fOP::op_oeq: return make_IntV(x == y, 1); + case fOP::op_ogt: return make_IntV(x > y, 1); + case fOP::op_oge: return make_IntV(x >= y, 1); + case fOP::op_olt: return make_IntV(x < y, 1); + case fOP::op_ole: return make_IntV(x <= y, 1); + case fOP::op_one: return make_IntV(x != y, 1); + case fOP::op_ueq: return make_IntV(x == y, 1); + case fOP::op_ugt: return make_IntV(x > y, 1); + case fOP::op_uge: return make_IntV(x >= y, 1); + case fOP::op_ult: return make_IntV(x < y, 1); + case fOP::op_ule: return make_IntV(x <= y, 1); + case fOP::op_une: return make_IntV(x != y, 1); /* TODO: QNAN <2022-03-10, David Deng> */ case fOP::op_ord: return make_IntV(1); case fOP::op_uno: return make_IntV(0); case fOP::const_false: return make_IntV(0, 1); case fOP::const_true: return make_IntV(1, 1); } - ABORT("Unknown float_op_2 operation"); + ABORT("Unknown float_op_2 operation");}; + switch (bw1) { + case 32: return apply(f1->f.f32, f2->f.f32); + case 64: return apply(f1->f.f64, f2->f.f64); + case 80: return apply(f1->f.f80, f2->f.f80); + } + ABORT("Operating on a float of unsupported bitwidth " << bw1); } else { ABORT("Non-concrete Float Detected"); - } + } } -/* TODO: implement those two <2022-03-10, David Deng> */ - inline PtrVal fp_ext(const PtrVal& v1, int from, int to) { auto f1 = v1->to_FloatV(); ASSERT((f1 != nullptr), "extending a non-FloatV value"); - return make_FloatV(f1->f, to); + return make_FloatV(proj_FloatV(v1), to); } inline PtrVal fp_trunc(const PtrVal& v1, int from, int to) { auto f1 = v1->to_FloatV(); ASSERT((f1 != nullptr), "truncating a non-FloatV value"); - /* TODO: support other bw values (e.g. fp80) <2022-03-10, David Deng> */ - switch (to) { - case 32: return make_FloatV(float(f1->f), to); - case 64: return make_FloatV(double(f1->f), to); - default: return make_FloatV(f1->f, to); - } + return make_FloatV(proj_FloatV(v1), to); } inline std::string ptrval_to_string(const PtrVal& ptr) { diff --git a/headers/test/Makefile b/headers/test/Makefile index b6d96203..59785fff 100644 --- a/headers/test/Makefile +++ b/headers/test/Makefile @@ -1,9 +1,12 @@ FLAGS := -I ../ -I ../../third-party/immer -I ../../third-party/parallel-hashmap -I ../../third-party/stp/build/include/ -L ../../third-party/stp/build/lib/ -lstp -fPIC -targets = fact_lms fact_plain sym_test conc_test stp_test fs_test external_test coverage_test +targets = fact_lms fact_plain sym_test conc_test stp_test fs_test external_test coverage_test float_test all: $(targets) +float_test: float_test.cpp ../gensym/value_ops.hpp + g++ -std=c++17 float_test.cpp -o float_test $(FLAGS) + coverage_test: coverage_test.cpp ../gensym.hpp g++ -std=c++17 coverage_test.cpp -o coverage_test $(FLAGS) -pthread diff --git a/headers/test/float_test.cpp b/headers/test/float_test.cpp new file mode 100644 index 00000000..151b46e0 --- /dev/null +++ b/headers/test/float_test.cpp @@ -0,0 +1,171 @@ +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +const long double pi = 3.14159265358979323846L; + +template +void test_ops(T x, T y, size_t bw) { + const PtrVal lhs = make_FloatV(x, bw), rhs = make_FloatV(y, bw); + assert(lhs->is_conc() && lhs->get_bw() == bw && lhs->get_byte_size() == bw/8); + assert(lhs->to_bytes().size() == bw/8); + assert(proj_FloatV(lhs) == x && proj_FloatV(rhs) == y); + assert(lhs == make_FloatV(x, bw)); + + assert(proj_FloatV(float_op_2(fOP::op_fadd, lhs, rhs)) == (T)(x + y)); + assert(proj_FloatV(float_op_2(fOP::op_fsub, lhs, rhs)) == (T)(x - y)); + assert(proj_FloatV(float_op_2(fOP::op_fmul, lhs, rhs)) == (T)(x * y)); + assert(proj_FloatV(float_op_2(fOP::op_fdiv, lhs, rhs)) == (T)(x / y)); + + const std::pair cmps[] = { + { fOP::op_oeq, x == y }, { fOP::op_ogt, x > y }, { fOP::op_oge, x >= y }, + { fOP::op_olt, x < y }, { fOP::op_ole, x <= y }, { fOP::op_one, x != y }, + { fOP::op_ueq, x == y }, { fOP::op_ugt, x > y }, { fOP::op_uge, x >= y }, + { fOP::op_ult, x < y }, { fOP::op_ule, x <= y }, { fOP::op_une, x != y }, + { fOP::op_ord, !std::isnan(x) && !std::isnan(y) }, + { fOP::op_uno, std::isnan(x) || std::isnan(y) }, + }; + for (auto [op, expected] : cmps) { + const PtrVal c = float_op_2(op, lhs, rhs); + assert(c->get_bw() == (op == fOP::op_ord || op == fOP::op_uno ? default_bw : 1)); + assert(proj_IntV(c) == expected); + } +} + +void test_f80() { + std::cout << "f80..." << std::endl; + // Round-trip keeps full long double precision, including extremes. + const long double vals[] = { 0.0L, -0.0L, 1.0L, -1.0L, 0.1L, 1.0L/3.0L, + pi, LDBL_EPSILON, LDBL_MAX, -LDBL_MAX, LDBL_TRUE_MIN }; + for (long double x : vals) assert(proj_FloatV(make_FloatV(x, 80)) == x); + + // Construction from the memory representation. + assert(proj_FloatV(make_FloatV_fp80({0,0,0,0,0,0,0,0x80,0xFF,0x3F})) == 1.0L); + std::array buf; + std::memcpy(buf.data(), &pi, 10); + assert(proj_FloatV(make_FloatV_fp80(buf)) == pi); + assert(make_FloatV_fp80(buf) == make_FloatV(pi, 80)); + + test_ops(1.5L, 0.5L, 80); + test_ops(0.1L, 0.2L, 80); + test_ops(1.0L/3.0L, 3.0L, 80); + test_ops(LDBL_TRUE_MIN, LDBL_MAX, 80); + + // Float-to-int conversions truncate toward zero. + assert(proj_IntV(fp_toui(make_FloatV(42.9L, 80), 64)) == 42); + assert(proj_IntV(fp_tosi(make_FloatV(-42.9L, 80), 64)) == -42); + assert(proj_FloatV(ui_tofp(make_IntV(5, 64))) == 5.0L); + assert(proj_FloatV(si_tofp(make_IntV(-5, 64))) == -5.0L); +} + +void test_f32() { + std::cout << "f32..." << std::endl; + const float vals[] = { 0.0f, -0.0f, 1.0f, 0.1f, 1.0f/3.0f, FLT_EPSILON, + FLT_MAX, -FLT_MAX, FLT_TRUE_MIN }; + for (float x : vals) assert(proj_FloatV(make_FloatV(x, 32)) == x); + + assert(proj_FloatV(make_FloatV(0.1L, 32)) == 0.1f); + assert(make_FloatV(0.1L, 32) == make_FloatV(0.1f, 32)); + + test_ops(1.5f, 0.5f, 32); + test_ops(0.1f, 0.2f, 32); + test_ops(1.0f/3.0f, 3.0f, 32); + test_ops(16777216.0f, 1.0f, 32); + test_ops(FLT_TRUE_MIN, FLT_MAX, 32); + + // test arithmetic executions in float precision + assert(proj_FloatV(float_op_2(fOP::op_fadd, make_FloatV(1.0L, 32), make_FloatV(1e-8L, 32))) == 1.0f); + assert(proj_IntV(fp_toui(make_FloatV(2.5f, 32), 32)) == 2); + assert(proj_IntV(fp_tosi(make_FloatV(-2.5f, 32), 32)) == -2); +} + +void test_f64() { + std::cout << "f64..." << std::endl; + const double vals[] = { 0.0, -0.0, 1.0, 0.1, 1.0/3.0, DBL_EPSILON, + DBL_MAX, -DBL_MAX, DBL_TRUE_MIN }; + for (double x : vals) assert(proj_FloatV(make_FloatV(x, 64)) == x); + + assert(proj_FloatV(make_FloatV(0.1L, 64)) == 0.1); + assert(proj_FloatV(make_FloatV(0.1L, 64)) != 0.1L); + assert(make_FloatV(0.1L, 64) == make_FloatV(0.1, 64)); + + test_ops(1.5, 0.5, 64); + test_ops(0.1, 0.2, 64); + test_ops(1.0/3.0, 3.0, 64); + test_ops(DBL_TRUE_MIN, DBL_MAX, 64); + + assert(proj_FloatV(float_op_2(fOP::op_fadd, make_FloatV(1.0L, 64), make_FloatV(1e-17L, 64))) == 1.0); + assert(proj_FloatV(float_op_2(fOP::op_fdiv, + make_FloatV(327.8082191780822, 64), make_FloatV(2.9489999999999998, 64))) + == 327.8082191780822 / 2.9489999999999998); + + assert(proj_IntV(fp_toui(make_FloatV(2.5, 64), 64)) == 2); + assert(proj_IntV(fp_tosi(make_FloatV(-2.5, 64), 64)) == -2); +} + +void test_cross_width() { + std::cout << "cross-width..." << std::endl; + const PtrVal f32pi = make_FloatV((float)pi, 32); + const PtrVal f64pi = make_FloatV((double)pi, 64); + const PtrVal f80pi = make_FloatV(pi, 80); + + // fp_ext widens losslessly; fp_trunc rounds to the narrower format. + assert(fp_ext(f32pi, 32, 64)->get_bw() == 64 + && proj_FloatV(fp_ext(f32pi, 32, 64)) == (double)(float)pi); + assert(proj_FloatV(fp_ext(f32pi, 32, 80)) == (float)pi); + assert(proj_FloatV(fp_ext(f64pi, 64, 80)) == (double)pi); + assert(proj_FloatV(fp_trunc(f80pi, 80, 64)) == (double)pi); + assert(proj_FloatV(fp_trunc(f80pi, 80, 32)) == (float)pi); + assert(fp_trunc(f64pi, 64, 32)->get_bw() == 32 + && proj_FloatV(fp_trunc(f64pi, 64, 32)) == (float)pi); + assert(proj_FloatV(fp_trunc(make_FloatV(0.1, 64), 64, 32)) == 0.1f); + + assert(make_FloatV(1.5, 64) == make_FloatV(1.5, 64)); + assert(make_FloatV(1.5, 64) != make_FloatV(1.5, 32)); + assert(*make_FloatV(0.0, 64) == *make_FloatV(-0.0, 64)); +} + +void test_special_values() { + std::cout << "special values..." << std::endl; + use_hashcons = false; + for (size_t bw : {32, 64, 80}) { + const PtrVal one = make_FloatV(1.0L, bw), zero = make_FloatV(0.0L, bw); + const PtrVal nzero = make_FloatV(-0.0L, bw); + const PtrVal inf = float_op_2(fOP::op_fdiv, one, zero); + const PtrVal neg_inf = float_op_2(fOP::op_fdiv, make_FloatV(-1.0L, bw), zero); + const PtrVal nan = float_op_2(fOP::op_fdiv, zero, zero); + assert(std::isinf(proj_FloatV(inf))); + assert(std::isinf(proj_FloatV(neg_inf)) && std::signbit(proj_FloatV(neg_inf))); + assert(std::isnan(proj_FloatV(nan))); + assert(std::isnan(proj_FloatV(float_op_2(fOP::op_fsub, inf, inf)))); + assert(std::isinf(proj_FloatV(float_op_2(fOP::op_fadd, inf, inf)))); + assert(proj_IntV(float_op_2(fOP::op_ogt, inf, one)) == 1); + assert(proj_IntV(float_op_2(fOP::op_oeq, nan, nan)) == 0); + assert(proj_IntV(float_op_2(fOP::op_ogt, nan, nan)) == 0); + assert(proj_IntV(float_op_2(fOP::op_one, nan, nan)) == 1); + assert(proj_IntV(float_op_2(fOP::op_oeq, nzero, zero)) == 1); + assert(std::signbit(proj_FloatV(float_op_2(fOP::op_fmul, nzero, one)))); + assert(!std::signbit(proj_FloatV(float_op_2(fOP::op_fadd, nzero, zero)))); + assert(std::isnan(proj_FloatV(float_op_2(fOP::op_fdiv, nzero, zero)))); + } + use_hashcons = true; +} + +} + +int main() { + test_f80(); + test_f32(); + test_f64(); + test_cross_width(); + test_special_values(); + std::cout << "All float tests passed." << std::endl; + return 0; +}