|
| 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