Skip testing subnormals when flushed to zero

When flush-to-zero (FTZ) mode is enabled, std::nextafter() returns zero
when a subnormal gets passed in. This can cause infinite loops when
trying to iterate over a range.

The inc() function returns the next larger value which does not compare
equal to the input. The only exception is negative zero, which produces
positive zero, since the CPU still discerns between these two when FTZ
has been enabled.

Bug: b/169904252
Change-Id: Idff987a4c14bd4f778e940ec4fc4de26e0a74372
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/62869
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
diff --git a/src/Pipeline/ShaderCore.cpp b/src/Pipeline/ShaderCore.cpp
index cbc5a6e..f4695cf 100644
--- a/src/Pipeline/ShaderCore.cpp
+++ b/src/Pipeline/ShaderCore.cpp
@@ -166,7 +166,7 @@
 	return (x + x * (x2 * (a2 + x2 * (a4 + x2 * (a6 + x2 * (a8 + x2 * (a10 + x2 * (a12 + x2 * (a14 + x2 * a16)))))))));
 }
 
-// Polynomal approximation of order 5 for sin(x * 2 * pi) in the range [-1/4, 1/4]
+// Polynomial approximation of order 5 for sin(x * 2 * pi) in the range [-1/4, 1/4]
 static Float4 Sin5(Float4 x)
 {
 	// A * x^5 + B * x^3 + C * x
diff --git a/src/System/Math.hpp b/src/System/Math.hpp
index 8c6a069..bafa046 100644
--- a/src/System/Math.hpp
+++ b/src/System/Math.hpp
@@ -370,11 +370,36 @@
 }
 
 // Convert floating value v to fixed point with p digits after the decimal point
-constexpr int toFixedPoint(float v, int p)
+inline constexpr int toFixedPoint(float v, int p)
 {
 	return static_cast<int>(v * (1 << p));
 }
 
+// Returns the next floating-point number which is not treated identical to the input.
+// Note that std::nextafter() does not skip representations flushed to zero.
+[[nodiscard]] inline float inc(float x)
+{
+	int x1 = bit_cast<int>(x);
+
+	while(bit_cast<float>(x1) == x)
+	{
+		// Since IEEE 754 uses ones' complement and integers are two's complement,
+		// we need to explicitly hop from negative zero to positive zero.
+		if(x1 == (int)0x80000000)  // -0.0f
+		{
+			// Note that while the comparison -0.0f == +0.0f returns true, this
+			// function returns the next value which can be treated differently.
+			return +0.0f;
+		}
+
+		// Negative ones' complement value are made less negative by subtracting 1
+		// in two's complement representation.
+		x1 += (x1 >= 0) ? 1 : -1;
+	}
+
+	return bit_cast<float>(x1);
+}
+
 }  // namespace sw
 
 #endif  // sw_Math_hpp
diff --git a/tests/MathUnitTests/unittests.cpp b/tests/MathUnitTests/unittests.cpp
index 0721c6f..5943f1c 100644
--- a/tests/MathUnitTests/unittests.cpp
+++ b/tests/MathUnitTests/unittests.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "System/Half.hpp"
+#include "System/Math.hpp"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -21,7 +22,7 @@
 
 using namespace sw;
 
-// Polynomal approximation of order 5 for sin(x * 2 * pi) in the range [-1/4, 1/4]
+// Polynomial approximation of order 5 for sin(x * 2 * pi) in the range [-1/4, 1/4]
 static float sin5(float x)
 {
 	// A * x^5 + B * x^3 + C * x
@@ -40,7 +41,7 @@
 	const float tolerance = powf(2.0f, -12.0f);  // Vulkan requires absolute error <= 2^−11 inside the range [−pi, pi]
 	const float pi = 3.1415926535f;
 
-	for(float x = -pi; x <= pi; x = nextafterf(x, +INFINITY))
+	for(float x = -pi; x <= pi; x = inc(x))
 	{
 		// Range reduction and mirroring
 		float x_2 = 0.25f - x * (0.5f / pi);
@@ -57,7 +58,7 @@
 	const float tolerance = powf(2.0f, -12.0f);  // Vulkan requires absolute error <= 2^−11 inside the range [−pi, pi]
 	const float pi = 3.1415926535f;
 
-	for(float x = -pi; x <= pi; x = nextafterf(x, +INFINITY))
+	for(float x = -pi; x <= pi; x = inc(x))
 	{
 		// Phase shift, range reduction, and mirroring
 		float x_2 = x * (0.5f / pi);