bionic/tests/math_data_test.h
Elliott Hughes 1b37ba2178 Improve math tests to allow a specific ulp bound.
At the moment our libm is only good enough for a 1 ulp bound on these tests,
but that's better than the 4 ulp bound you get from gtest by default.

I'm not really happy with the multiple structures and corresponding functions,
but at least they mean there's no duplication in the tests themselves, and it
should be easy enough for us to make further improvements in future.

Change-Id: I004e12970332e1d9531721361d6c34f908cfcecc
2014-11-04 18:00:02 -08:00

149 lines
4.6 KiB
C++

/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include <fenv.h>
template <typename RT, typename T1>
struct data_1_1_t {
RT expected;
T1 input;
};
template <typename RT, typename T1, typename T2>
struct data_1_2_t {
RT expected;
T1 input1;
T2 input2;
};
template <typename RT1, typename RT2, typename T>
struct data_2_1_t {
RT1 expected1;
RT2 expected2;
T input;
};
template <typename T> union fp_u;
template <> union fp_u<float> {
float value;
struct {
unsigned frac:23;
unsigned exp:8;
unsigned sign:1;
} bits;
uint32_t sign_magnitude;
};
template <> union fp_u<double> {
double value;
struct {
unsigned fracl;
unsigned frach:20;
unsigned exp:11;
unsigned sign:1;
} bits;
uint64_t sign_magnitude;
};
// TODO: long double.
template <typename T>
static inline auto SignAndMagnitudeToBiased(const T& value) -> decltype(fp_u<T>::sign_magnitude) {
fp_u<T> u;
u.value = value;
if (u.bits.sign) {
return ~u.sign_magnitude + 1;
} else {
u.bits.sign = 1;
return u.sign_magnitude;
}
}
// Based on the existing googletest implementation, which uses a fixed 4 ulp bound.
template <typename T>
size_t UlpDistance(T lhs, T rhs) {
const auto biased1 = SignAndMagnitudeToBiased(lhs);
const auto biased2 = SignAndMagnitudeToBiased(rhs);
return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
}
template <size_t ULP, typename T>
struct FpUlpEq {
::testing::AssertionResult operator()(const char* /* expected_expression */,
const char* /* actual_expression */,
T expected,
T actual) {
if (!isnan(expected) && !isnan(actual) && UlpDistance(expected, actual) <= ULP) {
return ::testing::AssertionSuccess();
}
// Output the actual and expected values as hex floating point.
char expected_str[64];
char actual_str[64];
snprintf(expected_str, sizeof(expected_str), "%a", expected);
snprintf(actual_str, sizeof(actual_str), "%a", actual);
return ::testing::AssertionFailure()
<< "expected (" << expected_str << ") != actual (" << actual_str << ")";
}
};
// Runs through the array 'data' applying 'f' to each of the input values
// and asserting that the result is within ULP ulps of the expected value.
// For testing a (double) -> double function like sin(3).
template <size_t ULP, typename RT, typename T, size_t N>
void DoMathDataTest(data_1_1_t<RT, T> (&data)[N], RT f(T)) {
fesetenv(FE_DFL_ENV);
FpUlpEq<ULP, RT> predicate;
for (size_t i = 0; i < N; ++i) {
EXPECT_PRED_FORMAT2(predicate,
data[i].expected, f(data[i].input)) << "Failed on element " << i;
}
}
// Runs through the array 'data' applying 'f' to each of the pairs of input values
// and asserting that the result is within ULP ulps of the expected value.
// For testing a (double, double) -> double function like pow(3).
template <size_t ULP, typename RT, typename T1, typename T2, size_t N>
void DoMathDataTest(data_1_2_t<RT, T1, T2> (&data)[N], RT f(T1, T2)) {
fesetenv(FE_DFL_ENV);
FpUlpEq<ULP, RT> predicate;
for (size_t i = 0; i < N; ++i) {
EXPECT_PRED_FORMAT2(predicate,
data[i].expected, f(data[i].input1, data[i].input2)) << "Failed on element " << i;
}
}
// Runs through the array 'data' applying 'f' to each of the input values
// and asserting that the results are within ULP ulps of the expected values.
// For testing a (double, double*, double*) -> void function like sincos(3).
template <size_t ULP, typename RT1, typename RT2, typename T1, size_t N>
void DoMathDataTest(data_2_1_t<RT1, RT2, T1> (&data)[N], void f(T1, RT1*, RT2*)) {
fesetenv(FE_DFL_ENV);
FpUlpEq<ULP, RT1> predicate1;
FpUlpEq<ULP, RT2> predicate2;
for (size_t i = 0; i < N; ++i) {
RT1 out1;
RT2 out2;
f(data[i].input, &out1, &out2);
EXPECT_PRED_FORMAT2(predicate1, data[i].expected1, out1) << "Failed on element " << i;
EXPECT_PRED_FORMAT2(predicate2, data[i].expected2, out2) << "Failed on element " << i;
}
}