diff --git a/bvm/Shaders/Math.h b/bvm/Shaders/Math.h index 3ad35cb71..7d8406b3f 100644 --- a/bvm/Shaders/Math.h +++ b/bvm/Shaders/Math.h @@ -1101,6 +1101,14 @@ namespace MultiPrecision return *this = *this / b; } + Float& operator <<= (int32_t n) { + return *this = *this << n; + } + + Float& operator >>= (int32_t n) { + return *this = *this >> n; + } + int cmp(const Float& x) const { if (IsZero()) @@ -1589,6 +1597,16 @@ namespace MultiPrecision return IsNumberNnz() && !HaveHiBit(); } + uint64_t get_Num() const + { + return IsNumberNnz() ? get_WithHiBit() : 0; + } + + int32_t get_Order() const + { + return m_Order; + } + void Set0() { m_Order = s_Zero; } void SetNaN() { m_Order = s_NaN; } @@ -1837,6 +1855,14 @@ namespace MultiPrecision return *this = *this / b; } + FloatEx& operator <<= (int32_t n) { + return *this = *this << n; + } + + FloatEx& operator >>= (int32_t n) { + return *this = *this >> n; + } + int cmp(const FloatEx& x) const { // negative, zero, positive, NaN diff --git a/bvm/unittest/shaders_test.cpp b/bvm/unittest/shaders_test.cpp index 144f48fd4..ca8750963 100644 --- a/bvm/unittest/shaders_test.cpp +++ b/bvm/unittest/shaders_test.cpp @@ -1248,6 +1248,51 @@ namespace bvm2 { return res * 1e-8; } + static double ToDouble(Shaders::MultiPrecision::Float x) + { + if (x.IsZero()) + return 0; + + return ldexp(x.m_Num, x.m_Order); + } + + static double ToDouble(Shaders::MultiPrecision::FloatEx x) + { + if (x.IsZero()) + return 0; + if (x.IsNaN()) + return NAN; + + //x.get_0 + + auto num = (double) x.get_Num(); + if (x.IsNegative()) + num = -num; + return ldexp(num, x.get_Order() - 63); + } + + static void AssertDoubleRelEqual(double a, double b) + { + if (a == 0.) + { + verify_test(b == 0.); + } + else + { + verify_test(isnormal(a) && isnormal(b)); + + a /= b; + verify_test(isnormal(a)); + + // double floating-point mantissa is 52 bits, our implementation has 63 bits + // we should have at least 50 bits of precision + const double eps = ldexp(1, -50); + + verify_test(a >= 1. - eps); + verify_test(a <= 1. + eps); + } + } + struct NephriteContext { MyProcessor& m_Proc; @@ -1480,15 +1525,6 @@ namespace bvm2 { res.m_Data[wlk.m_pKey->m_KeyInContract.m_iEpoch] = *wlk.m_pVal; } - - static double ToDouble(Shaders::Nephrite::Float x) - { - if (x.IsZero()) - return 0; - - return ldexp(x.m_Num, x.m_Order); - } - struct Entry :public Shaders::Nephrite::Trove { Entry() @@ -2870,6 +2906,65 @@ namespace bvm2 { verify_test(RunGuarded_T(cid, args2.s_iMethod, args2)); verify_test(args2.m_Arg1.RoundDown(d)); verify_test(d == -static_cast(a + b)); + + + // Multiplication + + args2.m_Arg1 = a; + args2.m_Arg2 = b; + args2.m_Op = 2; // mul + args2.m_Arg1 <<= 70; + args2.m_Arg2 >>= 300; + + double a_ = ToDouble(args2.m_Arg1); + double b_ = ToDouble(args2.m_Arg2); + double c_ = a_ * b_; + + verify_test(RunGuarded_T(cid, args2.s_iMethod, args2)); + double d_ = ToDouble(args2.m_Arg1); + + AssertDoubleRelEqual(c_, d_); + + args2.m_Arg1 = a; + args2.m_Arg1 <<= 705; + args2.m_Arg1.Negate(); + + a_ = ToDouble(args2.m_Arg1); + c_ = a_ * b_; + + verify_test(RunGuarded_T(cid, args2.s_iMethod, args2)); + d_ = ToDouble(args2.m_Arg1); + + AssertDoubleRelEqual(c_, d_); + + // Division + args2.m_Arg1 = a; + args2.m_Arg1 <<= 75; + args2.m_Arg2.Negate(); + args2.m_Op = 3; // div + + a_ = ToDouble(args2.m_Arg1); + b_ = ToDouble(args2.m_Arg2); + + verify_test(RunGuarded_T(cid, args2.s_iMethod, args2)); + + if (b) + { + c_ = a_ / b_; + d_ = ToDouble(args2.m_Arg1); + AssertDoubleRelEqual(c_, d_); + } + else + verify_test(args2.m_Arg1.IsNaN()); + + args2.m_Arg1 = 0; + verify_test(RunGuarded_T(cid, args2.s_iMethod, args2)); + + if (b) + verify_test(args2.m_Arg1.IsZero()); + else + verify_test(args2.m_Arg1.IsNaN()); + } }