Skip to content

Commit f2c2a91

Browse files
committed
ENH: Add GTest verifying itk::Math natives match vnl_math bit-for-bit
Parity coverage mirrors the breadth of vxl's vnl_math test suite: halfway-case rounding, floor/ceil, sign functions, powers, squared_magnitude across all overload types, the remainder sign matrix, angle normalization including the fmod boundary guard, and the constants (sqrteps's intentional 1 ULP divergence asserted explicitly).
1 parent fcc80ce commit f2c2a91

2 files changed

Lines changed: 233 additions & 0 deletions

File tree

Modules/Core/Common/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,6 +1428,7 @@ set(
14281428
itkMathCastWithRangeCheckGTest.cxx
14291429
itkMathGTest.cxx
14301430
itkMathRoundGTest.cxx
1431+
itkMathVnlParityGTest.cxx
14311432
itkMatrixGTest.cxx
14321433
itkMersenneTwisterRandomVariateGeneratorGTest.cxx
14331434
itkMetaDataDictionaryGTest.cxx
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
19+
// Verifies that the native itk::Math functions are drop-in replacements for
20+
// the vnl_math functions they superseded: identical results, bit-for-bit,
21+
// over the inputs each contract supports.
22+
23+
#include "itkMath.h"
24+
#include "itkGTest.h"
25+
#include <vnl/vnl_math.h>
26+
#include <cmath>
27+
#include <limits>
28+
29+
namespace
30+
{
31+
constexpr double maxHalfIntDomain = static_cast<double>(INT_MAX / 2) - 2.0;
32+
33+
const double roundingSamples[] = { 0.0, -0.0, 0.5, -0.5, 1.5, -1.5, 2.5, -2.5, 3.5,
34+
-3.5, 0.4999, -0.4999, 0.5001, -0.5001, 7.0, -7.0, 7.25, -7.25,
35+
1e6, -1e6, 1234567.89, -987654.3, 0.0001, -0.0001, 1.5e9, -1.5e9 };
36+
37+
const double angleSamples[] = { 0.0,
38+
-0.0,
39+
0.1,
40+
-0.1,
41+
itk::Math::pi / 2,
42+
-itk::Math::pi / 2,
43+
itk::Math::pi,
44+
-itk::Math::pi,
45+
3 * itk::Math::pi / 2,
46+
-3 * itk::Math::pi / 2,
47+
itk::Math::twopi,
48+
-itk::Math::twopi,
49+
itk::Math::twopi + 0.1,
50+
-itk::Math::twopi - 0.1,
51+
100.25,
52+
-100.25,
53+
1e6,
54+
-1e6,
55+
-1e-18,
56+
std::nextafter(0.0, -1.0) };
57+
} // namespace
58+
59+
TEST(itkMathVnlParity, RoundHalfIntegerToEven)
60+
{
61+
for (const double x : roundingSamples)
62+
{
63+
EXPECT_EQ(itk::Math::rnd_halfinttoeven(x), vnl_math::rnd_halfinttoeven(x)) << "x=" << x;
64+
EXPECT_EQ(itk::Math::rnd_halfinttoeven(static_cast<float>(x)), vnl_math::rnd_halfinttoeven(static_cast<float>(x)))
65+
<< "x=" << x;
66+
EXPECT_EQ(itk::Math::rnd(x), vnl_math::rnd(x)) << "x=" << x;
67+
EXPECT_EQ(itk::Math::rnd(static_cast<float>(x)), vnl_math::rnd(static_cast<float>(x))) << "x=" << x;
68+
}
69+
}
70+
71+
TEST(itkMathVnlParity, RoundHalfIntegerUp)
72+
{
73+
for (const double x : roundingSamples)
74+
{
75+
if (std::abs(x) >= maxHalfIntDomain)
76+
{
77+
continue; // outside the documented |x| < INT_MAX/2 contract
78+
}
79+
EXPECT_EQ(itk::Math::rnd_halfintup(x), vnl_math::rnd_halfintup(x)) << "x=" << x;
80+
EXPECT_EQ(itk::Math::rnd_halfintup(static_cast<float>(x)), vnl_math::rnd_halfintup(static_cast<float>(x)))
81+
<< "x=" << x;
82+
}
83+
}
84+
85+
TEST(itkMathVnlParity, FloorCeil)
86+
{
87+
for (const double x : roundingSamples)
88+
{
89+
EXPECT_EQ(itk::Math::floor(x), vnl_math::floor(x)) << "x=" << x;
90+
EXPECT_EQ(itk::Math::floor(static_cast<float>(x)), vnl_math::floor(static_cast<float>(x))) << "x=" << x;
91+
EXPECT_EQ(itk::Math::ceil(x), vnl_math::ceil(x)) << "x=" << x;
92+
EXPECT_EQ(itk::Math::ceil(static_cast<float>(x)), vnl_math::ceil(static_cast<float>(x))) << "x=" << x;
93+
}
94+
}
95+
96+
TEST(itkMathVnlParity, SgnAndSgn0)
97+
{
98+
for (const int x : { -5, -1, 0, 1, 5, INT_MAX, INT_MIN })
99+
{
100+
EXPECT_EQ(itk::Math::sgn(x), vnl_math::sgn(x)) << "x=" << x;
101+
EXPECT_EQ(itk::Math::sgn0(x), vnl_math::sgn0(x)) << "x=" << x;
102+
}
103+
for (const double x : { -2.5, -0.0, 0.0, 1e-300, -1e-300, 3.75 })
104+
{
105+
EXPECT_EQ(itk::Math::sgn(x), vnl_math::sgn(x)) << "x=" << x;
106+
EXPECT_EQ(itk::Math::sgn0(x), vnl_math::sgn0(x)) << "x=" << x;
107+
EXPECT_EQ(itk::Math::sgn(static_cast<float>(x)), vnl_math::sgn(static_cast<float>(x))) << "x=" << x;
108+
EXPECT_EQ(itk::Math::sgn0(static_cast<float>(x)), vnl_math::sgn0(static_cast<float>(x))) << "x=" << x;
109+
}
110+
}
111+
112+
TEST(itkMathVnlParity, SqrAndCube)
113+
{
114+
for (const int x : { -46340, -7, -1, 0, 1, 7, 46340 })
115+
{
116+
EXPECT_EQ(itk::Math::sqr(x), vnl_math::sqr(x)) << "x=" << x;
117+
}
118+
for (const int x : { -1290, -3, 0, 3, 1290 })
119+
{
120+
EXPECT_EQ(itk::Math::cube(x), vnl_math::cube(x)) << "x=" << x;
121+
}
122+
for (const double x : { -2.5, 0.0, 0.125, 3.75, 1e150 })
123+
{
124+
EXPECT_EQ(itk::Math::sqr(x), vnl_math::sqr(x)) << "x=" << x;
125+
EXPECT_EQ(itk::Math::cube(x), vnl_math::cube(x)) << "x=" << x;
126+
EXPECT_EQ(itk::Math::sqr(static_cast<float>(x)), vnl_math::sqr(static_cast<float>(x))) << "x=" << x;
127+
}
128+
}
129+
130+
TEST(itkMathVnlParity, SquaredMagnitude)
131+
{
132+
for (const char x : { char{ -11 }, char{ 0 }, char{ 13 }, char{ 127 } })
133+
{
134+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << static_cast<int>(x);
135+
}
136+
for (const unsigned char x : { static_cast<unsigned char>(0u), static_cast<unsigned char>(200u) })
137+
{
138+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << static_cast<int>(x);
139+
}
140+
for (const int x : { -46340, -7, 0, 7, 46340 })
141+
{
142+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
143+
}
144+
for (const unsigned int x : { 0u, 65535u, 65536u })
145+
{
146+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
147+
}
148+
for (const long x : { -3037000499L, -42L, 0L, 42L, 3037000499L })
149+
{
150+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
151+
}
152+
for (const long long x : { -3037000499LL, 0LL, 3037000499LL })
153+
{
154+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
155+
}
156+
for (const double x : { -2.5, 0.0, 3.75, 1e150 })
157+
{
158+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
159+
EXPECT_EQ(itk::Math::squared_magnitude(static_cast<float>(x)), vnl_math::squared_magnitude(static_cast<float>(x)))
160+
<< "x=" << x;
161+
EXPECT_EQ(itk::Math::squared_magnitude(static_cast<long double>(x)),
162+
vnl_math::squared_magnitude(static_cast<long double>(x)))
163+
<< "x=" << x;
164+
}
165+
}
166+
167+
TEST(itkMathVnlParity, RemainderSignMatrix)
168+
{
169+
for (const int x : { -7, -3, 0, 3, 7, 1000000 })
170+
{
171+
for (const int y : { -5, -3, 3, 5, 1000003 })
172+
{
173+
EXPECT_EQ(itk::Math::remainder_truncated(x, y), vnl_math::remainder_truncated(x, y)) << "x=" << x << " y=" << y;
174+
EXPECT_EQ(itk::Math::remainder_floored(x, y), vnl_math::remainder_floored(x, y)) << "x=" << x << " y=" << y;
175+
}
176+
}
177+
for (const double x : { -7.5, -2.25, 0.0, 2.25, 7.5 })
178+
{
179+
for (const double y : { -2.0, -0.75, 0.75, 2.0 })
180+
{
181+
EXPECT_EQ(itk::Math::remainder_truncated(x, y), vnl_math::remainder_truncated(x, y)) << "x=" << x << " y=" << y;
182+
EXPECT_EQ(itk::Math::remainder_floored(x, y), vnl_math::remainder_floored(x, y)) << "x=" << x << " y=" << y;
183+
const auto fx = static_cast<float>(x);
184+
const auto fy = static_cast<float>(y);
185+
EXPECT_EQ(itk::Math::remainder_truncated(fx, fy), vnl_math::remainder_truncated(fx, fy))
186+
<< "x=" << x << " y=" << y;
187+
EXPECT_EQ(itk::Math::remainder_floored(fx, fy), vnl_math::remainder_floored(fx, fy)) << "x=" << x << " y=" << y;
188+
}
189+
}
190+
}
191+
192+
TEST(itkMathVnlParity, AngleNormalization)
193+
{
194+
for (const double angle : angleSamples)
195+
{
196+
const double itkWrapped = itk::Math::angle_0_to_2pi(angle);
197+
const double vnlWrapped = vnl_math::angle_0_to_2pi(angle);
198+
EXPECT_EQ(itkWrapped, vnlWrapped) << "angle=" << angle;
199+
EXPECT_EQ(std::signbit(itkWrapped), std::signbit(vnlWrapped)) << "angle=" << angle;
200+
EXPECT_EQ(itk::Math::angle_minuspi_to_pi(angle), vnl_math::angle_minuspi_to_pi(angle)) << "angle=" << angle;
201+
}
202+
}
203+
204+
TEST(itkMathVnlParity, Constants)
205+
{
206+
EXPECT_EQ(itk::Math::e, vnl_math::e);
207+
EXPECT_EQ(itk::Math::log2e, vnl_math::log2e);
208+
EXPECT_EQ(itk::Math::log10e, vnl_math::log10e);
209+
EXPECT_EQ(itk::Math::ln2, vnl_math::ln2);
210+
EXPECT_EQ(itk::Math::ln10, vnl_math::ln10);
211+
EXPECT_EQ(itk::Math::pi, vnl_math::pi);
212+
EXPECT_EQ(itk::Math::twopi, vnl_math::twopi);
213+
EXPECT_EQ(itk::Math::pi_over_2, vnl_math::pi_over_2);
214+
EXPECT_EQ(itk::Math::pi_over_4, vnl_math::pi_over_4);
215+
EXPECT_EQ(itk::Math::pi_over_180, vnl_math::pi_over_180);
216+
EXPECT_EQ(itk::Math::one_over_pi, vnl_math::one_over_pi);
217+
EXPECT_EQ(itk::Math::two_over_pi, vnl_math::two_over_pi);
218+
EXPECT_EQ(itk::Math::deg_per_rad, vnl_math::deg_per_rad);
219+
EXPECT_EQ(itk::Math::sqrt2pi, vnl_math::sqrt2pi);
220+
EXPECT_EQ(itk::Math::two_over_sqrtpi, vnl_math::two_over_sqrtpi);
221+
EXPECT_EQ(itk::Math::one_over_sqrt2pi, vnl_math::one_over_sqrt2pi);
222+
EXPECT_EQ(itk::Math::sqrt2, vnl_math::sqrt2);
223+
EXPECT_EQ(itk::Math::sqrt1_2, vnl_math::sqrt1_2);
224+
EXPECT_EQ(itk::Math::sqrt1_3, vnl_math::sqrt1_3);
225+
EXPECT_EQ(itk::Math::euler, vnl_math::euler);
226+
EXPECT_EQ(itk::Math::eps, vnl_math::eps);
227+
EXPECT_EQ(itk::Math::float_eps, vnl_math::float_eps);
228+
EXPECT_EQ(itk::Math::float_sqrteps, vnl_math::float_sqrteps);
229+
// Intentional 1 ULP divergence: itk::Math::sqrteps is the exact sqrt(eps) = 2^-26,
230+
// vnl_math::sqrteps rounds 1 ULP above it (see the ITKv6 migration guide).
231+
EXPECT_EQ(std::nextafter(itk::Math::sqrteps, 1.0), vnl_math::sqrteps);
232+
}

0 commit comments

Comments
 (0)