2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2014,2015,2017, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 #include "gromacs/simd/simd_math.h"
46 #include "gromacs/math/utilities.h"
47 #include "gromacs/options/basicoptions.h"
48 #include "gromacs/simd/simd.h"
60 /*! \addtogroup module_simd */
63 #if GMX_SIMD_HAVE_REAL
65 class SimdMathTest : public SimdTest
68 ::testing::AssertionResult
69 compareSimdMathFunction(const char * refFuncExpr,
70 const char * simdFuncExpr,
71 const char * denormalsToZeroExpr,
73 SimdReal gmx_simdcall simdFunc(SimdReal x),
74 bool denormalsToZero);
77 /*! \brief Test approximate equality of SIMD vs reference version of a function.
79 * This macro takes vanilla C and SIMD flavors of a function and tests it with
80 * the number of points, range, and tolerances specified by the test fixture class.
82 #define GMX_EXPECT_SIMD_FUNC_NEAR(refFunc, tstFunc) \
83 EXPECT_PRED_FORMAT3(compareSimdMathFunction, refFunc, tstFunc, false)
85 /*! \brief Test approximate equality of SIMD vs reference function, denormals can be zero.
87 * This macro takes vanilla C and SIMD flavors of a function and tests it with
88 * the number of points, range, and tolerances specified by the test fixture class.
90 * This version of the function will also return success if the test function
91 * returns zero where the reference function returns a denormal value.
93 #define GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(refFunc, tstFunc) \
94 EXPECT_PRED_FORMAT3(compareSimdMathFunction, refFunc, tstFunc, true)
96 /*! \brief Implementation routine to compare SIMD vs reference functions.
98 * \param refFuncExpr Description of reference function expression
99 * \param simdFuncExpr Description of SIMD function expression
100 * \param denormalsToZeroExpr Description of denormal-to-zero setting
101 * \param refFunc Reference math function pointer
102 * \param simdFunc SIMD math function pointer
103 * \param denormalsToZero If true, the function will consider denormal
104 * values equivalent to 0.0.
106 * The function will be tested with the range and tolerances specified in
107 * the SimdBaseTest class. You should not never call this function directly,
108 * but use the macro GMX_EXPECT_SIMD_FUNC_NEAR(refFunc,tstFunc) instead.
110 ::testing::AssertionResult
111 SimdMathTest::compareSimdMathFunction(const char * refFuncExpr,
112 const char * simdFuncExpr,
113 const char * denormalsToZeroExpr,
114 real refFunc(real x),
115 SimdReal gmx_simdcall simdFunc(SimdReal x),
116 bool denormalsToZero)
118 std::vector<real> vx(GMX_SIMD_REAL_WIDTH);
119 std::vector<real> vref(GMX_SIMD_REAL_WIDTH);
120 std::vector<real> vtst(GMX_SIMD_REAL_WIDTH);
122 std::int64_t ulpDiff, maxUlpDiff;
124 real refValMaxUlpDiff, simdValMaxUlpDiff;
127 int niter = s_nPoints/GMX_SIMD_REAL_WIDTH;
128 int npoints = niter*GMX_SIMD_REAL_WIDTH;
131 double r; std::int64_t i;
135 float r; std::int32_t i;
140 dx = (range_.second-range_.first)/npoints;
142 for (iter = 0; iter < niter; iter++)
144 for (i = 0; i < GMX_SIMD_REAL_WIDTH; i++)
146 vx[i] = range_.first+dx*(iter*GMX_SIMD_REAL_WIDTH+i);
147 vref[i] = refFunc(vx[i]);
149 vtst = simdReal2Vector(simdFunc(vector2SimdReal(vx)));
151 for (i = 0, signOk = true, absOk = true; i < GMX_SIMD_REAL_WIDTH; i++)
155 // Clamp denormal values to zero if requested
156 if (std::abs(vref[i]) <= GMX_REAL_MIN)
160 if (std::abs(vtst[i]) <= GMX_REAL_MIN)
166 absDiff = fabs(vref[i]-vtst[i]);
167 absOk = absOk && ( absDiff < absTol_ );
168 signOk = signOk && ( (vref[i] >= 0 && vtst[i] >= 0) ||
169 (vref[i] <= 0 && vtst[i] <= 0));
171 if (absDiff >= absTol_)
173 /* We replicate the trivial ulp differences comparison here rather than
174 * calling the lower-level routine for comparing them, since this enables
175 * us to run through the entire test range and report the largest deviation
176 * without lots of extra glue routines.
180 ulpDiff = llabs(conv0.i-conv1.i);
181 if (ulpDiff > maxUlpDiff)
183 maxUlpDiff = ulpDiff;
184 maxUlpDiffPos = vx[i];
185 refValMaxUlpDiff = vref[i];
186 simdValMaxUlpDiff = vtst[i];
190 if ( (absOk == false) && (signOk == false) )
192 return ::testing::AssertionFailure()
193 << "Failing SIMD math function comparison due to sign differences." << std::endl
194 << "Reference function: " << refFuncExpr << std::endl
195 << "Simd function: " << simdFuncExpr << std::endl
196 << "Test range is ( " << range_.first << " , " << range_.second << " ) " << std::endl
197 << "Denormals can be 0: " << denormalsToZeroExpr << std::endl
198 << "First sign difference around x=" << std::setprecision(20) << ::testing::PrintToString(vx) << std::endl
199 << "Ref values: " << std::setprecision(20) << ::testing::PrintToString(vref) << std::endl
200 << "SIMD values: " << std::setprecision(20) << ::testing::PrintToString(vtst) << std::endl;
204 if (maxUlpDiff <= ulpTol_)
206 return ::testing::AssertionSuccess();
210 return ::testing::AssertionFailure()
211 << "Failing SIMD math function ulp comparison between " << refFuncExpr << " and " << simdFuncExpr << std::endl
212 << "Requested ulp tolerance: " << ulpTol_ << std::endl
213 << "Requested abs tolerance: " << absTol_ << std::endl
214 << "Denormals can be 0: " << denormalsToZeroExpr << std::endl
215 << "Largest Ulp difference occurs for x=" << std::setprecision(20) << maxUlpDiffPos << std::endl
216 << "Ref values: " << std::setprecision(20) << refValMaxUlpDiff << std::endl
217 << "SIMD values: " << std::setprecision(20) << simdValMaxUlpDiff << std::endl
218 << "Ulp diff.: " << std::setprecision(20) << maxUlpDiff << std::endl;
226 // Actual math function tests below
232 /*! \cond internal */
233 /*! \addtogroup module_simd */
236 TEST_F(SimdMathTest, copysign)
238 GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R(-c0, c1, c2), copysign(setSimdRealFrom3R( c0, c1, c2), setSimdRealFrom3R(-c3, c4, 0)));
239 GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R(-c0, c1, c2), copysign(setSimdRealFrom3R(-c0, -c1, -c2), setSimdRealFrom3R(-c3, c4, 0)));
240 GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R( c0, -c1, c2), copysign(setSimdRealFrom3R( c0, c1, c2), setSimdRealFrom3R( c3, -c4, 0)));
241 GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R( c0, -c1, c2), copysign(setSimdRealFrom3R(-c0, -c1, -c2), setSimdRealFrom3R( c3, -c4, 0)));
244 /*! \brief Function wrapper to evaluate reference 1/sqrt(x) */
248 return 1.0/std::sqrt(x);
251 TEST_F(SimdMathTest, invsqrt)
253 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
254 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrt);
257 TEST_F(SimdMathTest, maskzInvsqrt)
259 SimdReal x = setSimdRealFrom3R(c1, 0.0, c2);
260 SimdBool m = (setZero() < x);
261 SimdReal ref = setSimdRealFrom3R(1.0/std::sqrt(c1), 0.0, 1.0/std::sqrt(c2));
262 GMX_EXPECT_SIMD_REAL_NEAR(ref, maskzInvsqrt(x, m));
265 /*! \brief Function wrapper to return first result when testing \ref invsqrtPair */
266 SimdReal gmx_simdcall
267 tstInvsqrtPair0(SimdReal x)
270 invsqrtPair(x, x, &r0, &r1);
274 /*! \brief Function wrapper to return second result when testing \ref invsqrtPair */
275 SimdReal gmx_simdcall
276 tstInvsqrtPair1(SimdReal x)
279 invsqrtPair(x, x, &r0, &r1);
283 TEST_F(SimdMathTest, invsqrtPair)
285 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
286 // The accuracy conversions lose a bit of extra accuracy compared to
287 // doing the iterations in all-double.
288 setUlpTol(4*ulpTol_);
290 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair0);
291 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair1);
294 /*! \brief Function wrapper to evaluate reference sqrt(x) */
301 /*! \brief Dummy function returning 0.0 to test function ranges that should be zero */
302 gmx_unused static real
303 refZero(real gmx_unused x)
309 TEST_F(SimdMathTest, sqrt)
311 // The accuracy conversions lose a bit of extra accuracy compared to
312 // doing the iterations in all-double.
313 setUlpTol(4*ulpTol_);
315 // First test that 0.0 and a few other values works
316 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, std::sqrt(c1), std::sqrt(c2)), sqrt(setSimdRealFrom3R(0, c1, c2)));
318 // Values smaller-than-or-equal to GMX_FLOAT_MIN will be clamped to 0.0,
319 // so only test larger values
320 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
321 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt);
324 // Make sure that values smaller than GMX_FLOAT_MIN lead to result 0.0
325 setRange(0.0, 0.99*GMX_FLOAT_MIN);
326 GMX_EXPECT_SIMD_FUNC_NEAR(refZero, sqrt);
330 TEST_F(SimdMathTest, sqrtUnsafe)
332 // The accuracy conversions lose a bit of extra accuracy compared to
333 // doing the iterations in all-double.
334 setUlpTol(4*ulpTol_);
336 setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
337 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt<MathOptimization::Unsafe>);
340 /*! \brief Function wrapper to evaluate reference 1/x */
346 TEST_F(SimdMathTest, inv)
349 setRange(-1e10, -1e-10);
350 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, inv);
351 setRange(1e-10, 1e10);
352 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, inv);
355 TEST_F(SimdMathTest, maskzInv)
357 SimdReal x = setSimdRealFrom3R(c1, 0.0, c2);
358 SimdBool m = (setZero() < x);
359 SimdReal ref = setSimdRealFrom3R(1.0/c1, 0.0, 1.0/c2);
360 GMX_EXPECT_SIMD_REAL_NEAR(ref, maskzInv(x, m));
363 TEST_F(SimdMathTest, log)
365 setRange(1e-30, 1e30);
366 GMX_EXPECT_SIMD_FUNC_NEAR(std::log, log);
369 TEST_F(SimdMathTest, exp2)
371 // Test normal/denormal/zero range separately to make errors clearer
373 // First test the range where we get normalized (non-denormal) results,
374 // since we don't require denormal results to be reproduced correctly.
376 setRange(-1022, 1023);
380 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
382 // Some implementations might have denormal support, in which case they
383 // support an extended range, adding roughly the number of bits in the
384 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
385 // In this range we allow the value to be either correct (denormal) or 0.0
387 setRange(-1075, -1022);
389 setRange(-150, -126);
391 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2);
393 // For arguments smaller than the subnormal the result should be zero
394 // both in the reference and our implementations.
396 setRange(-1000000.0, -1075.0);
398 setRange(-100000.0, -150.0);
400 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
402 // Test a few very negative values, including values so small that they
403 // will start to cause inf values in the polynomial interpolations
404 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp2(-GMX_FLOAT_MAX), std::exp2(-0.1*GMX_REAL_MAX), std::exp2(-GMX_REAL_MAX)),
405 exp2(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
408 TEST_F(SimdMathTest, exp2Unsafe)
410 // The unsafe version is only defined in this range
412 setRange(-1022, 1023);
416 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2<MathOptimization::Unsafe>);
419 TEST_F(SimdMathTest, exp)
421 // Test normal/denormal/zero range separately to make errors clearer
423 // First test the range where we get normalized (non-denormal) results,
424 // since we don't require denormal results to be reproduced correctly.
426 setRange(-708.3, 709.1);
428 setRange(-87.3, 88.0);
430 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
432 // Some implementations might have denormal support, in which case they
433 // support an extended range, adding roughly the number of bits in the
434 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
435 // Then multiply with ln(2) to get our limit for exp().
436 // In this range we allow the value to be either correct (denormal) or 0.0
438 setRange(-746.0, -708.3);
440 setRange(-104.0, -87.3);
442 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, exp);
444 // For arguments smaller than the subnormal the result should be zero
445 // both in the reference and our implementations.
447 setRange(-1000000.0, -746.0);
449 setRange(-100000.0, -104.0);
451 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
453 // Test a few very negative values, including values so small that they
454 // will start to cause inf values in the polynomial interpolations
455 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
456 exp(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
459 TEST_F(SimdMathTest, expUnsafe)
462 setRange(-708.3, 709.1);
464 setRange(-87.3, 88.0);
466 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp<MathOptimization::Unsafe>);
469 /*! \brief Function wrapper for erf(x), with argument/return in default Gromacs precision.
471 * \note Single-precision erf() in some libraries can be slightly lower precision
472 * than the SIMD flavor, so we use a cast to force double precision for reference.
477 return std::erf(static_cast<double>(x));
480 TEST_F(SimdMathTest, erf)
483 setAbsTol(GMX_REAL_MIN);
484 GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erf);
487 /*! \brief Function wrapper for erfc(x), with argument/return in default Gromacs precision.
489 * \note Single-precision erfc() in some libraries can be slightly lower precision
490 * than the SIMD flavor, so we use a cast to force double precision for reference.
495 return std::erfc(static_cast<double>(x));
498 TEST_F(SimdMathTest, erfc)
501 setAbsTol(GMX_REAL_MIN);
502 // Our erfc algorithm has 4 ulp accuracy, so relax defaultTol a bit
503 setUlpTol(4*ulpTol_);
504 GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfc);
507 TEST_F(SimdMathTest, sin)
509 setRange(-8*M_PI, 8*M_PI);
510 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
511 // Range reduction leads to accuracy loss, so we might want higher tolerance here
512 setRange(-10000, 10000);
513 setUlpTol(2*ulpTol_);
514 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
517 TEST_F(SimdMathTest, cos)
519 setRange(-8*M_PI, 8*M_PI);
520 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
521 // Range reduction leads to accuracy loss, so we might want higher tolerance here
522 setRange(-10000, 10000);
523 setUlpTol(2*ulpTol_);
524 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
527 TEST_F(SimdMathTest, tan)
529 // Tan(x) is a little sensitive due to the division in the algorithm.
530 // Rather than using lots of extra FP operations, we accept the algorithm
531 // presently only achieves a ~3 ulp error and use the medium tolerance.
532 setRange(-8*M_PI, 8*M_PI);
533 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tan);
534 // Range reduction leads to accuracy loss, so we might want higher tolerance here
535 setRange(-10000, 10000);
536 setUlpTol(2*ulpTol_);
537 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tan);
540 TEST_F(SimdMathTest, asin)
542 // Our present asin(x) algorithm achieves 2-3 ulp accuracy
544 GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asin);
547 TEST_F(SimdMathTest, acos)
549 // Our present acos(x) algorithm achieves 2-3 ulp accuracy
551 GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acos);
554 TEST_F(SimdMathTest, atan)
556 // Our present atan(x) algorithm achieves 1 ulp accuracy
557 setRange(-10000, 10000);
558 GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atan);
561 TEST_F(SimdMathTest, atan2)
563 // test each quadrant
564 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
565 atan2(rSimd_c0c1c2, rSimd_c3c4c5));
566 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
567 atan2(rSimd_m0m1m2, rSimd_c3c4c5));
568 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
569 atan2(rSimd_m0m1m2, rSimd_m3m0m4));
570 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
571 atan2(rSimd_c0c1c2, rSimd_m3m0m4));
573 // cases important for calculating angles
574 // values on coordinate axes
575 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
576 atan2(setZero(), rSimd_c0c1c2));
577 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
578 atan2(rSimd_c0c1c2, setZero()));
579 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
580 atan2(setZero(), rSimd_m0m1m2));
581 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
582 atan2(rSimd_m0m1m2, setZero()));
583 // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
584 // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
585 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
588 /*! \brief Evaluate reference version of PME force correction. */
590 refPmeForceCorrection(real x)
594 real y = std::sqrt(x);
595 return 2*std::exp(-x)/(std::sqrt(M_PI)*x) - std::erf(static_cast<double>(y))/(x*y);
599 return -4/(3*std::sqrt(M_PI));
603 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
604 TEST_F(SimdMathTest, pmeForceCorrection)
606 // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
608 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
610 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
614 setAbsTol(GMX_REAL_EPS);
615 GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrection);
618 /*! \brief Evaluate reference version of PME potential correction. */
620 refPmePotentialCorrection(real x)
622 real y = std::sqrt(x);
623 return std::erf(static_cast<double>(y))/y;
626 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
627 TEST_F(SimdMathTest, pmePotentialCorrection)
629 // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
631 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
633 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
636 setAbsTol(GMX_REAL_EPS);
637 GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrection);
640 // Functions that only target single accuracy, even for double SIMD data
642 TEST_F(SimdMathTest, invsqrtSingleAccuracy)
644 /* Increase the allowed error by the difference between the actual precision and single */
645 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
647 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
648 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrtSingleAccuracy);
651 /*! \brief Function wrapper to return first result when testing \ref invsqrtPairSingleAccuracy */
652 SimdReal gmx_simdcall
653 tst_invsqrt_SingleAccuracy_pair0(SimdReal x)
656 invsqrtPairSingleAccuracy(x, x, &r0, &r1);
660 /*! \brief Function wrapper to return second result when testing \ref invsqrtPairSingleAccuracy */
661 SimdReal gmx_simdcall
662 tst_invsqrt_SingleAccuracy_pair1(SimdReal x)
665 invsqrtPairSingleAccuracy(x, x, &r0, &r1);
669 TEST_F(SimdMathTest, invsqrtPairSingleAccuracy)
671 /* Increase the allowed error by the difference between the actual precision and single */
672 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
674 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
675 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair0);
676 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair1);
679 TEST_F(SimdMathTest, sqrtSingleAccuracy)
681 /* Increase the allowed error by the difference between the actual precision and single */
682 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
684 // First test that 0.0 and a few other values works
685 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, std::sqrt(c0), std::sqrt(c1)), sqrtSingleAccuracy(setSimdRealFrom3R(0, c0, c1)));
687 // Values smaller-than-or-equal to GMX_FLOAT_MIN will be clamped to 0.0,
688 // so only test larger values
689 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
690 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy);
693 // Make sure that values smaller than GMX_FLOAT_MIN lead to result 0.0
694 setRange(0.0, 0.99*GMX_FLOAT_MIN);
695 GMX_EXPECT_SIMD_FUNC_NEAR(refZero, sqrtSingleAccuracy);
699 TEST_F(SimdMathTest, sqrtSingleAccuracyUnsafe)
701 /* Increase the allowed error by the difference between the actual precision and single */
702 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
704 // Test the full range
705 setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
706 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy<MathOptimization::Unsafe>);
709 TEST_F(SimdMathTest, invSingleAccuracy)
711 /* Increase the allowed error by the difference between the actual precision and single */
712 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
715 setRange(-1e10, -1e-10);
716 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
717 setRange(1e-10, 1e10);
718 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
721 TEST_F(SimdMathTest, logSingleAccuracy)
723 /* Increase the allowed error by the difference between the actual precision and single */
724 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
726 setRange(1e-30, 1e30);
727 GMX_EXPECT_SIMD_FUNC_NEAR(std::log, logSingleAccuracy);
730 TEST_F(SimdMathTest, exp2SingleAccuracy)
732 /* Increase the allowed error by the difference between the actual precision and single */
733 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
736 setRange(-1022.49, 1023.49);
738 setRange(-126, 127.49);
740 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy);
742 // Test a range that should be zero both for reference and simd versions.
743 // Some implementations might have subnormal support, in which case they
744 // support an extended range, adding roughly the number of bits in the
745 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
747 setRange(-1000000.0, -1075.0);
749 setRange(-100000.0, -150.0);
751 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2SingleAccuracy);
753 // Test a few very negative values
754 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp2(-GMX_FLOAT_MAX), std::exp2(-0.1*GMX_REAL_MAX), std::exp2(-GMX_REAL_MAX)),
755 exp2SingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
758 TEST_F(SimdMathTest, exp2SingleAccuracyUnsafe)
760 /* Increase the allowed error by the difference between the actual precision and single */
761 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
764 setRange(-1022.49, 1023.49);
766 setRange(-126, 127.49);
768 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy<MathOptimization::Unsafe>);
771 TEST_F(SimdMathTest, expSingleAccuracy)
773 /* Increase the allowed error by the difference between the actual precision and single */
774 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
777 setRange(-708.7, 709.4);
779 setRange(-87.3, 88.3);
781 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy);
783 // Test a range that should be zero both for reference and simd versions.
784 // Some implementations might have subnormal support, in which case they
785 // support an extended range, adding roughly the number of bits in the
786 // mantissa to the smallest result exponent (1023+52 in double, 127+23 single).
787 // Then multiply with ln(2) to get our limit for exp().
789 setRange(-1000000.0, -746.0);
791 setRange(-100000.0, -104.0);
793 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, expSingleAccuracy);
795 // Test a few very negative values, including values so small that they
796 // will start to cause inf values in the polynomial interpolations
797 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
798 expSingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
801 TEST_F(SimdMathTest, expSingleAccuracyUnsafe)
803 /* Increase the allowed error by the difference between the actual precision and single */
804 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
807 setRange(-708.7, 709.4);
809 setRange(-87.3, 88.3);
811 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy<MathOptimization::Unsafe>);
814 TEST_F(SimdMathTest, erfSingleAccuracy)
816 /* Increase the allowed error by the difference between the actual precision and single */
817 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
820 setAbsTol(GMX_REAL_MIN);
821 GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erfSingleAccuracy);
824 TEST_F(SimdMathTest, erfcSingleAccuracy)
826 /* Increase the allowed error by the difference between the actual precision and single */
827 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
830 setAbsTol(GMX_REAL_MIN);
831 // Our erfc algorithm has 4 ulp accuracy, so relax tolerance a bit
832 setUlpTol(4*ulpTol_);
833 GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfcSingleAccuracy);
837 TEST_F(SimdMathTest, sinSingleAccuracy)
839 /* Increase the allowed error by the difference between the actual precision and single */
840 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
842 setRange(-8*M_PI, 8*M_PI);
843 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
844 // Range reduction leads to accuracy loss, so we might want higher tolerance here
845 setRange(-10000, 10000);
846 setUlpTol(2*ulpTol_);
847 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
850 TEST_F(SimdMathTest, cosSingleAccuracy)
852 /* Increase the allowed error by the difference between the actual precision and single */
853 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
855 setRange(-8*M_PI, 8*M_PI);
856 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
857 // Range reduction leads to accuracy loss, so we might want higher tolerance here
858 setRange(-10000, 10000);
859 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
862 TEST_F(SimdMathTest, tanSingleAccuracy)
864 /* Increase the allowed error by the difference between the actual precision and single */
865 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
867 // Tan(x) is a little sensitive due to the division in the algorithm.
868 // Rather than using lots of extra FP operations, we accept the algorithm
869 // presently only achieves a ~3 ulp error and use the medium tolerance.
870 setRange(-8*M_PI, 8*M_PI);
871 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
872 // Range reduction leads to accuracy loss, so we might want higher tolerance here
873 setRange(-10000, 10000);
874 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
877 TEST_F(SimdMathTest, asinSingleAccuracy)
879 /* Increase the allowed error by the difference between the actual precision and single */
880 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
882 // Our present asin(x) algorithm achieves 2-3 ulp accuracy
884 GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asinSingleAccuracy);
887 TEST_F(SimdMathTest, acosSingleAccuracy)
889 /* Increase the allowed error by the difference between the actual precision and single */
890 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
892 // Our present acos(x) algorithm achieves 2-3 ulp accuracy
894 GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acosSingleAccuracy);
897 TEST_F(SimdMathTest, atanSingleAccuracy)
899 /* Increase the allowed error by the difference between the actual precision and single */
900 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
902 // Our present atan(x) algorithm achieves 1 ulp accuracy
903 setRange(-10000, 10000);
904 GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atanSingleAccuracy);
907 TEST_F(SimdMathTest, atan2SingleAccuracy)
909 /* Increase the allowed error by the difference between the actual precision and single */
910 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
912 // test each quadrant
913 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
914 atan2SingleAccuracy(rSimd_c0c1c2, rSimd_c3c4c5));
915 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
916 atan2SingleAccuracy(rSimd_m0m1m2, rSimd_c3c4c5));
917 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
918 atan2SingleAccuracy(rSimd_m0m1m2, rSimd_m3m0m4));
919 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
920 atan2SingleAccuracy(rSimd_c0c1c2, rSimd_m3m0m4));
921 // cases important for calculating angles
922 // values on coordinate axes
923 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
924 atan2SingleAccuracy(setZero(), rSimd_c0c1c2));
925 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
926 atan2SingleAccuracy(rSimd_c0c1c2, setZero()));
927 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
928 atan2SingleAccuracy(setZero(), rSimd_m0m1m2));
929 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
930 atan2SingleAccuracy(rSimd_m0m1m2, setZero()));
932 // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
933 // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
934 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2SingleAccuracy(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
937 TEST_F(SimdMathTest, pmeForceCorrectionSingleAccuracy)
939 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
940 // Pme correction only needs to be ~1e-6 accuracy single.
941 // Then increase the allowed error by the difference between the actual precision and single.
942 setUlpTol( (std::int64_t(5e-6/GMX_FLOAT_EPS)) * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
945 setAbsTol(GMX_FLOAT_EPS);
946 GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrectionSingleAccuracy);
949 TEST_F(SimdMathTest, pmePotentialCorrectionSingleAccuracy)
951 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
952 // Pme correction only needs to be ~1e-6 accuracy single.
953 // Then increase the allowed error by the difference between the actual precision and single.
954 setUlpTol( (std::int64_t(5e-6/GMX_FLOAT_EPS)) * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
957 setAbsTol(GMX_FLOAT_EPS);
958 GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrectionSingleAccuracy);
963 #endif // GMX_SIMD_HAVE_REAL