diff --git a/include/universal/number/dbns/dbns_impl.hpp b/include/universal/number/dbns/dbns_impl.hpp index 21ea1ef62..e3e358ce7 100644 --- a/include/universal/number/dbns/dbns_impl.hpp +++ b/include/universal/number/dbns/dbns_impl.hpp @@ -47,7 +47,7 @@ dbns& maxneg(dbns& lmaxn return lmaxneg; } - +// double-base logarithmic number system: bases 2^-1, and 3 template class dbns { static_assert(_nbits > _fbbits, "configuration not supported: too many first base bits leaving no bits for second base"); @@ -81,12 +81,13 @@ class dbns { static constexpr int64_t min_exponent = (maxShift > 0) ? (-(1ll << leftShift)) : 0; static constexpr int64_t max_exponent = (maxShift > 0) ? (1ll << leftShift) - 1 : 0; static constexpr int rightShift = (fbbits == 0 ? 0 : (64 - fbbits)); - static constexpr uint64_t FB_MASK = (rightShift > 0 ? (0xFFFF'FFFF'FFFF'FFFFull >> rightShift) : 0ull); + static constexpr uint64_t FB_MASK = (rightShift > 0 ? ((0xFFFF'FFFF'FFFF'FFFFull >> rightShift) << sbbits) : 0ull); static constexpr uint64_t SB_MASK = (0xFFFF'FFFF'FFFF'FFFFull >> (64 - (nbits - fbbits - 1))); using BlockBinary = blockbinary; // sign + dbns exponent using ExponentBlockBinary = blockbinary; // just the dbns exponent + // the smallest value with this base set and the assumption that exponents are positive is 0b0.111.0000 static constexpr float base[2] = { 0.5f, 3.0f }; /// trivial constructor @@ -233,7 +234,7 @@ class dbns { return *this; } if (rhs.iszero()) { -#if LNS_THROW_ARITHMETIC_EXCEPTION +#if DBNS_THROW_ARITHMETIC_EXCEPTION throw dbns_divide_by_zero(); #else setnan(); @@ -337,94 +338,69 @@ class dbns { return *this; } constexpr dbns& minpos() noexcept { - // minimum positive value has this bit pattern: 0-11...11-00...00, that is, sign = 0, first base = 11..11, second base = 00..00 + // minimum positive value has this bit pattern: 0-11...11-00...00, that is, sign = 0, first base = 11..10, second base = 00..00 clear(); - for (unsigned i = nbits - fbbits - 1; i < nbits - 1; ++i) { + for (unsigned i = sbbits + 1; i < nbits - 1; ++i) { setbit(i, true); } return *this; } constexpr dbns& zero() noexcept { - // the zero value has this bit pattern: 0-100..00-00..000, sign = 0, msb = 1, rest 0 + // the zero value has this bit pattern: 0-11..11-00..000, sign = 0, fbbits all 1, rest 0 clear(); - setbit(nbits - 2, true); // msb = 1 + if constexpr (1 == nrBlocks) { + setbits(FB_MASK); + } + else { + for (unsigned i = sbbits; i < nbits - 1; ++i) { + setbit(i, true); + } + } return *this; } constexpr dbns& minneg() noexcept { - // minimum negative value has this bit pattern: 1-11...11-00...00, that is, sign = 0, first base = 11..11, second base = 00..00 - clear(); - for (unsigned i = nbits - fbbits - 1; i < nbits; ++i) { - setbit(i, true); - } + // minimum negative value has this bit pattern: 1-11...10-00...00, that is, sign = 0, first base = 11..10, second base = 00..00 + minpos(); + setbit(nbits - 1ull, true); return *this; } constexpr dbns& maxneg() noexcept { // maximum negative value has this bit pattern: 1-00..00-11...11, that is, sign = 0, first base = 00..00, second base = 11..11 - clear(); - for (unsigned i = 0; i < nbits - fbbits - 1; ++i) { - setbit(i, true); - } + maxpos(); setbit(nbits - 1ull, true); // sign = 1 return *this; } // selectors - constexpr bool iszero() const noexcept { // special encoding: 0.1000.0000 - if constexpr (nrBlocks == 1) { - return (_block[MSB_UNIT] == MSU_ZERO); - } - else if constexpr (nrBlocks == 2) { - if constexpr (SPECIAL_BITS_TOGETHER) { - return (_block[0] == 0 && _block[1] == MSU_ZERO); - } - else { - return !sign() && _block[0] == MSB_BIT_MASK; - } + constexpr bool iszero() const noexcept { // special encoding: 0.11..11.0000 + if constexpr (1 == nrBlocks) { + if (!at(nbits - 1) && ((_block[MSU] & FB_MASK) == FB_MASK) && ((_block[MSU] & SB_MASK) == 0)) return true; } else { - if constexpr (SPECIAL_BITS_TOGETHER) { - for (unsigned i = 0; i < nrBlocks - 1; ++i) { - if (_block[i] != 0) return false; - } - return (_block[MSB_UNIT] == MSU_ZERO); // this will cover the sign != 1 condition + for (unsigned i = 0; i < sbbits; ++i) { + if (at(i)) return false; } - else { - for (unsigned i = 0; i < nrBlocks - 2; ++i) { - if (_block[i] != 0) return false; - } - return !sign() && _block[MSB_UNIT] == BLOCK_MSB_MASK; + for (unsigned i = sbbits; i < nbits - 1; ++i) { + if (!at(i)) return false; } + // zero is sign bit is off, nan is sign bit is on + return !at(nbits - 1); } + return false; } constexpr bool isneg() const noexcept { return sign(); } constexpr bool ispos() const noexcept { return !sign(); } constexpr bool isinf() const noexcept { return false; } constexpr bool isnan() const noexcept { // special encoding - if constexpr (nrBlocks == 1) { - return (_block[MSB_UNIT] == MSU_NAN); // 1.1000.0000 is NaN + // 1.1111.0000 is NaN + for (unsigned i = 0; i < sbbits; ++i) { + if (at(i)) return false; } - else if constexpr (nrBlocks == 2) { - if constexpr (SPECIAL_BITS_TOGETHER) { - return (_block[0] == 0 && _block[1] == MSU_NAN); - } - else { - return sign() && (_block[MSU - 1] == BLOCK_MSB_MASK); - } - } - else { - if constexpr (SPECIAL_BITS_TOGETHER) { - for (unsigned i = 0; i < nrBlocks - 1; ++i) { - if (_block[i] != 0) return false; - } - return (_block[MSB_UNIT] == MSU_NAN); - } - else { - for (unsigned i = 0; i < nrBlocks - 2; ++i) { - if (_block[i] != 0) return false; - } - return sign() && (_block[MSU - 1] == BLOCK_MSB_MASK); - } + for (unsigned i = sbbits; i < nbits - 1; ++i) { + if (!at(i)) return false; } + // zero is sign bit is off, nan is sign bit is on + return at(nbits - 1); } constexpr bool sign() const noexcept { return (SIGN_BIT_MASK & _block[MSU]) != 0; @@ -460,15 +436,32 @@ class dbns { } constexpr uint64_t extractExponent(int base) const noexcept { - uint64_t bits = uint64_t(_block); - if (base == 0) { - bits >>= (nbits - fbbits - 1); // normalize the value - bits &= FB_MASK; // null the sign bit + if constexpr (1 == nrBlocks) { + uint64_t bits = static_cast(_block[MSU]); + if (base == 0) { + bits &= FB_MASK; + bits >>= sbbits; // normalize the value + } + else if (base == 1) { + bits &= SB_MASK; // value is already normalized + } + return bits; } - else if (base == 1) { - bits &= SB_MASK; // normalize the value + else { + uint64_t bits{ 0 }; + if (0 == base) { + for (unsigned i = sbbits; i < nbits - 1; ++i) { + bits |= (at(i) ? (1 << (i - sbbits)) : 0); + } + } + else { + for (unsigned i = 0; i < sbbits; ++i) { + bits |= (at(i) ? (1 << i) : 0); + } + } + return bits; } - return bits; + return 0; } explicit operator int() const noexcept { return to_signed(); } explicit operator long() const noexcept { return to_signed(); } @@ -545,6 +538,9 @@ class dbns { } template CONSTEXPRESSION dbns& convert_ieee754(Real v) noexcept { + using std::abs; + using std::log2; + using std::round; bool s{ false }; uint64_t unbiasedExponent{ 0 }; uint64_t rawFraction{ 0 }; @@ -581,114 +577,33 @@ class dbns { } // check if the value is in the representable range - // NOTE: this is required to protect the rounding code below, which only works for values between [minpos, maxpos] - // TODO: this is all incredibly slow as we are creating special values and converting them to Real to compare - if constexpr (behavior == Behavior::Saturating) { - dbns maxpos(SpecificValue::maxpos); - dbns maxneg(SpecificValue::maxneg); - Real absoluteValue = std::abs(v); - //std::cout << "maxpos : " << to_binary(maxpos) << " : " << maxpos << '\n'; - if (v > 0 && v >= Real(maxpos)) { - return *this = maxpos; - } - if (v < 0 && v <= Real(maxneg)) { - return *this = maxneg; +// if (abs(v) < minpos()) { +// setzero(); +// return *this; +// } + double fulle = -log2(abs(v)); +// std::cout << "fulle : " << fulle << '\n'; + double best_err = 1.0e10; + int32_t best_e0 = 500; + int32_t best_e1 = 500; + int32_t b0{ 1 }, b1{ 1 }; // exponent biases + double err{ 0.0 }; + int32_t e0{ 0 }, e1{ 0 }; + for (e1 = 0; e1 < SB_MASK; ++e1) { + e0 = static_cast(round((fulle - e1 * b1) / b0)); + err = abs(fulle - (e0 * b0 + e1 * b1)); +// std::cout << "e0 : " << e0 << " e1 : " << e1 << " err : " << err << '\n'; + if (err < best_err) { + best_err = err; + best_e0 = e0; + best_e1 = e1; } - dbns minpos(SpecificValue::minpos); - dbns halfMinpos(SpecificValue::minpos); // in log space - //std::cout << "minpos : " << minpos << '\n'; - //std::cout << "halfMinpos : " << halfMinpos << '\n'; - if (absoluteValue <= Real(halfMinpos)) { - setzero(); - return *this; - } - else if (absoluteValue <= Real(minpos)) { - return *this = (v > 0 ? minpos : -minpos); - } - } - - bool negative = (v < Real(0.0f)); - v = (negative ? -v : v); - Real logv = std::log2(v); - if (logv == 0.0) { - _block.clear(); - _block.setbit(nbits - 1, negative); - return *this; } - - - ExponentBlockBinary dbnsExponent{ 0 }; - extractFields(logv, s, unbiasedExponent, rawFraction, bits); // use native conversion - if (unbiasedExponent > 0) rawFraction |= (1ull << ieee754_parameter::fbits); - int radixPoint = ieee754_parameter::fbits - (static_cast(unbiasedExponent) - ieee754_parameter::bias); - - // our fixed-point has its radixPoint at fbbits - int shiftRight = radixPoint - int(fbbits); - if (shiftRight > 0) { - if (shiftRight > 63) { - // this shift degree would be undefined behavior, but the intended transformation is that we have no bits - rawFraction = 0; - } - else { - // we need to round the raw bits - // collect guard, round, and sticky bits - // this same logic will work for the case where - // we only have a guard bit and no round and/or sticky bits - // because the mask logic will make round and sticky both 0 - // so no need to special case it - uint64_t mask = (1ull << (shiftRight - 1)); - bool guard = (mask & rawFraction); - mask >>= 1; - bool round = (mask & rawFraction); - if (shiftRight > 1) { - mask = (0xFFFF'FFFF'FFFF'FFFFull << (shiftRight - 2)); - mask = ~mask; - } - else { - mask = 0; - } - bool sticky = (mask & rawFraction); - - rawFraction >>= shiftRight; // shift out the bits we are rounding away - bool lsb = (rawFraction & 0x1ul); - // ... lsb | guard round sticky round - // x 0 x x down - // 0 1 0 0 down round to even - // 1 1 0 0 up round to even - // x 1 0 1 up - // x 1 1 0 up - // x 1 1 1 up - if (guard) { - if (lsb && (!round && !sticky)) ++rawFraction; // round to even - if (round || sticky) ++rawFraction; - } - rawFraction = (s ? (~rawFraction + 1) : rawFraction); // if negative, map to two's complement - } - dbnsExponent.setbits(rawFraction); - } - else { - int shiftLeft = -shiftRight; - if (shiftLeft < (64 - ieee754_parameter::fbits)) { // what is the distance between the MSB and 64? - // no need to round, just shift the bits in place - rawFraction <<= shiftLeft; - rawFraction = (s ? (~rawFraction + 1) : rawFraction); // if negative, map to two's complement - dbnsExponent.setbits(rawFraction); - } - else { - // we need to project the bits we have on the fixpnt - for (unsigned i = 0; i < ieee754_parameter::fbits + 1; ++i) { - if (rawFraction & 0x01) { - dbnsExponent.setbit(i + shiftLeft); - } - rawFraction >>= 1; - } - if (s) dbnsExponent.twosComplement(); - } - } -// std::cout << "dbns exponent : " << to_binary(dbnsExponent) << " : " << dbnsExponent << '\n'; - _block = dbnsExponent; - setsign(negative); - + e0 = best_e0; + e1 = best_e1; +// std::cout << "e0 : " << e0 << " e1 : " << e1 << " err : " << err << '\n'; + e0 <<= sbbits; + _block.setblock(MSU, (s ? SIGN_BIT_MASK : 0) | e0 | e1); return *this; } diff --git a/linalg/data/serialization.cpp b/linalg/data/serialization.cpp index 66e292c56..8d9d9a39d 100644 --- a/linalg/data/serialization.cpp +++ b/linalg/data/serialization.cpp @@ -131,7 +131,7 @@ void TestVectorSerialization() { df.save(s, false); // decimal format df.clear(); df.restore(s); - df.save(std::cout, false); + df.save(std::cout, true); } template @@ -146,7 +146,7 @@ void TestMatrixSerialization() { df.save(s, false); // decimal format df.clear(); df.restore(s); - df.save(std::cout, false); + df.save(std::cout, true); } void TestSerialization() { @@ -230,7 +230,8 @@ try { // ReportNativeHexFormats(); // ReportNumberSystemFormats(); - //TestVectorSerialization(); + TestVectorSerialization(); + TestVectorSerialization>(); TestMatrixSerialization(); // TestVectorSerialization(); diff --git a/static/dbns/api/api.cpp b/static/dbns/api/api.cpp index 3382de541..4510201fc 100644 --- a/static/dbns/api/api.cpp +++ b/static/dbns/api/api.cpp @@ -56,11 +56,12 @@ try { ReportTestSuiteHeader(test_suite, reportTestCases); - // float x{ 1.0f }; - // bases(x, 2.5f, 3, 5, 7, 9); - - // generate a value table for dbns<5,2> - GenerateDbnsTable<5,2>(std::cout); +#ifdef EXTRA + { + // experiment with variadic template arguments to specify multi-base lns configurations + float x{ 1.0f }; + bases(x, 2, 3, 5.5f, 7.1, 9); + } { dbns<8, 3> l(1); @@ -73,25 +74,33 @@ try { std::cout << dynamic_range(l) << '\n'; } +#endif - // important behavioral traits - ReportTrivialityOfType>(); + { + std::cout << "+-------- important behavioral traits --------+\n"; + ReportTrivialityOfType>(); + } // default behavior { std::cout << "+--------- default dbns bahavior --------+\n"; using Real = dbns<8, 3>; - Real a(1.0f), b(1.0f), c; -// ArithmeticOperators(a, b); - a = 1; // integer assignment - b = 1; + Real a(0.5f), b(1.0f), c(3.0f); + + ReportValue(a, "a = 0.5"); + ReportValue(b, "b = 1.0"); + ReportValue(c, "c = 3.0"); + + // ArithmeticOperators(a, b); + a = 0.5f; + b = 3.0f; c = a + b; -// ReportBinaryOperation(a, "+", b, c); + ReportBinaryOperation(a, "+", b, c); } - + return 0; { std::cout << "+--------- dynamic ranges of 8-bit dbns<> configurations --------+\n"; -// std::cout << symmetry_range(dbns<8, 0>()) << '\n'; + // std::cout << symmetry_range(dbns<8, 0>()) << '\n'; std::cout << symmetry_range(dbns<8, 1>()) << '\n'; std::cout << symmetry_range(dbns<8, 2>()) << '\n'; std::cout << symmetry_range(dbns<8, 3>()) << '\n'; @@ -103,11 +112,11 @@ try { // configuration { std::cout << "+--------- arithmetic operators with explicit alignment bahavior --------+\n"; -// using dbns16 = dbns<16, 5, std::uint16_t>; -// ArithmeticOperators(1.0f, 1.0f); + // using dbns16 = dbns<16, 5, std::uint16_t>; + // ArithmeticOperators(1.0f, 1.0f); -// using dbns24 = dbns<24, 5, std::uint32_t>; -// ArithmeticOperators(1.0f, 1.0f); + // using dbns24 = dbns<24, 5, std::uint32_t>; + // ArithmeticOperators(1.0f, 1.0f); } { @@ -125,12 +134,12 @@ try { constexpr size_t rbits = 3; using Real = dbns; // BlockType = uint8_t, behavior = Saturating -// CONSTEXPRESSION Real a{}; // zero constexpr -// std::cout << type_tag(a) << '\n'; // TODO: type_tag doesn't work for dbns + // CONSTEXPRESSION Real a{}; // zero constexpr + // std::cout << type_tag(a) << '\n'; // TODO: type_tag doesn't work for dbns - // TODO: needs a constexpr version of log2() function -// CONSTEXPRESSION Real b(1.0f); // constexpr of a native type conversion -// std::cout << to_binary(b) << " : " << b << '\n'; + // TODO: needs a constexpr version of log2() function + // CONSTEXPRESSION Real b(1.0f); // constexpr of a native type conversion + // std::cout << to_binary(b) << " : " << b << '\n'; CONSTEXPRESSION Real c(SpecificValue::minpos); // constexpr of a special value in the encoding std::cout << to_binary(c) << " : " << c << " == minpos" << '\n'; @@ -162,9 +171,27 @@ try { dbns b = -dbns(0.0); // if (a != b) std::cout << "you can't compare indeterminate NaN\n"; if (a.isnan() && b.isnan()) std::cout << "PASS: both +dbns(0) and -dbns(0) are indeterminate\n"; - std::cout << "+dbns(0.0f): " << dbns(0.0f) << "\n"; + std::cout << "+dbns(0.0f): " << dbns(0.0f) << "\n"; std::cout << "-dbns(0.0f): " << -dbns(0.0f) << "\n"; } + + { + std::cout << "+--------- extract exponents --------+\n"; + { + dbns<8, 3> l; // 1 limb + l.setbits(0x11); + ReportValue(l); + std::cout << "first exponent : " << l.extractExponent(0) << '\n'; + std::cout << "second exponent : " << l.extractExponent(1) << '\n'; + } + { + dbns<16, 9> l; // two limbs + l.setbits(0x1fff); + ReportValue(l); + std::cout << "first exponent : " << l.extractExponent(0) << '\n'; + std::cout << "second exponent : " << l.extractExponent(1) << '\n'; + } + } { std::cout << "+--------- comparison to classic floats --------+\n"; diff --git a/static/dbns/conversion/assignment.cpp b/static/dbns/conversion/assignment.cpp new file mode 100644 index 000000000..f6210d03c --- /dev/null +++ b/static/dbns/conversion/assignment.cpp @@ -0,0 +1,149 @@ +// assignment.cpp: test suite runner for assignment conversion of floats to fixed-sized, arbitrary precision double-base logarithmic number system +// +// Copyright (C) 2013-2023 Stillwater Supercomputing, Inc. +// +// This file is part of the universal numbers project, which is released under an MIT Open Source license. +#include +// minimum set of include files to reflect source code dependencies +#include +#include +#include + + +namespace sw { namespace universal { + + // TODO: needs a type trait to only match on dbns<> type + template + int ValidateAssignment(bool reportTestCases) { + constexpr size_t nbits = DbnsType::nbits; + constexpr size_t NR_ENCODINGS = (1ull << nbits); + int nrOfFailedTestCases = 0; + + DbnsType a, b; + for (size_t i = 0; i < NR_ENCODINGS; ++i) { + a.setbits(i); + double da = double(a); + b = da; + // std::cout << to_binary(a) << " : " << da << " vs " << b << '\n'; + if (a != b) { + ++nrOfFailedTestCases; + if (reportTestCases) ReportAssignmentError("FAIL", "=", da, b, a); + } + else { + // if (reportTestCases) ReportAssignmentSuccess("PASS", "=", da, b, a); + } + } + + // test clipping or saturation + + return nrOfFailedTestCases; + } + +} } + +template +void SampleTest(Real v) { + using namespace sw::universal; + std::cout << symmetry_range(dbns<8, 1>()) << '\n' << to_binary(dbns<8, 1>(v)) << " : " << dbns<8, 1>(v) << '\n'; + std::cout << symmetry_range(dbns<8, 2>()) << '\n' << to_binary(dbns<8, 2>(v)) << " : " << dbns<8, 2>(v) << '\n'; + std::cout << symmetry_range(dbns<8, 3>()) << '\n' << to_binary(dbns<8, 3>(v)) << " : " << dbns<8, 3>(v) << '\n'; + std::cout << symmetry_range(dbns<8, 4>()) << '\n' << to_binary(dbns<8, 4>(v)) << " : " << dbns<8, 4>(v) << '\n'; + std::cout << symmetry_range(dbns<8, 5>()) << '\n' << to_binary(dbns<8, 5>(v)) << " : " << dbns<8, 5>(v) << '\n'; + std::cout << symmetry_range(dbns<8, 6>()) << '\n' << to_binary(dbns<8, 6>(v)) << " : " << dbns<8, 6>(v) << '\n'; +} + +// Regression testing guards: typically set by the cmake configuration, but MANUAL_TESTING is an override +#define MANUAL_TESTING 1 +// REGRESSION_LEVEL_OVERRIDE is set by the cmake file to drive a specific regression intensity +// It is the responsibility of the regression test to organize the tests in a quartile progression. +//#undef REGRESSION_LEVEL_OVERRIDE +#ifndef REGRESSION_LEVEL_OVERRIDE +#undef REGRESSION_LEVEL_1 +#undef REGRESSION_LEVEL_2 +#undef REGRESSION_LEVEL_3 +#undef REGRESSION_LEVEL_4 +#define REGRESSION_LEVEL_1 1 +#define REGRESSION_LEVEL_2 1 +#define REGRESSION_LEVEL_3 1 +#define REGRESSION_LEVEL_4 1 +#endif + +int main() +try { + using namespace sw::universal; + + std::string test_suite = "dbns assignment validation"; + std::string test_tag = "assignment"; + bool reportTestCases = true; + int nrOfFailedTestCases = 0; + + ReportTestSuiteHeader(test_suite, reportTestCases); + +#if MANUAL_TESTING + +// using DBNS16_8 = dbns<16, 8, std::uint16_t>; +// using DBNS12_5 = dbns<12, 5, std::uint8_t>; + using DBNS8_3 = dbns<8, 3, std::uint8_t>; + using DBNS6_3 = dbns<6, 3, std::uint8_t>; +// using DBNS4_1 = dbns<4, 1, std::uint8_t>; + + // GenerateBitWeightTable(); + SampleTest(1024.0f); + return 0; + + + // manual exhaustive test +// nrOfFailedTestCases += ReportTestResult(ValidateAssignment(reportTestCases), type_tag(DBNS4_1()), test_tag); + nrOfFailedTestCases += ReportTestResult(ValidateAssignment(reportTestCases), type_tag(DBNS6_3()), test_tag); + nrOfFailedTestCases += ReportTestResult(ValidateAssignment(reportTestCases), type_tag(DBNS8_3()), test_tag); +// nrOfFailedTestCases += ReportTestResult(ValidateAssignment(reportTestCases), type_tag(DBNS12_5()), test_tag); + + ReportTestSuiteResults(test_suite, nrOfFailedTestCases); + return EXIT_SUCCESS; +#else + +#if REGRESSION_LEVEL_1 + nrOfFailedTestCases += ReportTestResult(ValidateAssignment< dbns<4, 1, std::uint8_t> >(reportTestCases), type_tag(dbns<4, 1, std::uint8_t>()), test_tag); + nrOfFailedTestCases += ReportTestResult(ValidateAssignment< dbns<4, 2, std::uint8_t> >(reportTestCases), type_tag(dbns<4, 2, std::uint8_t>()), test_tag); + + nrOfFailedTestCases += ReportTestResult(ValidateAssignment< dbns<8, 2, std::uint8_t> >(reportTestCases), type_tag(dbns<8, 2, std::uint8_t>()), test_tag); + nrOfFailedTestCases += ReportTestResult(ValidateAssignment< dbns<8, 3, std::uint8_t> >(reportTestCases), type_tag(dbns<8, 3, std::uint8_t>()), test_tag); + nrOfFailedTestCases += ReportTestResult(ValidateAssignment< dbns<8, 4, std::uint8_t> >(reportTestCases), type_tag(dbns<8, 4, std::uint8_t>()), test_tag); + nrOfFailedTestCases += ReportTestResult(ValidateAssignment< dbns<8, 5, std::uint8_t> >(reportTestCases), type_tag(dbns<8, 5, std::uint8_t>()), test_tag); + nrOfFailedTestCases += ReportTestResult(ValidateAssignment< dbns<8, 6, std::uint8_t> >(reportTestCases), type_tag(dbns<8, 6, std::uint8_t>()), test_tag); +#endif + +#if REGRESSION_LEVEL_2 +#endif + +#if REGRESSION_LEVEL_3 +#endif + +#if REGRESSION_LEVEL_4 +#endif + + ReportTestSuiteResults(test_suite, nrOfFailedTestCases); + return (nrOfFailedTestCases > 0 ? EXIT_FAILURE : EXIT_SUCCESS); + +#endif // MANUAL_TESTING +} +catch (char const* msg) { + std::cerr << msg << std::endl; + return EXIT_FAILURE; +} +catch (const sw::universal::universal_arithmetic_exception& err) { + std::cerr << "Caught unexpected universal arithmetic exception : " << err.what() << std::endl; + return EXIT_FAILURE; +} +catch (const sw::universal::universal_internal_exception& err) { + std::cerr << "Caught unexpected universal internal exception: " << err.what() << std::endl; + return EXIT_FAILURE; +} +catch (const std::runtime_error& err) { + std::cerr << "Uncaught runtime exception: " << err.what() << std::endl; + return EXIT_FAILURE; +} +catch (...) { + std::cerr << "Caught unknown exception" << std::endl; + return EXIT_FAILURE; +} diff --git a/static/dbns/conversion/conversion.cpp b/static/dbns/conversion/conversion.cpp new file mode 100644 index 000000000..5ffd7d7a8 --- /dev/null +++ b/static/dbns/conversion/conversion.cpp @@ -0,0 +1,379 @@ +// conversion.cpp : test suite runner for conversion operators to arbitrary precision, fixed-size double-base logarithmic floating-point +// +// Copyright (C) 2023-2023 Stillwater Supercomputing, Inc. +// +// This file is part of the universal numbers project, which is released under an MIT Open Source license. +#include +#include +#include + +namespace sw { namespace universal { + + template + void ReportConversionError(const std::string& test_case, const std::string& op, double input, const TestType& result, RefType ref, const std::string& rounding) { + constexpr size_t nbits = TestType::nbits; // number system concept requires a static member indicating its size in bits + auto old_precision = std::cerr.precision(); + std::cerr << std::setprecision(10); + std::cerr << test_case + << " " << op << " " + << std::setw(NUMBER_COLUMN_WIDTH) << input + << " did not convert to " + << std::setw(NUMBER_COLUMN_WIDTH) << ref << " instead it yielded " + << std::setw(NUMBER_COLUMN_WIDTH) << double(result) + << " encoding " << std::setw(nbits) << to_binary(result) << " converted from " << to_binary(ref) << " " << rounding; + std::cerr << '\n'; + std::cerr << std::setprecision(old_precision); + } + + template + void ReportConversionSuccess(const std::string& test_case, const std::string& op, double input, const TestType& result, RefType ref, const std::string& rounding) { + constexpr size_t nbits = TestType::nbits; // number system concept requires a static member indicating its size in bits + auto old_precision = std::cerr.precision(); + std::cerr << std::setprecision(10); + std::cerr << test_case + << " " << op << " " + << std::setw(NUMBER_COLUMN_WIDTH) << input + << " success " + << std::setw(NUMBER_COLUMN_WIDTH) << result << " golden reference is " + << std::setw(NUMBER_COLUMN_WIDTH) << ref + << " encoding " << std::setw(nbits) << to_binary(result) << " converted from " << to_binary(ref) << " " << rounding; + std::cerr << '\n'; + std::cerr << std::setprecision(old_precision); + } + + template + int Compare(double input, const TestType& result, const RefType& ref, const std::string& rounding, bool reportTestCases) { + int fail = 0; + double dresult = double(result); + double dref = double(ref); + if (std::fabs(dresult - dref) > 0.000000001) { + fail++; + if (reportTestCases) ReportConversionError("FAIL", "=", input, result, ref, rounding); + } + else { + //if (reportTestCases) ReportConversionSuccess("PASS", "=", input, result, ref, rounding); + } + return fail; + } + + // enumerate all conversion cases for a posit configuration + template + int VerifyConversion(bool reportTestCases) { + // we are going to generate a test set that consists of all dbns configs and their midpoints + // we do this by enumerating an dbns that is 1-bit larger than the test configuration + // These larger posits will be at the mid-point between the smaller sample values + // and we'll enumerate the exact value, and a perturbation smaller and a perturbation larger + // to test the rounding logic of the conversion. + using TestType = dbns; + using ContainingType = dbns; + + constexpr size_t max = nbits > 16 ? 16 : nbits; + size_t NR_TEST_CASES = (size_t(1) << (max + 1)); + size_t QUARTER = (size_t(1) << (max - 1)); + size_t HALF = (size_t(1) << max); + + if constexpr (nbits > 16) { + std::cout << "VerifyConversion: " << type_tag(TestType()) << " : NR_TEST_CASES = " << NR_TEST_CASES << " constrained due to nbits > 16" << std::endl; + } + +// ContainingType halfMinpos(SpecificValue::minpos); // in the more precise type +// double dhalfMinpos = double(halfMinpos); +// std::cerr << "half minpos : " << halfMinpos << " : " << to_binary(halfMinpos) << '\n'; + + // execute the test + int nrOfFailedTests = 0; + for (size_t i = 0; i < NR_TEST_CASES; i++) { + ContainingType ref, prev, next; + // std::cerr << "i : " << i << '\n'; + ref.setbits(i); + double da = double(ref); + double eps = da * 1.0e-6; // (da > 0 ? da * 1.0e-6 : da * -1.0e-6); + double input; + TestType a; + if (i % 2) { + if (i == QUARTER - 1) { + if (reportTestCases) std::cerr << " odd-1: special case of project to maxpos\n"; + input = da - eps; + a = input; + prev.setbits(i - 1); + nrOfFailedTests += Compare(input, a, prev, "round down to maxpos", reportTestCases); + input = da + eps; + a = input; + nrOfFailedTests += Compare(input, a, prev, "project down to maxpos", reportTestCases); + } + else if (i == HALF - 1) { + if (reportTestCases) std::cerr << " odd-2: special case of project to 1.0\n"; + input = da - eps; + a = input; + prev.setbits(i - 1); + nrOfFailedTests += Compare(input, a, prev, "round down to 1.0", reportTestCases); + input = da + eps; + a = input; + next.setbits(0); // encoding of 1.0 + nrOfFailedTests += Compare(input, a, next, "round up to 1.0", reportTestCases); + } + else if (i == NR_TEST_CASES - 1) { + if (reportTestCases) std::cerr << " odd-3: special case of project to -1.0\n"; + input = da - eps; + a = input; + prev.setbits(i - 1); + nrOfFailedTests += Compare(input, a, prev, "round down to -1.0", reportTestCases); + input = da + eps; + a = input; + next.setbits(0); + next.setsign(); // encoding of -1.0 + nrOfFailedTests += Compare(input, a, next, "round up to -1.0", reportTestCases); + } + else { + // for odd values, we are between dbns values, so we create the round-up and round-down cases + // std::cerr << " odd-4: between value case\n"; + // round-down + input = da - eps; + a = input; + prev.setbits(i - 1); + //next.setbits(i + 1); + //std::cout << "da : " << da << '\n'; + //std::cout << "eps : " << eps << '\n'; + //std::cout << "input : " << input << '\n'; + //std::cout << "previous : " << to_binary(prev) << " : " << prev << '\n'; + //std::cout << "midpoint : " << to_binary(ref) << " : " << ref << '\n'; + //std::cout << "next : " << to_binary(next) << " : " << next << '\n'; + //std::cout << "sample : " << to_binary(a) << " : " << a << '\n'; + //std::cout << input << " : " << float(ref) << " : " << float(next) << '\n'; + nrOfFailedTests += Compare(input, a, prev, "round down", reportTestCases); + // round-up + input = da + eps; + a = input; + next.setbits(i + 1); + nrOfFailedTests += Compare(input, a, next, "round up", reportTestCases); + } + } + else { + // for the even values, we generate the round-to-actual cases + if (i == QUARTER) { + if (reportTestCases) std::cerr << "even-1: special case of rounding to 0\n"; + input = eps; + a = input; + nrOfFailedTests += Compare(input, a, ref, "round down", reportTestCases); + input = 0.0; + a = input; + nrOfFailedTests += Compare(input, a, ref, " == ", reportTestCases); + input = -eps; + a = input; + nrOfFailedTests += Compare(input, a, ref, "round up", reportTestCases); + } + else { + // std::cerr << "even-2: same value case\n"; + // round-up + input = da - eps; + a = input; + nrOfFailedTests += Compare(input, a, ref, "round up", reportTestCases); + a = da; + nrOfFailedTests += Compare(input, a, ref, " == ", reportTestCases); + // round-down + input = da + eps; + a = input; + nrOfFailedTests += Compare(input, a, ref, "round down", reportTestCases); + } + } + } + return nrOfFailedTests; + } + + // enumerate all conversion cases for integers + template + int VerifyIntegerConversion(bool reportTestCases) { + using TestType = dbns; + + // we generate numbers from 1 via maxpos to -1 and through the special case of 0 back to 1 + constexpr unsigned max = nbits > 20 ? 20 : nbits; + size_t NR_TEST_CASES = (size_t(1) << (max - 1)) + 1; + int nrOfFailedTestCases = 0; + // special cases in case we are clipped by the nbits > 20 + long ref = 0x80000000; // -2147483648 + TestType result(ref); + if (ref != result) { + std::cout << " FAIL long(" << ref << ") != long(" << result << ") : reference = -2147483648" << std::endl; + nrOfFailedTestCases++; + } + TestType v(1); + for (size_t i = 0; i < NR_TEST_CASES; ++i) { + if (!v.isnan()) { + ref = (long)v; // obtain the integer cast of this dbns + result = ref; // assign this integer to a dbns + if (ref != result) { // compare the integer cast to the reference dbns + if (reportTestCases) std::cout << " FAIL long(" << v << ") != long(" << result << ") : reference = " << ref << std::endl; + nrOfFailedTestCases++; + } + else { + //if (reportTestCases) std::cout << " PASS " << v << " casts to " << result << " : reference = " << ref << std::endl; + } + } + ++v; + } + return nrOfFailedTestCases; + } + +} } // namespace sw::universal + +template +void GenerateTestCase(double input, double reference, const TestType& result) { + if (std::fabs(double(result) - reference) > 0.000000001) + ReportConversionError("FAIL", "=", input, result, reference, std::string("faithful x = x")); + else + ReportConversionSuccess("PASS", "=", input, result, reference, std::string("faithful x = x")); + std::cout << std::endl; +} + +// Regression testing guards: typically set by the cmake configuration, but MANUAL_TESTING is an override +#define MANUAL_TESTING 1 +// REGRESSION_LEVEL_OVERRIDE is set by the cmake file to drive a specific regression intensity +// It is the responsibility of the regression test to organize the tests in a quartile progression. +//#undef REGRESSION_LEVEL_OVERRIDE +#ifndef REGRESSION_LEVEL_OVERRIDE +#undef REGRESSION_LEVEL_1 +#undef REGRESSION_LEVEL_2 +#undef REGRESSION_LEVEL_3 +#undef REGRESSION_LEVEL_4 +#define REGRESSION_LEVEL_1 1 +#define REGRESSION_LEVEL_2 1 +#define REGRESSION_LEVEL_3 1 +#define REGRESSION_LEVEL_4 1 +#endif + +int main() +try { + using namespace sw::universal; + + std::string test_suite = "dbns<> conversion validation"; + std::string test_tag = "conversion"; + bool reportTestCases = false; + int nrOfFailedTestCases = 0; + + ReportTestSuiteHeader(test_suite, reportTestCases); + +#if MANUAL_TESTING + + { + using DBNS5_2 = dbns<5, 2, std::uint8_t>; + DBNS5_2 minpos(SpecificValue::minpos); + double mp = double(minpos); + DBNS5_2 result = mp; + GenerateTestCase(mp, mp, result); + double halfMinpos = mp / 2.0; + result = halfMinpos; + GenerateTestCase(halfMinpos, 0.0, result); + double quarterMinpos = halfMinpos / 2.0; + result = quarterMinpos; + GenerateTestCase(quarterMinpos, 0.0, result); + double threeQuarterMinpos = halfMinpos + quarterMinpos; + result = threeQuarterMinpos; + GenerateTestCase(threeQuarterMinpos, mp, result); + + using DBNS6_3 = dbns<6, 3, std::uint8_t>; + DBNS6_3 ref; + ref.setbits(17); + std::cout << to_binary(ref) << " : " << ref << '\n'; + double input = double(ref); + result = input; + std::cout << to_binary(ref) << " : " << ref << " -> " << result << " : " << to_binary(result) << '\n'; + GenerateTestCase(input, double(DBNS5_2(SpecificValue::minpos)), result); + } + + { + using DBNS5_2 = dbns<5, 2, std::uint8_t>; + using DBNS6_3 = dbns<6, 3, std::uint8_t>; + + constexpr size_t NR_SAMPLES = 32; + DBNS5_2 a; + DBNS6_3 b; + for (size_t i = 0; i < NR_SAMPLES; ++i) { + b.setbits(i); + if (i % 2 == 0) { + a.setbits(i / 2); + std::cout << to_binary(b) << " : " << std::setw(10) << b << " - " << std::setw(10) << a << " : " << to_binary(a) << '\n'; + } + else { + std::cout << to_binary(b) << " : " << std::setw(10) << b << '\n'; + } + } + } + + nrOfFailedTestCases += VerifyConversion<5, 2, std::uint8_t>(true); + ReportTestSuiteResults(test_suite, nrOfFailedTestCases); + return 0; + nrOfFailedTestCases += ReportTestResult(VerifyIntegerConversion<4, 1, std::uint8_t>(true), "dbns<4,1>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyIntegerConversion<5, 2, std::uint8_t>(true), "dbns<5,2>", test_tag); + + nrOfFailedTestCases += ReportTestResult(VerifyConversion<4, 1, std::uint8_t>(true), "dbns<4,1>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion<5, 2, std::uint8_t>(true), "dbns<5,2>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion<6, 3, std::uint8_t>(true), "dbns<6,3>", test_tag); + + nrOfFailedTestCases += ReportTestResult(VerifyConversion<4, 1, std::uint8_t>(true), "dbns<4,1>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion<5, 2, std::uint8_t>(true), "dbns<5,2>", test_tag); + + ReportTestSuiteResults(test_suite, nrOfFailedTestCases); + return EXIT_SUCCESS; +#else + +#if REGRESSION_LEVEL_1 + + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 4, 1, std::uint8_t>(reportTestCases), "dbns<4,1>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 4, 2, std::uint8_t>(reportTestCases), "dbns<4,2>", test_tag); + + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 6, 2, std::uint8_t>(reportTestCases), "dbns<6,2>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 6, 3, std::uint8_t>(reportTestCases), "dbns<6,3>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 6, 4, std::uint8_t>(reportTestCases), "dbns<6,4>", test_tag); + + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 8, 2, std::uint8_t>(reportTestCases), "dbns<8,2>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 8, 3, std::uint8_t>(reportTestCases), "dbns<8,3>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 8, 4, std::uint8_t>(reportTestCases), "dbns<8,4>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 8, 5, std::uint8_t>(reportTestCases), "dbns<8,5>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion< 8, 6, std::uint8_t>(reportTestCases), "dbns<8,6>", test_tag); + +#endif + +#if REGRESSION_LEVEL_2 + +#endif + +#if REGRESSION_LEVEL_3 + +#endif + +#if REGRESSION_LEVEL_4 + nrOfFailedTestCases += ReportTestResult(VerifyConversion<10, 3, std::uint8_t>(reportTestCases), "dbns<10,3>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion<10, 4, std::uint8_t>(reportTestCases), "dbns<10,4>", test_tag); + nrOfFailedTestCases += ReportTestResult(VerifyConversion<10, 5, std::uint8_t>(reportTestCases), "dbns<10,5>", test_tag); +#endif // REGRESSION_LEVEL_4 + + ReportTestSuiteResults(test_suite, nrOfFailedTestCases); + return (nrOfFailedTestCases > 0 ? EXIT_FAILURE : EXIT_SUCCESS); + +#endif // MANUAL_TESTING +} +catch (char const* msg) { + std::cerr << msg << std::endl; + return EXIT_FAILURE; +} +catch (const sw::universal::universal_arithmetic_exception& err) { + std::cerr << "Unexpected universal arithmetic exception: " << err.what() << std::endl; + return EXIT_FAILURE; +} +catch (const sw::universal::universal_internal_exception& err) { + std::cerr << "Unexpected universal internal exception: " << err.what() << std::endl; + return EXIT_FAILURE; +} +catch (const std::runtime_error& err) { + std::cerr << "Unexpected runtime exception: " << err.what() << std::endl; + return EXIT_FAILURE; +} +catch (...) { + std::cerr << "Caught unknown exception" << std::endl; + return EXIT_FAILURE; +} + +/* +Generate Value table for an dbns<6,3> in TXT format + + */