// Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "fp-testing.h" #include "testing.h" #include "../../lib/evaluate/type.h" #include #include #include #include #include using namespace Fortran::evaluate; using namespace Fortran::common; using Real2 = Scalar>; using Real3 = Scalar>; using Real4 = Scalar>; using Real8 = Scalar>; using Real10 = Scalar>; using Real16 = Scalar>; using Integer4 = Scalar>; using Integer8 = Scalar>; void dumpTest() { struct { std::uint64_t raw; const char *expected; } table[] = { {0x7f876543, "NaN 0x7f876543"}, {0x7f800000, "Inf"}, {0xff800000, "-Inf"}, {0x00000000, "0.0"}, {0x80000000, "-0.0"}, {0x3f800000, "0x1.0p0"}, {0xbf800000, "-0x1.0p0"}, {0x40000000, "0x1.0p1"}, {0x3f000000, "0x1.0p-1"}, {0x7f7fffff, "0x1.fffffep127"}, {0x00800000, "0x1.0p-126"}, {0x00400000, "0x0.8p-127"}, {0x00000001, "0x0.000002p-127"}, {0, nullptr}, }; for (int j{0}; table[j].expected != nullptr; ++j) { TEST(Real4{Integer4{table[j].raw}}.DumpHexadecimal() == table[j].expected) ("%d", j); } } template void basicTests(int rm, Rounding rounding) { static constexpr int kind{R::bits / 8}; char desc[64]; using Word = typename R::Word; std::snprintf(desc, sizeof desc, "bits=%d, le=%d, kind=%d", R::bits, Word::littleEndian, kind); R zero; TEST(!zero.IsNegative())(desc); TEST(!zero.IsNotANumber())(desc); TEST(!zero.IsInfinite())(desc); TEST(zero.IsZero())(desc); MATCH(0, zero.Exponent())(desc); TEST(zero.RawBits().IsZero())(desc); MATCH(0, zero.RawBits().ToUInt64())(desc); TEST(zero.ABS().RawBits().IsZero())(desc); TEST(zero.Negate().RawBits().IEOR(Word::MASKL(1)).IsZero())(desc); TEST(zero.Compare(zero) == Relation::Equal)(desc); R minusZero{Word{std::uint64_t{1}}.SHIFTL(R::bits - 1)}; TEST(minusZero.IsNegative())(desc); TEST(!minusZero.IsNotANumber())(desc); TEST(!minusZero.IsInfinite())(desc); TEST(minusZero.IsZero())(desc); TEST(minusZero.ABS().RawBits().IsZero())(desc); TEST(minusZero.Negate().RawBits().IsZero())(desc); MATCH(0, minusZero.Exponent())(desc); MATCH(0, minusZero.RawBits().LEADZ())(desc); MATCH(1, minusZero.RawBits().POPCNT())(desc); TEST(minusZero.Compare(minusZero) == Relation::Equal)(desc); TEST(zero.Compare(minusZero) == Relation::Equal)(desc); ValueWithRealFlags vr; MATCH(0, vr.value.RawBits().ToUInt64())(desc); TEST(vr.flags.empty())(desc); R nan{Word{std::uint64_t{1}} .SHIFTL(R::bits) .SubtractSigned(Word{std::uint64_t{1}}) .value}; MATCH(R::bits, nan.RawBits().POPCNT())(desc); TEST(!nan.IsNegative())(desc); TEST(nan.IsNotANumber())(desc); TEST(!nan.IsInfinite())(desc); TEST(!nan.IsZero())(desc); TEST(zero.Compare(nan) == Relation::Unordered)(desc); TEST(minusZero.Compare(nan) == Relation::Unordered)(desc); TEST(nan.Compare(zero) == Relation::Unordered)(desc); TEST(nan.Compare(minusZero) == Relation::Unordered)(desc); TEST(nan.Compare(nan) == Relation::Unordered)(desc); int significandBits{R::precision - R::implicitMSB}; int exponentBits{R::bits - significandBits - 1}; std::uint64_t maxExponent{(std::uint64_t{1} << exponentBits) - 1}; MATCH(nan.Exponent(), maxExponent)(desc); R inf{Word{maxExponent}.SHIFTL(significandBits)}; TEST(!inf.IsNegative())(desc); TEST(!inf.IsNotANumber())(desc); TEST(inf.IsInfinite())(desc); TEST(!inf.IsZero())(desc); TEST(inf.RawBits().CompareUnsigned(inf.ABS().RawBits()) == Ordering::Equal) (desc); TEST(zero.Compare(inf) == Relation::Less)(desc); TEST(minusZero.Compare(inf) == Relation::Less)(desc); TEST(nan.Compare(inf) == Relation::Unordered)(desc); TEST(inf.Compare(inf) == Relation::Equal)(desc); R negInf{Word{maxExponent}.SHIFTL(significandBits).IOR(Word::MASKL(1))}; TEST(negInf.IsNegative())(desc); TEST(!negInf.IsNotANumber())(desc); TEST(negInf.IsInfinite())(desc); TEST(!negInf.IsZero())(desc); TEST(inf.RawBits().CompareUnsigned(negInf.ABS().RawBits()) == Ordering::Equal) (desc); TEST(inf.RawBits().CompareUnsigned(negInf.Negate().RawBits()) == Ordering::Equal) (desc); TEST(inf.Negate().RawBits().CompareUnsigned(negInf.RawBits()) == Ordering::Equal) (desc); TEST(zero.Compare(negInf) == Relation::Greater)(desc); TEST(minusZero.Compare(negInf) == Relation::Greater)(desc); TEST(nan.Compare(negInf) == Relation::Unordered)(desc); TEST(inf.Compare(negInf) == Relation::Greater)(desc); TEST(negInf.Compare(negInf) == Relation::Equal)(desc); for (std::uint64_t j{0}; j < 63; ++j) { char ldesc[128]; std::uint64_t x{1}; x <<= j; std::snprintf(ldesc, sizeof ldesc, "%s j=%d x=0x%llx rm=%d", desc, static_cast(j), static_cast(x), rm); Integer8 ix{x}; TEST(!ix.IsNegative())(ldesc); MATCH(x, ix.ToUInt64())(ldesc); vr = R::FromInteger(ix, rounding); TEST(!vr.value.IsNegative())(ldesc); TEST(!vr.value.IsNotANumber())(ldesc); TEST(!vr.value.IsZero())(ldesc); auto ivf = vr.value.template ToInteger(); if (j > (maxExponent / 2)) { TEST(vr.flags.test(RealFlag::Overflow))(ldesc); TEST(vr.value.IsInfinite())(ldesc); TEST(ivf.flags.test(RealFlag::Overflow))(ldesc); MATCH(0x7fffffffffffffff, ivf.value.ToUInt64())(ldesc); } else { TEST(vr.flags.empty())(ldesc); TEST(!vr.value.IsInfinite())(ldesc); TEST(ivf.flags.empty())(ldesc); MATCH(x, ivf.value.ToUInt64())(ldesc); std::stringstream ss; vr.value.AsFortran(ss, kind, rounding); std::string decimal{ss.str()}; const char *p{decimal.data()}; MATCH(x, static_cast(std::stold(decimal)))(ldesc); auto check{R::Read(p, rounding)}; auto icheck{check.value.template ToInteger()}; MATCH(x, icheck.value.ToUInt64())(ldesc); TEST(vr.value.Compare(check.value) == Relation::Equal)(ldesc); } TEST(vr.value.AINT().value.Compare(vr.value) == Relation::Equal)(ldesc); ix = ix.Negate().value; TEST(ix.IsNegative())(ldesc); x = -x; std::int64_t nx = x; MATCH(x, ix.ToUInt64())(ldesc); MATCH(nx, ix.ToInt64())(ldesc); vr = R::FromInteger(ix); TEST(vr.value.IsNegative())(ldesc); TEST(!vr.value.IsNotANumber())(ldesc); TEST(!vr.value.IsZero())(ldesc); ivf = vr.value.template ToInteger(); if (j > (maxExponent / 2)) { TEST(vr.flags.test(RealFlag::Overflow))(ldesc); TEST(vr.value.IsInfinite())(ldesc); TEST(ivf.flags.test(RealFlag::Overflow))(ldesc); MATCH(0x8000000000000000, ivf.value.ToUInt64())(ldesc); } else { TEST(vr.flags.empty())(ldesc); TEST(!vr.value.IsInfinite())(ldesc); TEST(ivf.flags.empty())(ldesc); MATCH(x, ivf.value.ToUInt64())(ldesc); MATCH(nx, ivf.value.ToInt64())(ldesc); } TEST(vr.value.AINT().value.Compare(vr.value) == Relation::Equal)(ldesc); } } // Takes an integer and distributes its bits across a floating // point value. The LSB is used to complement the result. std::uint32_t MakeReal(std::uint32_t n) { int shifts[] = {-1, 31, 23, 30, 22, 0, 24, 29, 25, 28, 26, 1, 16, 21, 2, -1}; std::uint32_t x{0}; for (int j{1}; shifts[j] >= 0; ++j) { x |= ((n >> j) & 1) << shifts[j]; } x ^= -(n & 1); return x; } std::uint64_t MakeReal(std::uint64_t n) { int shifts[] = { -1, 63, 52, 62, 51, 0, 53, 61, 54, 60, 55, 59, 1, 16, 50, 2, -1}; std::uint64_t x{0}; for (int j{1}; shifts[j] >= 0; ++j) { x |= ((n >> j) & 1) << shifts[j]; } x ^= -(n & 1); return x; } inline bool IsNaN(std::uint32_t x) { return (x & 0x7f800000) == 0x7f800000 && (x & 0x007fffff) != 0; } inline bool IsNaN(std::uint64_t x) { return (x & 0x7ff0000000000000) == 0x7ff0000000000000 && (x & 0x000fffffffffffff) != 0; } inline bool IsInfinite(std::uint32_t x) { return (x & 0x7fffffff) == 0x7f800000; } inline bool IsInfinite(std::uint64_t x) { return (x & 0x7fffffffffffffff) == 0x7ff0000000000000; } inline bool IsNegative(std::uint32_t x) { return (x & 0x80000000) != 0; } inline bool IsNegative(std::uint64_t x) { return (x & 0x8000000000000000) != 0; } inline std::uint32_t NormalizeNaN(std::uint32_t x) { if (IsNaN(x)) { x = 0x7fe00000; } return x; } inline std::uint64_t NormalizeNaN(std::uint64_t x) { if (IsNaN(x)) { x = 0x7ffc000000000000; } return x; } enum FlagBits { Overflow = 1, DivideByZero = 2, InvalidArgument = 4, Underflow = 8, Inexact = 16, }; std::uint32_t FlagsToBits(const RealFlags &flags) { std::uint32_t bits{0}; #ifndef __clang__ // TODO: clang support for fenv.h is broken, so tests of flag settings // are disabled. if (flags.test(RealFlag::Overflow)) { bits |= Overflow; } if (flags.test(RealFlag::DivideByZero)) { bits |= DivideByZero; } if (flags.test(RealFlag::InvalidArgument)) { bits |= InvalidArgument; } if (flags.test(RealFlag::Underflow)) { bits |= Underflow; } if (flags.test(RealFlag::Inexact)) { bits |= Inexact; } #endif // __clang__ return bits; } template void inttest(std::int64_t x, int pass, Rounding rounding) { union { UINT ui; FLT f; } u; ScopedHostFloatingPointEnvironment fpenv; Integer8 ix{x}; ValueWithRealFlags real; real = real.value.FromInteger(ix, rounding); #ifndef __clang__ // broken and also slow fpenv.ClearFlags(); #endif FLT fcheck = x; // TODO unsigned too auto actualFlags{FlagsToBits(fpenv.CurrentFlags())}; u.f = fcheck; UINT rcheck{NormalizeNaN(u.ui)}; UINT check = real.value.RawBits().ToUInt64(); MATCH(rcheck, check)("%d 0x%llx", pass, x); MATCH(actualFlags, FlagsToBits(real.flags))("%d 0x%llx", pass, x); } template FLT ToIntPower(FLT x, int power) { if (power == 0) { return x / x; } bool negative{power < 0}; if (negative) { power = -power; } FLT result{1}; while (power > 0) { if (power & 1) { result *= x; } x *= x; power >>= 1; } if (negative) { result = 1.0 / result; } return result; } template FLT TimesIntPowerOfTen(FLT x, int power) { if (power > decimalDigits || power < -decimalDigits) { auto maxExactPowerOfTen{ TimesIntPowerOfTen(1, decimalDigits)}; auto big{ToIntPower(maxExactPowerOfTen, power / decimalDigits)}; auto small{ TimesIntPowerOfTen(1, power % decimalDigits)}; return (x * big) * small; } return x * ToIntPower(10.0, power); } template void subsetTests(int pass, Rounding rounding, std::uint32_t opds) { for (int j{0}; j < 63; ++j) { std::int64_t x{1}; x <<= j; inttest(x, pass, rounding); inttest(-x, pass, rounding); } inttest(0, pass, rounding); inttest( static_cast(0x8000000000000000), pass, rounding); union { UINT ui; FLT f; } u; ScopedHostFloatingPointEnvironment fpenv; for (UINT j{0}; j < opds; ++j) { UINT rj{MakeReal(j)}; u.ui = rj; FLT fj{u.f}; REAL x{typename REAL::Word{std::uint64_t{rj}}}; // unary operations { ValueWithRealFlags aint{x.AINT()}; #ifndef __clang__ // broken and also slow fpenv.ClearFlags(); #endif FLT fcheck{std::trunc(fj)}; auto actualFlags{FlagsToBits(fpenv.CurrentFlags())}; actualFlags &= ~Inexact; // x86 std::trunc can set Inexact; AINT ain't u.f = fcheck; #ifndef __clang__ if (IsNaN(u.ui)) { actualFlags |= InvalidArgument; // x86 std::trunc(NaN) workaround } #endif UINT rcheck{NormalizeNaN(u.ui)}; UINT check = aint.value.RawBits().ToUInt64(); MATCH(rcheck, check) ("%d AINT(0x%llx)", pass, static_cast(rj)); MATCH(actualFlags, FlagsToBits(aint.flags)) ("%d AINT(0x%llx)", pass, static_cast(rj)); } { MATCH(IsNaN(rj), x.IsNotANumber()) ("%d IsNaN(0x%llx)", pass, static_cast(rj)); MATCH(IsInfinite(rj), x.IsInfinite()) ("%d IsInfinite(0x%llx)", pass, static_cast(rj)); static constexpr int kind{REAL::bits / 8}; std::stringstream ss, css; x.AsFortran(ss, kind, rounding); std::string s{ss.str()}; if (IsNaN(rj)) { css << "(0._" << kind << "/0.)"; MATCH(css.str(), s) ("%d invalid(0x%llx)", pass, static_cast(rj)); } else if (IsInfinite(rj)) { css << '('; if (IsNegative(rj)) { css << '-'; } css << "1._" << kind << "/0.)"; MATCH(css.str(), s) ("%d overflow(0x%llx)", pass, static_cast(rj)); } else { const char *p = s.data(); if (*p == '(') { ++p; } auto readBack{REAL::Read(p, rounding)}; MATCH(rj, readBack.value.RawBits().ToUInt64()) ("%d Read(AsFortran()) 0x%llx %s %g", pass, static_cast(rj), s.data(), static_cast(fj)); MATCH('_', *p) ("%d Read(AsFortran()) 0x%llx %s %d", pass, static_cast(rj), s.data(), static_cast(p - s.data())); } } // dyadic operations for (UINT k{0}; k < opds; ++k) { UINT rk{MakeReal(k)}; u.ui = rk; FLT fk{u.f}; REAL y{typename REAL::Word{std::uint64_t{rk}}}; { ValueWithRealFlags sum{x.Add(y, rounding)}; #ifndef __clang__ // broken and also slow fpenv.ClearFlags(); #endif FLT fcheck{fj + fk}; auto actualFlags{FlagsToBits(fpenv.CurrentFlags())}; u.f = fcheck; UINT rcheck{NormalizeNaN(u.ui)}; UINT check = sum.value.RawBits().ToUInt64(); MATCH(rcheck, check) ("%d 0x%llx + 0x%llx", pass, static_cast(rj), static_cast(rk)); MATCH(actualFlags, FlagsToBits(sum.flags)) ("%d 0x%llx + 0x%llx", pass, static_cast(rj), static_cast(rk)); } { ValueWithRealFlags diff{x.Subtract(y, rounding)}; #ifndef __clang__ // broken and also slow fpenv.ClearFlags(); #endif FLT fcheck{fj - fk}; auto actualFlags{FlagsToBits(fpenv.CurrentFlags())}; u.f = fcheck; UINT rcheck{NormalizeNaN(u.ui)}; UINT check = diff.value.RawBits().ToUInt64(); MATCH(rcheck, check) ("%d 0x%llx - 0x%llx", pass, static_cast(rj), static_cast(rk)); MATCH(actualFlags, FlagsToBits(diff.flags)) ("%d 0x%llx - 0x%llx", pass, static_cast(rj), static_cast(rk)); } { ValueWithRealFlags prod{x.Multiply(y, rounding)}; #ifndef __clang__ // broken and also slow fpenv.ClearFlags(); #endif FLT fcheck{fj * fk}; auto actualFlags{FlagsToBits(fpenv.CurrentFlags())}; u.f = fcheck; UINT rcheck{NormalizeNaN(u.ui)}; UINT check = prod.value.RawBits().ToUInt64(); MATCH(rcheck, check) ("%d 0x%llx * 0x%llx", pass, static_cast(rj), static_cast(rk)); MATCH(actualFlags, FlagsToBits(prod.flags)) ("%d 0x%llx * 0x%llx", pass, static_cast(rj), static_cast(rk)); } { ValueWithRealFlags quot{x.Divide(y, rounding)}; #ifndef __clang__ // broken and also slow fpenv.ClearFlags(); #endif FLT fcheck{fj / fk}; auto actualFlags{FlagsToBits(fpenv.CurrentFlags())}; u.f = fcheck; UINT rcheck{NormalizeNaN(u.ui)}; UINT check = quot.value.RawBits().ToUInt64(); MATCH(rcheck, check) ("%d 0x%llx / 0x%llx", pass, static_cast(rj), static_cast(rk)); MATCH(actualFlags, FlagsToBits(quot.flags)) ("%d 0x%llx / 0x%llx", pass, static_cast(rj), static_cast(rk)); } } } } void roundTest(int rm, Rounding rounding, std::uint32_t opds) { basicTests(rm, rounding); basicTests(rm, rounding); basicTests(rm, rounding); basicTests(rm, rounding); basicTests(rm, rounding); basicTests(rm, rounding); ScopedHostFloatingPointEnvironment::SetRounding(rounding); subsetTests(rm, rounding, opds); subsetTests(rm, rounding, opds); } int main() { dumpTest(); std::uint32_t opds{512}; // for quick testing by default if (const char *p{std::getenv("REAL_TEST_OPERANDS")}) { // Use 8192 or 16384 for more exhaustive testing. opds = std::atol(p); } roundTest(0, Rounding{RoundingMode::TiesToEven}, opds); roundTest(1, Rounding{RoundingMode::ToZero}, opds); roundTest(2, Rounding{RoundingMode::Up}, opds); roundTest(3, Rounding{RoundingMode::Down}, opds); // TODO: how to test Rounding::TiesAwayFromZero on x86? return testing::Complete(); }