diff --git a/src.cmake b/src.cmake index 44005aa041..daf96a588f 100644 --- a/src.cmake +++ b/src.cmake @@ -273,6 +273,7 @@ set(ENGINETESTLIST ${COMMON_DIR}/ColorTest.cpp ${COMMON_DIR}/StringTest.cpp ${COMMON_DIR}/cm/unittest.cpp + ${COMMON_DIR}/MathTest.cpp ${COMMON_DIR}/UtilTest.cpp ${ENGINE_DIR}/framework/CommandSystemTest.cpp ) diff --git a/src/common/Endian.h b/src/common/Endian.h index 284e595631..c8276f709a 100644 --- a/src/common/Endian.h +++ b/src/common/Endian.h @@ -119,6 +119,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # error The file boost/detail/endian.hpp needs to be set up for your CPU type. #endif +// forward declaration +namespace Util { + template + ToT bit_cast(FromT from); +} + // Compilers are smart enough to optimize these to a single bswap instruction inline int16_t Swap16(int16_t x) { diff --git a/src/common/Math.h b/src/common/Math.h index 54ab4dfa59..2185fc0706 100644 --- a/src/common/Math.h +++ b/src/common/Math.h @@ -35,7 +35,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Math { - // Returns min if value is NaN + // This is designed to return min if value is NaN. That's not guaranteed to work when + // compiling with fast-math flags though. template inline WARN_UNUSED_RESULT T Clamp(T value, T min, T max) { @@ -47,6 +48,22 @@ namespace Math { return value; } + // IsFinite: Replacements for std::isfinite that should work even with fast-math flags + + // An IEEE754 float is finite when the exponent is not all ones. + // 'volatile' serves as an optimization barrier against the compiler assuming that + // the float can never have a NaN bit pattern. + inline bool IsFinite(float x) + { + volatile uint32_t bits = Util::bit_cast(x); + return ~bits & 0x7f800000; + } + + inline bool IsFinite(double x) + { + volatile uint64_t bits = Util::bit_cast(x); + return ~bits & 0x7ff0000000000000; + } } #include "math/Vector.h" diff --git a/src/common/MathTest.cpp b/src/common/MathTest.cpp new file mode 100644 index 0000000000..68394c0978 --- /dev/null +++ b/src/common/MathTest.cpp @@ -0,0 +1,66 @@ +/* +=========================================================================== +Daemon BSD Source Code +Copyright (c) 2024, Daemon Developers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Daemon developers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL DAEMON DEVELOPERS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +=========================================================================== +*/ + +#include + +#include "common/Common.h" + +namespace Math { +namespace { + +// Uncomment this and the tests should fail with GCC or Clang in release mode :-) +//#define IsFinite std::isfinite + +TEST(IsFiniteTest, Float) +{ + ASSERT_TRUE(IsFinite(-0.0f)); + ASSERT_TRUE(IsFinite(std::numeric_limits::max())); + ASSERT_TRUE(IsFinite(std::numeric_limits::min())); + ASSERT_TRUE(IsFinite(123.45f)); + + ASSERT_FALSE(IsFinite(std::stof("nan"))); + ASSERT_FALSE(IsFinite(std::stof("inf"))); + ASSERT_FALSE(IsFinite(std::stof("-inf"))); +} + +TEST(IsFiniteTest, Double) +{ + ASSERT_TRUE(IsFinite(-0.0)); + ASSERT_TRUE(IsFinite(std::numeric_limits::max())); + ASSERT_TRUE(IsFinite(std::numeric_limits::min())); + ASSERT_TRUE(IsFinite(123.45)); + + ASSERT_FALSE(IsFinite(std::stod("nan"))); + ASSERT_FALSE(IsFinite(std::stod("inf"))); + ASSERT_FALSE(IsFinite(std::stod("-inf"))); +} + +} // namespace Math +} // namespace diff --git a/src/engine/audio/Audio.cpp b/src/engine/audio/Audio.cpp index 68631387cd..f6a7d42412 100644 --- a/src/engine/audio/Audio.cpp +++ b/src/engine/audio/Audio.cpp @@ -82,7 +82,7 @@ namespace Audio { } bool IsValidVector(Vec3 v) { - return not std::isnan(v[0]) and not std::isnan(v[1]) and not std::isnan(v[2]); + return Math::IsFinite(v[0]) && Math::IsFinite(v[1]) && Math::IsFinite(v[2]); } bool Init() { @@ -436,7 +436,7 @@ namespace Audio { return; } - if (slotNum < 0 or slotNum >= N_REVERB_SLOTS or std::isnan(ratio)) { + if (slotNum < 0 or slotNum >= N_REVERB_SLOTS or !Math::IsFinite(ratio)) { return; } diff --git a/src/engine/audio/Emitter.cpp b/src/engine/audio/Emitter.cpp index 042a2fc119..f94812e386 100644 --- a/src/engine/audio/Emitter.cpp +++ b/src/engine/audio/Emitter.cpp @@ -198,7 +198,7 @@ namespace Audio { void UpdateReverbSlot(int slotNum, std::string name, float ratio) { ASSERT_GE(slotNum, 0); ASSERT_LT(slotNum, N_REVERB_SLOTS); - ASSERT(!std::isnan(ratio)); + ASSERT(Math::IsFinite(ratio)); auto& slot = reverbSlots[slotNum];