Skip to content

Commit faea421

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 faea421

2 files changed

Lines changed: 236 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: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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+
// static_cast: char is unsigned on some targets and char{ -11 } would narrow.
133+
for (const char x : { static_cast<char>(-11), char{ 0 }, char{ 13 }, char{ 127 } })
134+
{
135+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << static_cast<int>(x);
136+
}
137+
for (const unsigned char x : { static_cast<unsigned char>(0u), static_cast<unsigned char>(200u) })
138+
{
139+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << static_cast<int>(x);
140+
}
141+
for (const int x : { -46340, -7, 0, 7, 46340 })
142+
{
143+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
144+
}
145+
for (const unsigned int x : { 0u, 65535u, 65536u })
146+
{
147+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
148+
}
149+
// Largest long whose square fits the unsigned result; long is 32-bit on LLP64.
150+
const long longSafe = static_cast<long>(std::sqrt(static_cast<double>(std::numeric_limits<long>::max()))) - 2;
151+
for (const long x : { -longSafe, -42L, 0L, 42L, longSafe })
152+
{
153+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
154+
}
155+
for (const long long x : { -3037000499LL, 0LL, 3037000499LL })
156+
{
157+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
158+
}
159+
for (const double x : { -2.5, 0.0, 3.75, 1e150 })
160+
{
161+
EXPECT_EQ(itk::Math::squared_magnitude(x), vnl_math::squared_magnitude(x)) << "x=" << x;
162+
EXPECT_EQ(itk::Math::squared_magnitude(static_cast<float>(x)), vnl_math::squared_magnitude(static_cast<float>(x)))
163+
<< "x=" << x;
164+
EXPECT_EQ(itk::Math::squared_magnitude(static_cast<long double>(x)),
165+
vnl_math::squared_magnitude(static_cast<long double>(x)))
166+
<< "x=" << x;
167+
}
168+
}
169+
170+
TEST(itkMathVnlParity, RemainderSignMatrix)
171+
{
172+
for (const int x : { -7, -3, 0, 3, 7, 1000000 })
173+
{
174+
for (const int y : { -5, -3, 3, 5, 1000003 })
175+
{
176+
EXPECT_EQ(itk::Math::remainder_truncated(x, y), vnl_math::remainder_truncated(x, y)) << "x=" << x << " y=" << y;
177+
EXPECT_EQ(itk::Math::remainder_floored(x, y), vnl_math::remainder_floored(x, y)) << "x=" << x << " y=" << y;
178+
}
179+
}
180+
for (const double x : { -7.5, -2.25, 0.0, 2.25, 7.5 })
181+
{
182+
for (const double y : { -2.0, -0.75, 0.75, 2.0 })
183+
{
184+
EXPECT_EQ(itk::Math::remainder_truncated(x, y), vnl_math::remainder_truncated(x, y)) << "x=" << x << " y=" << y;
185+
EXPECT_EQ(itk::Math::remainder_floored(x, y), vnl_math::remainder_floored(x, y)) << "x=" << x << " y=" << y;
186+
const auto fx = static_cast<float>(x);
187+
const auto fy = static_cast<float>(y);
188+
EXPECT_EQ(itk::Math::remainder_truncated(fx, fy), vnl_math::remainder_truncated(fx, fy))
189+
<< "x=" << x << " y=" << y;
190+
EXPECT_EQ(itk::Math::remainder_floored(fx, fy), vnl_math::remainder_floored(fx, fy)) << "x=" << x << " y=" << y;
191+
}
192+
}
193+
}
194+
195+
TEST(itkMathVnlParity, AngleNormalization)
196+
{
197+
for (const double angle : angleSamples)
198+
{
199+
const double itkWrapped = itk::Math::angle_0_to_2pi(angle);
200+
const double vnlWrapped = vnl_math::angle_0_to_2pi(angle);
201+
EXPECT_EQ(itkWrapped, vnlWrapped) << "angle=" << angle;
202+
EXPECT_EQ(std::signbit(itkWrapped), std::signbit(vnlWrapped)) << "angle=" << angle;
203+
EXPECT_EQ(itk::Math::angle_minuspi_to_pi(angle), vnl_math::angle_minuspi_to_pi(angle)) << "angle=" << angle;
204+
}
205+
}
206+
207+
TEST(itkMathVnlParity, Constants)
208+
{
209+
EXPECT_EQ(itk::Math::e, vnl_math::e);
210+
EXPECT_EQ(itk::Math::log2e, vnl_math::log2e);
211+
EXPECT_EQ(itk::Math::log10e, vnl_math::log10e);
212+
EXPECT_EQ(itk::Math::ln2, vnl_math::ln2);
213+
EXPECT_EQ(itk::Math::ln10, vnl_math::ln10);
214+
EXPECT_EQ(itk::Math::pi, vnl_math::pi);
215+
EXPECT_EQ(itk::Math::twopi, vnl_math::twopi);
216+
EXPECT_EQ(itk::Math::pi_over_2, vnl_math::pi_over_2);
217+
EXPECT_EQ(itk::Math::pi_over_4, vnl_math::pi_over_4);
218+
EXPECT_EQ(itk::Math::pi_over_180, vnl_math::pi_over_180);
219+
EXPECT_EQ(itk::Math::one_over_pi, vnl_math::one_over_pi);
220+
EXPECT_EQ(itk::Math::two_over_pi, vnl_math::two_over_pi);
221+
EXPECT_EQ(itk::Math::deg_per_rad, vnl_math::deg_per_rad);
222+
EXPECT_EQ(itk::Math::sqrt2pi, vnl_math::sqrt2pi);
223+
EXPECT_EQ(itk::Math::two_over_sqrtpi, vnl_math::two_over_sqrtpi);
224+
EXPECT_EQ(itk::Math::one_over_sqrt2pi, vnl_math::one_over_sqrt2pi);
225+
EXPECT_EQ(itk::Math::sqrt2, vnl_math::sqrt2);
226+
EXPECT_EQ(itk::Math::sqrt1_2, vnl_math::sqrt1_2);
227+
EXPECT_EQ(itk::Math::sqrt1_3, vnl_math::sqrt1_3);
228+
EXPECT_EQ(itk::Math::euler, vnl_math::euler);
229+
EXPECT_EQ(itk::Math::eps, vnl_math::eps);
230+
EXPECT_EQ(itk::Math::float_eps, vnl_math::float_eps);
231+
EXPECT_EQ(itk::Math::float_sqrteps, vnl_math::float_sqrteps);
232+
// Intentional 1 ULP divergence: itk::Math::sqrteps is the exact sqrt(eps) = 2^-26,
233+
// vnl_math::sqrteps rounds 1 ULP above it (see the ITKv6 migration guide).
234+
EXPECT_EQ(std::nextafter(itk::Math::sqrteps, 1.0), vnl_math::sqrteps);
235+
}

0 commit comments

Comments
 (0)