Merge branch 'jjj11x/parallel_random_test'

add lots of random tests for decn library
This commit is contained in:
Jeff Wang 2021-01-27 21:40:49 -05:00
commit 1e6d786482
14 changed files with 1093 additions and 391 deletions

View File

@ -25,5 +25,7 @@ jobs:
${{ github.workspace }}/build/ ${{ github.workspace }}/build/
${{ github.workspace }}/build_qt/lcov/ ${{ github.workspace }}/build_qt/lcov/
${{ github.workspace }}/build_qt/decn.c.gcov ${{ github.workspace }}/build_qt/decn.c.gcov
${{ github.workspace }}/build_qt/Testing/
${{ github.workspace }}/main.hex ${{ github.workspace }}/main.hex
if-no-files-found: error if-no-files-found: error

View File

@ -4,20 +4,23 @@ project(stc_rpncalc C CXX)
find_package(Qt5 COMPONENTS Widgets Qml Quick REQUIRED) find_package(Qt5 COMPONENTS Widgets Qml Quick REQUIRED)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
message(STATUS "using address sanitizer") message(STATUS "using address sanitizer")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls")
link_libraries(asan) link_libraries(asan)
endif() endif()
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type (for tests debug make sense)") set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type (for tests debug make sense)")
# Compiler warnings # Compiler warnings
if(MSVC) if(MSVC)
add_compile_options(/W4 /WX) add_compile_options(/W4 /WX)
else() else()
add_compile_options(-Wall -Wextra -pedantic) add_compile_options(-Wall -Wextra -pedantic)
endif() endif()
# CTest Catch2 tests
enable_testing()
# Directory with source code # Directory with source code
add_subdirectory(src) add_subdirectory(src)
add_subdirectory(qt_gui) add_subdirectory(qt_gui)

View File

@ -2,7 +2,6 @@ FROM ubuntu:18.04
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
build-essential \ build-essential \
catch \
clang \ clang \
cmake \ cmake \
git \ git \
@ -13,4 +12,10 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
ninja-build \ ninja-build \
qtdeclarative5-dev \ qtdeclarative5-dev \
sdcc=3.5.0+dfsg-2build1 \ sdcc=3.5.0+dfsg-2build1 \
vim-tiny vim-tiny \
wget
# install more up-to-date catch2
RUN wget http://mirrors.kernel.org/ubuntu/pool/universe/c/catch2/catch2_2.13.0-1_all.deb
RUN echo "1d501c7f817cfcd46dd1b79edc10896d catch2_2.13.0-1_all.deb" | md5sum --check --
RUN dpkg -i catch2_2.13.0-1_all.deb

View File

@ -91,7 +91,7 @@ The keys on the *original* calculator map as follows:
- acts as acos(x) when shifted down - acts as acos(x) when shifted down
- `3 `: acts as tan(x) when shifted - `3 `: acts as tan(x) when shifted
- acts as atan(x) when shifted down - acts as atan(x) when shifted down
- all trig functions are currently calculated in degrees - all trig functions are currently calculated in radians (TODO: change to degrees by default)
- `- `: acts as to radians when shifted - `- `: acts as to radians when shifted
- acts as to degrees when shifted down - acts as to degrees when shifted down
- `+ `: acts as LastX when shifted - `+ `: acts as LastX when shifted
@ -119,7 +119,7 @@ Github releases has prebuilt binaries for the calculator. Building is fairly str
- See https://sourceforge.net/p/sdcc/discussion/1865/thread/9589cc8d57/ - See https://sourceforge.net/p/sdcc/discussion/1865/thread/9589cc8d57/
- Luckily SDCC has few dependencies, and older versions can be installed fairly easily. - Luckily SDCC has few dependencies, and older versions can be installed fairly easily.
- CMakeLists.txt is for building the Qt desktop application, and also the decimal-number-library test application. - CMakeLists.txt is for building the Qt desktop application, and also the decimal-number-library test application.
- build similarly to other cmake projects: - build similarly to other cmake projects, see [Dockerfile](Dockerfile) for build dependencies:
- `mkdir build_qt && cd build_qt` - `mkdir build_qt && cd build_qt`
- `cmake -DCMAKE_BUILD_TYPE=Debug -G "Eclipse CDT4 - Ninja" ..` - `cmake -DCMAKE_BUILD_TYPE=Debug -G "Eclipse CDT4 - Ninja" ..`
- (you can choose a different generator, I prefer using Ninja to build, because it's fast) - (you can choose a different generator, I prefer using Ninja to build, because it's fast)
@ -261,7 +261,7 @@ The original firmware that came with this calculator used a fixed-point format,
This replacement calculator firmware uses decimal floating point, using base-100 to store numbers and do calculations. Base-100 allows for efficient storage into 8-bit bytes, and is easier to work with than packed-BCD. Unlike straight binary representations, base-100 is still fairly easy to display as decimal. Also unlike binary representations, there is no conversion error from binary/decimal (e.g. numbers like `0.1` can be represented exactly). This replacement calculator firmware uses decimal floating point, using base-100 to store numbers and do calculations. Base-100 allows for efficient storage into 8-bit bytes, and is easier to work with than packed-BCD. Unlike straight binary representations, base-100 is still fairly easy to display as decimal. Also unlike binary representations, there is no conversion error from binary/decimal (e.g. numbers like `0.1` can be represented exactly).
Each `uint8_t` stores a base-100 "`digit100`", referred to as an "`lsu`", for least significant unit. (The LSU terminology is borrowed from the decNumber library: I originally considered using the decNumber library similar to the WP-34S calculator, but just the library itself takes several times more flash than is available on this calculator. I also considered using the BigNu mber arduino library, but that library uses C++ and lots of pointers passed to functions, which are extremely expensive on the 8051 architecture.) The number format is as follows: Each `uint8_t` stores a base-100 "`digit100`", referred to as an "`lsu`", for least significant unit. (The LSU terminology is borrowed from the decNumber library: I originally considered using the decNumber library similar to the WP-34S calculator, but just the library itself takes several times more flash than is available on this calculator. I also considered using the BigNumber arduino library, but that library uses C++ and lots of pointers passed to functions, which are extremely expensive on the 8051 architecture.) The number format is as follows:
- `lsu[0]`: contains the most significant `digit100` (the most significant 2 decimal digits) - `lsu[0]`: contains the most significant `digit100` (the most significant 2 decimal digits)
- implicit decimal point between `lsu[0]/10` and `lsu[0]%10` - implicit decimal point between `lsu[0]/10` and `lsu[0]%10`
@ -322,7 +322,7 @@ The number `0.135` would be stored the same way, except now the exponent is `0x7
The keyboard matrix is scanned once every 5ms. The keyboard debouncing is based on the quick draw/integrator hybrid algorithm described [here](https://summivox.wordpress.com/2016/06/03/keyboard-matrix-scanning-and-debouncing/). This algorithm combines the advantages of both methods: The keyboard matrix is scanned once every 5ms. The keyboard debouncing is based on the quick draw/integrator hybrid algorithm described [here](https://summivox.wordpress.com/2016/06/03/keyboard-matrix-scanning-and-debouncing/). This algorithm combines the advantages of both methods:
1. It signals a key press immediately, the very first instant a keyboard matrix scan detects a key is pressed (similar to the "quick-draw" method). 1. It signals a key press immediately, the very first instant a keyboard matrix scan detects a key is pressed (similar to the "quick-draw" method).
1. It has an "integrator" to determine both when a key is fully pressed and when a key is fully released. This prevents the mechanically bouncy keys from registering multiple times when pressed. 1. It has an "integrator" (a saturating up/down counter) to determine both when a key is fully pressed and when a key is fully released. This prevents the mechanically bouncy keys from registering multiple times when pressed.
In practice, the keyboard debouncing works much better than the original firmware (which would occasionally miss keystrokes). In practice, the keyboard debouncing works much better than the original firmware (which would occasionally miss keystrokes).

View File

@ -1,11 +1,3 @@
add_library(Catch INTERFACE)
if(EXISTS /usr/include/catch/catch.hpp)
target_include_directories(Catch INTERFACE /usr/include/catch)
elseif(EXISTS /usr/include/catch2/catch.hpp)
target_include_directories(Catch INTERFACE /usr/include/catch2)
else()
endif()
#code coverage #code coverage
add_library(coverage_config INTERFACE) add_library(coverage_config INTERFACE)
target_compile_options(coverage_config INTERFACE -O0 -g --coverage) target_compile_options(coverage_config INTERFACE -O0 -g --coverage)
@ -18,11 +10,37 @@ add_library(decn decn.c ../utils.c)
add_library(decn_cover decn.c) add_library(decn_cover decn.c)
target_link_libraries(decn_cover PUBLIC coverage_config) target_link_libraries(decn_cover PUBLIC coverage_config)
add_executable(decn_test decn_test.c ../utils.c) # old tests (compare output with reference "golden" output file)
target_link_libraries(decn_test decn_cover coverage_config Catch) add_executable(decn_test
decn_test.c
../utils.c
)
target_link_libraries(decn_test
decn_cover
coverage_config
)
add_executable(decn_tests catch_main.cpp decn_tests.cpp decn_tests_trig.cpp ../utils.c) # catch2 unit tests
target_link_libraries(decn_tests decn_cover coverage_config mpfr Catch) find_package(Catch2 REQUIRED)
enable_testing()
set (BUILD_TESTING ON)
add_executable(decn_tests
catch_main.cpp
decn_tests.cpp
decn_tests_div_sqrt.cpp
decn_tests_transcendental.cpp
decn_tests_trig.cpp
../utils.c
)
target_link_libraries(decn_tests
mpfr
decn_cover
coverage_config
Catch2::Catch2
)
include(CTest)
include(Catch)
catch_discover_tests(decn_tests)
# decn prototyping # decn prototyping
add_subdirectory(proto) add_subdirectory(proto)

View File

@ -21,4 +21,4 @@
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.hpp" #include <catch2/catch.hpp>

View File

@ -149,7 +149,7 @@ exp_t get_exponent(const dec80* const x){
#endif #endif
} }
static void set_exponent(dec80* acc, exp_t exponent, uint8_t num_is_neg){ void set_exponent(dec80* acc, exp_t exponent, uint8_t num_is_neg){
#ifdef EXP16 #ifdef EXP16
if (num_is_neg){ if (num_is_neg){
exponent |= 0x8000; exponent |= 0x8000;
@ -196,7 +196,7 @@ static void shift_left(dec80* x){
} }
} }
static void remove_leading_zeros(dec80* x){ void remove_leading_zeros(dec80* x){
uint8_t digit100; uint8_t digit100;
uint8_t is_negative = (x->exponent < 0); uint8_t is_negative = (x->exponent < 0);
exp_t exponent = get_exponent(x); exp_t exponent = get_exponent(x);

View File

@ -60,6 +60,11 @@ typedef struct {
//remove sign bit, and return 15 bit exponent sign-extended to 16 bits //remove sign bit, and return 15 bit exponent sign-extended to 16 bits
exp_t get_exponent(const dec80* const x); exp_t get_exponent(const dec80* const x);
void set_exponent(dec80* acc, exp_t exponent, uint8_t num_is_neg);
void remove_leading_zeros(dec80* x);
void copy_decn(dec80* const dest, const dec80* const src); void copy_decn(dec80* const dest, const dec80* const src);
extern dec80 AccDecn; extern dec80 AccDecn;

View File

@ -21,11 +21,14 @@
#include <string> #include <string>
#include <random>
#include <boost/multiprecision/mpfr.hpp> #include <boost/multiprecision/mpfr.hpp>
#include <catch.hpp> #include <catch2/catch.hpp>
#include "decn.h" #include "decn.h"
#include "../utils.h" #include "../utils.h"
#include "decn_tests.h"
namespace bmp = boost::multiprecision; namespace bmp = boost::multiprecision;
using Catch::Matchers::Equals; using Catch::Matchers::Equals;
@ -258,333 +261,6 @@ TEST_CASE("multiply"){
CHECK_THAT(Buf, Equals("Error")); //acc*b CHECK_THAT(Buf, Equals("Error")); //acc*b
} }
static void div_test(
//input
const char* a_str, int a_exp,
const char* b_str, int b_exp
)
{
CAPTURE(a_str); CAPTURE(a_exp);
CAPTURE(b_str); CAPTURE(b_exp);
//do division
build_dec80(a_str, a_exp);
build_decn_at(&BDecn, b_str, b_exp);
// decn_to_str_complete(&AccDecn);
// printf(" acc: %s\n", Buf);
// decn_to_str_complete(&BDecn);
// printf(" b: %s\n", Buf);
div_decn();
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // acc / b
//calculate actual result
bmp::mpfr_float::default_precision(50);
std::string a_full_str(a_str);
a_full_str += "e" + std::to_string(a_exp);
std::string b_full_str(b_str);
b_full_str += "e" + std::to_string(b_exp);;
// CAPTURE(a_full_str);
// CAPTURE(b_full_str);
bmp::mpfr_float a_actual(a_full_str);
bmp::mpfr_float b_actual(b_full_str);
a_actual /= b_actual; //calculate actual result
if (decn_is_nan(&AccDecn)){
//check that NaN result of division by 0
CAPTURE(a_actual);
CHECK(b_actual == 0);
} else {
bmp::mpfr_float calculated(Buf);
bmp::mpfr_float rel_diff = abs((a_actual - calculated) / a_actual);
CHECK(rel_diff < 1e-17);
}
}
TEST_CASE("division"){
div_test(
"1", 0,
"0", 0
);
div_test(
"3.14", 60,
"-1.5", -2
);
div_test(
"4", 0,
"4", 0
);
div_test(
"1", 0,
"3", 0
);
div_test(
"500", 0,
"99", 0
);
div_test(
"500", 0,
"2", 0
);
div_test(
"3", 0,
"25", -15
);
div_test(
"0.02", 0,
"0.03", 0
);
}
static void sqrt_test(const char* x_str, int x_exp)
{
CAPTURE(x_str); CAPTURE(x_exp);
build_dec80(x_str, x_exp);
// decn_to_str_complete(&AccDecn);
// printf(" acc: %s\n", Buf);
sqrt_decn();
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // sqrt(x)
//calculate actual result
bmp::mpfr_float::default_precision(50);
std::string x_full_str(x_str);
x_full_str += "e" + std::to_string(x_exp);
CAPTURE(x_full_str);
bmp::mpfr_float x_actual(x_full_str);
CAPTURE(x_actual);
if (decn_is_nan(&AccDecn)){
//check that NaN is from result of sqrt(-)
CHECK(x_actual <= 0);
} else if (decn_is_zero(&AccDecn)){
//check actual is also 0
CHECK(x_actual == 0);
} else {
x_actual = sqrt(x_actual);
bmp::mpfr_float calculated(Buf);
bmp::mpfr_float rel_diff = abs((x_actual - calculated) / x_actual);
CHECK(rel_diff < 3e-16); //TODO
}
}
TEST_CASE("sqrt"){
sqrt_test("0", 0);
sqrt_test("2", 0);
sqrt_test("-1", 0);
sqrt_test("0.155", 0);
sqrt_test("10", 0);
sqrt_test("1.1", 10);
sqrt_test("2.02", -10);
sqrt_test("2.02", 0);
sqrt_test("1.5", 0);
sqrt_test("9", 99);
sqrt_test("123", 12345);
}
static void log_test(
//input
const char* x_str, int x_exp,
bool base10=false
)
{
CAPTURE(x_str); CAPTURE(x_exp);
CAPTURE(base10);
build_dec80(x_str, x_exp);
// decn_to_str_complete(&AccDecn);
// printf(" acc: %s\n", Buf);
if (base10){
log10_decn();
} else {
ln_decn();
}
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // log(x)
//calculate actual result
bmp::mpfr_float::default_precision(50);
std::string x_full_str(x_str);
x_full_str += "e" + std::to_string(x_exp);
CAPTURE(x_full_str);
bmp::mpfr_float x_actual(x_full_str);
CAPTURE(x_actual);
if (decn_is_nan(&AccDecn)){
//check that NaN is from result of log(-)
CHECK(x_actual <= 0);
} else {
if (base10){
x_actual = log10(x_actual);
} else {
x_actual = log(x_actual);
}
bmp::mpfr_float calculated(Buf);
bmp::mpfr_float rel_diff = abs((x_actual - calculated) / x_actual);
CHECK(rel_diff < 3e-16); //TODO
}
}
TEST_CASE("log"){
log_test("0", 0);
log_test("-1", 0);
log_test("0.155", 0);
log_test("10", 0);
log_test("1.1", 10);
log_test("2.02", -10);
log_test("2.02", 0);
log_test("1.5", 0, true);
log_test("9", 99);
log_test("123", 12345);
}
static void exp_test(
//input
const char* x_str, int x_exp,
double epsilon=6e-16,
bool base10=false
)
{
CAPTURE(x_str); CAPTURE(x_exp);
CAPTURE(base10);
build_dec80(x_str, x_exp);
if (base10){
exp10_decn();
} else {
exp_decn();
}
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // exp(x)
CAPTURE(AccDecn.exponent);
//calculate actual result
bmp::mpfr_float::default_precision(50);
bmp::mpfr_float calculated(Buf);
std::string x_full_str(x_str);
x_full_str += "e" + std::to_string(x_exp);
bmp::mpfr_float x_actual(x_full_str);
if (base10){
x_actual *= log(10);
}
x_actual = exp(x_actual);
CAPTURE(x_actual);
bmp::mpfr_float rel_diff = abs((x_actual - calculated) / x_actual);
CHECK(rel_diff < epsilon);
}
static void exp10_test(
//input
const char* x_str, int x_exp,
double epsilon=3e-15
)
{
exp_test(x_str, x_exp, epsilon, true);
}
TEST_CASE("exp"){
exp_test("4.4", 0);
exp_test("0.155", 0);
exp_test("9.999", 0);
exp_test("10", 0);
exp_test("10.001", 0);
exp_test("2.3", 2, 6e-15);
exp_test("2.02", -10);
exp_test("2.02", 0);
exp_test("1.5", 0);
exp_test("294.69999999", 0, 8e-15);
//do not operate on NaN
set_dec80_NaN(&AccDecn);
exp_decn();
CHECK(decn_is_nan(&AccDecn)); //still NaN
}
TEST_CASE("exp10"){
exp10_test("4.4", 0);
exp10_test("0.155", 0);
exp10_test("9.999", 0);
exp10_test("10", 0);
exp10_test("10.001", 0);
exp10_test("2.02", -10);
exp10_test("2.02", 0);
exp10_test("1.5", 0);
exp10_test("127", 0, 3e-14);
}
static void pow_test(
//input
const char* a_str, int a_exp,
const char* b_str, int b_exp
)
{
CAPTURE(a_str); CAPTURE(a_exp);
CAPTURE(b_str); CAPTURE(b_exp);
//compute power
build_decn_at(&BDecn, b_str, b_exp);
build_dec80(a_str, a_exp);
pow_decn();
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // a^b
//calculate actual result
bmp::mpfr_float::default_precision(50);
bmp::mpfr_float calculated(Buf);
std::string a_full_str(a_str);
a_full_str += "e" + std::to_string(a_exp);
std::string b_full_str(b_str);
b_full_str += "e" + std::to_string(b_exp);;
// CAPTURE(a_full_str);
// CAPTURE(b_full_str);
bmp::mpfr_float a_actual(a_full_str);
bmp::mpfr_float b_actual(b_full_str);
a_actual = pow(a_actual, b_actual);
if (decn_is_zero(&AccDecn)) {
bmp::mpfr_float diff = abs(a_actual - calculated);
CHECK(diff < 3e-14);
} else {
bmp::mpfr_float rel_diff = abs((a_actual - calculated)/a_actual);
CHECK(rel_diff < 3e-14);
}
}
TEST_CASE("power"){
pow_test(
"3.14", 60,
"-1.5", -2
);
pow_test(
"3", 0,
"201", 0
);
pow_test(
"5", 0,
"0", 0
);
pow_test(
"5", 0,
"0", 2
);
pow_test(
"0", 0,
"5", 0
);
pow_test(
"0", 0,
"0", 0
);
}
TEST_CASE("u32str corner"){ TEST_CASE("u32str corner"){
u32str(0, &Buf[0], 10); u32str(0, &Buf[0], 10);
CHECK_THAT(Buf, Equals("0")); CHECK_THAT(Buf, Equals("0"));

28
src/decn/decn_tests.h Normal file
View File

@ -0,0 +1,28 @@
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/*
* decn_tests.h
*
* Created on: Oct 26, 2020
*/
#ifndef DECN_TESTS_H_
#define DECN_TESTS_H_
static const int NUM_RAND_TESTS = 123456;
#endif

View File

@ -0,0 +1,200 @@
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/*
* decn_tests_div_sqrt.cpp
*
* Unit tests using https://github.com/catchorg/Catch2
*
* separate out reciprocal/division and sqrt tests
*
* Created on: Oct 26, 2020
*/
#include <string>
#include <random>
#include <boost/multiprecision/mpfr.hpp>
#include <catch2/catch.hpp>
#include "decn.h"
#include "../utils.h"
#include "decn_tests.h"
namespace bmp = boost::multiprecision;
using Catch::Matchers::Equals;
static void div_test(){ //acc / b
bmp::mpf_float::default_precision(50);
decn_to_str_complete(&AccDecn);
CAPTURE(Buf);
bmp::mpfr_float a_actual(Buf);
decn_to_str_complete(&BDecn);
CAPTURE(Buf);
bmp::mpfr_float b_actual(Buf);
//calc result
div_decn();
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // acc / b
//calculate actual result
a_actual /= b_actual;
if (decn_is_nan(&AccDecn)){
//check that NaN result of division by 0
CAPTURE(a_actual);
CHECK(b_actual == 0);
} else {
bmp::mpfr_float calculated(Buf);
bmp::mpfr_float rel_diff = abs((a_actual - calculated) / a_actual);
CHECK(rel_diff < 2e-17);
}
}
static void div_test(
//input
const char* a_str, int a_exp,
const char* b_str, int b_exp
)
{
CAPTURE(a_str); CAPTURE(a_exp);
CAPTURE(b_str); CAPTURE(b_exp);
//do division
build_dec80(a_str, a_exp);
build_decn_at(&BDecn, b_str, b_exp);
div_test();
}
TEST_CASE("division"){
div_test(
"1", 0,
"0", 0
);
div_test(
"3.14", 60,
"-1.5", -2
);
div_test(
"4", 0,
"4", 0
);
div_test(
"1", 0,
"3", 0
);
div_test(
"500", 0,
"99", 0
);
div_test(
"500", 0,
"2", 0
);
div_test(
"3", 0,
"25", -15
);
div_test(
"0.02", 0,
"0.03", 0
);
}
TEST_CASE("division random"){
std::default_random_engine gen;
std::uniform_int_distribution<int> distrib(0, 99);
std::uniform_int_distribution<int> sign_distrib(0,1);
for (int j = 0; j < NUM_RAND_TESTS; j++){
AccDecn.lsu[0] = distrib(gen);
BDecn.lsu[0] = distrib(gen);
for (int i = 1; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
BDecn.lsu[i] = distrib(gen);
}
set_exponent(&AccDecn, distrib(gen), sign_distrib(gen));
set_exponent(&BDecn, distrib(gen), sign_distrib(gen));
div_test();
}
}
static void sqrt_test(){
decn_to_str_complete(&AccDecn);
CAPTURE(Buf);
//calculate result
sqrt_decn();
//build mpfr float
bmp::mpfr_float::default_precision(50);
bmp::mpfr_float x_actual(Buf);
//print calc result
decn_to_str_complete(&AccDecn);
CAPTURE(Buf);
//calculate actual result
CAPTURE(x_actual);
if (decn_is_nan(&AccDecn)){
//check that NaN is from result of sqrt(-)
CHECK(x_actual <= 0);
} else if (decn_is_zero(&AccDecn)){
//check actual is also 0
CHECK(x_actual == 0);
} else {
x_actual = sqrt(x_actual);
CAPTURE(x_actual);
bmp::mpfr_float calculated(Buf);
bmp::mpfr_float rel_diff = abs((x_actual - calculated) / x_actual);
CHECK(rel_diff < 2e-17);
}
}
static void sqrt_test(const char* x_str, int x_exp)
{
CAPTURE(x_str); CAPTURE(x_exp);
build_dec80(x_str, x_exp);
sqrt_test();
}
TEST_CASE("sqrt"){
sqrt_test("0", 0);
sqrt_test("2", 0);
sqrt_test("-1", 0);
sqrt_test("0.155", 0);
sqrt_test("10", 0);
sqrt_test("1.1", 10);
sqrt_test("2.02", -10);
sqrt_test("2.02", 0);
sqrt_test("1.5", 0);
sqrt_test("9", 99);
sqrt_test("123", 12345);
}
TEST_CASE("sqrt random"){
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0,99);
std::uniform_int_distribution<int> exp_distrib(-99,99);
std::uniform_int_distribution<int> sign_distrib(0,1);
for (int j = 0; j < NUM_RAND_TESTS; j++){
for (int i = 0; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distribution(generator);
}
int sign = sign_distrib(generator);
set_exponent(&AccDecn, exp_distrib(generator), sign);
sqrt_test();
}
}

View File

@ -0,0 +1,490 @@
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/*
* decn_tests_transcendental.cpp
*
* Unit tests using https://github.com/catchorg/Catch2
*
* separate out transcendental function tests
*
* Created on: Oct 26, 2020
*/
#include <string>
#include <random>
#include <boost/multiprecision/mpfr.hpp>
#include <catch2/catch.hpp>
#include "decn.h"
#include "../utils.h"
#include "decn_tests.h"
namespace bmp = boost::multiprecision;
using Catch::Matchers::Equals;
static void log_test_(bool base10, double epsilon){
bmp::mpfr_float::default_precision(50);
CAPTURE(base10);
decn_to_str_complete(&AccDecn);
CAPTURE(Buf);
//build mpfr float
bmp::mpfr_float x_actual(Buf);
//calculate result
if (base10){
log10_decn();
} else {
ln_decn();
}
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // log(x)
//calculate actual result
CAPTURE(x_actual);
if (decn_is_nan(&AccDecn)){
//check that NaN is from result of log(-)
CHECK(x_actual <= 0);
} else {
if (base10){
x_actual = log10(x_actual);
} else {
x_actual = log(x_actual);
}
bmp::mpfr_float calculated(Buf);
CAPTURE(calculated);
bmp::mpfr_float rel_diff = abs((x_actual - calculated) / x_actual);
CHECK(rel_diff < epsilon);
}
}
static void log_test(bool base10=false){
//check if near 1.0
remove_leading_zeros(&AccDecn);
double lsu0 = AccDecn.lsu[0];
int exp = get_exponent(&AccDecn);
if (exp == -1){
lsu0 /= (double) 10;
lsu0 += (double) AccDecn.lsu[1] / (10*100);
lsu0 += (double) AccDecn.lsu[2] / (10*100*100);
lsu0 += (double) AccDecn.lsu[3] / (10*100*100*100);
} else if (exp == 0){
lsu0 += (double) AccDecn.lsu[1] / 100;
lsu0 += (double) AccDecn.lsu[2] / (100*100);
lsu0 += (double) AccDecn.lsu[3] / (100*100*100);
}
CAPTURE((int) AccDecn.lsu[0]); CAPTURE((int) AccDecn.lsu[1]);
CAPTURE(exp);
CAPTURE(lsu0);
if (exp == 0 || exp == -1){
//check if near 1.0
if (lsu0 >= 7 && lsu0 < 8){
log_test_(base10, 7.5e-16);
} else if (lsu0 >= 8 && lsu0 < 9){
log_test_(base10, 1.5e-15);
} else if (lsu0 >= 9 && lsu0 < 9.6){
log_test_(base10, 1.0e-14);
} else if (lsu0 >= 9.6 && lsu0 < 9.9){
log_test_(base10, 4.1e-13);
} else if (lsu0 >= 9.9 && lsu0 < 9.999){
log_test_(base10, 1.5e-11);
} else if (lsu0 >= 9.999 && lsu0 < 9.99999){
log_test_(base10, 6.0e-10);
} else if (lsu0 >= 9.99999 && lsu0 < 9.9999999){
log_test_(base10, 3.0e-9);
} else if (lsu0 >= 9.9999999 && lsu0 < 10.0){
log_test_(base10, 1.3e-7);
} else if (lsu0 >= 10.0 && lsu0 < 10.00001){
log_test_(base10, 6.0e-10);
} else if (lsu0 >= 10.00001 && lsu0 < 10.001){
log_test_(base10, 6.0e-11);
} else if (lsu0 >= 10.001 && lsu0 < 10.1){
log_test_(base10, 1.5e-12);
} else if (lsu0 >= 10.1 && lsu0 < 11){
log_test_(base10, 1.6e-14);
} else if (lsu0 >= 11 && lsu0 < 13){
log_test_(base10, 2.0e-15);
} else {
log_test_(base10, 6.5e-16);
}
} else {
log_test_(base10, 2e-16);
}
}
static void log_test(
//input
const char* x_str, int x_exp,
bool base10=false
)
{
CAPTURE(x_str); CAPTURE(x_exp);
CAPTURE(base10);
build_dec80(x_str, x_exp);
log_test(base10);
}
TEST_CASE("log"){
log_test("0", 0);
log_test("-1", 0);
log_test("0.155", 0);
log_test("10", 0);
log_test("1.1", 10);
log_test("2.02", -10);
log_test("2.02", 0);
log_test("1.5", 0, true);
log_test("9", 99);
log_test("123", 12345);
}
TEST_CASE("log random"){
std::default_random_engine gen;
std::uniform_int_distribution<int> distrib(0,99);
std::uniform_int_distribution<int> exp_distrib(-99,99);
std::uniform_int_distribution<int> sign_distrib(0,1);
for (int j = 0; j < NUM_RAND_TESTS; j++){
for (int i = 0; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
}
int exp = exp_distrib(gen);
set_exponent(&AccDecn, exp, 0);
int base10 = sign_distrib(gen);
log_test(base10);
}
}
static void log_test_near1(int lsu0_low, int lsu0_high, int exp){
std::default_random_engine gen;
std::uniform_int_distribution<int> lsu0_distrib(lsu0_low, lsu0_high);
std::uniform_int_distribution<int> distrib(0,99);
std::uniform_int_distribution<int> exp_distrib(-99,99);
std::uniform_int_distribution<int> sign_distrib(0,1);
for (int j = 0; j < NUM_RAND_TESTS; j++){
AccDecn.lsu[0] = lsu0_distrib(gen);
for (int i = 1; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
}
set_exponent(&AccDecn, exp, 0);
int base10 = sign_distrib(gen);
log_test(base10);
}
}
TEST_CASE("log random 0 to 0.99..."){
log_test_near1(0, 99, -1);
}
TEST_CASE("log random 0.8 to 0.99..."){
log_test_near1(80, 99, -1);
}
TEST_CASE("log random 1.0 to 9.9"){
log_test_near1(10, 99, 0);
}
TEST_CASE("log random 1.0 to 2.0"){
log_test_near1(10, 20, 0);
}
static void exp_test_(bool base10, double epsilon){
bmp::mpfr_float::default_precision(50);
CAPTURE(base10);
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); //x
CAPTURE(AccDecn.exponent);
//build mpfr float
bmp::mpfr_float x_actual(Buf);
//calculate result
if (base10){
exp10_decn();
} else {
exp_decn();
}
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // exp(x)
//calculate actual result
bmp::mpfr_float calculated(Buf);
if (base10){
x_actual *= log(10);
}
x_actual = exp(x_actual);
CAPTURE(x_actual);
bmp::mpfr_float rel_diff = abs((x_actual - calculated) / x_actual);
CHECK(rel_diff < epsilon);
}
static void exp_test(bool base10=false){
double x;
int exp = get_exponent(&AccDecn);
if (exp == 1){
x = AccDecn.lsu[0];
x += (double) AccDecn.lsu[1] / 100;
} else if (exp == 2){
x = (double) AccDecn.lsu[0] * 10;
x += (double) AccDecn.lsu[1] / 10;
}
CAPTURE((int) AccDecn.lsu[0]); CAPTURE((int) AccDecn.lsu[1]);
CAPTURE(exp);
CAPTURE(x);
double epsilon;
if (exp == 1 || exp == 2){
if (x > 230){
epsilon = 8e-15;
} else if (x > 210){
epsilon = 6e-15;
} else if (x > 180){
epsilon = 5e-15;
} else if (x > 150){
epsilon = 4e-15;
} else if (x > 125){
epsilon = 3e-15;
} else if (x > 100){
epsilon = 2e-15;
} else if (x > 65){
epsilon = 1e-15;
}
} else {
epsilon = 6e-16;
}
CAPTURE(base10);
if (base10){
epsilon *= 20;
}
exp_test_(base10, epsilon);
}
static void exp_test(
//input
const char* x_str, int x_exp,
bool base10=false
)
{
CAPTURE(x_str); CAPTURE(x_exp);
CAPTURE(base10);
build_dec80(x_str, x_exp);
exp_test(base10);
}
static void exp10_test(const char* x_str, int x_exp){
exp_test(x_str, x_exp, true);
}
TEST_CASE("exp"){
exp_test("4.4", 0);
exp_test("0.155", 0);
exp_test("9.999", 0);
exp_test("10", 0);
exp_test("10.001", 0);
exp_test("2.3", 2);//, 6e-15);
exp_test("2.02", -10);
exp_test("2.02", 0);
exp_test("1.5", 0);
exp_test("99.999999", 0);
exp_test("230.2", 0);//, 6e-15);
exp_test("-230", 0);//, 6e-15);
exp_test("294.69999999", 0);//, 8e-15);
//do not operate on NaN
set_dec80_NaN(&AccDecn);
exp_decn();
CHECK(decn_is_nan(&AccDecn)); //still NaN
}
TEST_CASE("exp10"){
exp10_test("4.4", 0);
exp10_test("0.155", 0);
exp10_test("9.999", 0);
exp10_test("10", 0);
exp10_test("10.001", 0);
exp10_test("2.02", -10);
exp10_test("2.02", 0);
exp10_test("1.5", 0);
exp10_test("127", 0);//, 3e-14);
exp10_test("99.999999", 0);//, 2e-14);
}
static void test_exp_random(int exp_distrib_low){
std::default_random_engine gen;
std::uniform_int_distribution<int> distrib(0, 99);
std::uniform_int_distribution<int> lsu0_high_distrib(0, 23);
std::uniform_int_distribution<int> exp_distrib(exp_distrib_low, 2);
std::uniform_int_distribution<int> sign_distrib(0, 1);
for (int j = 0; j < NUM_RAND_TESTS; j++){
int exp = exp_distrib(gen);
int sign = sign_distrib(gen);
if (exp == 2) {
//limit x to approximately +/- 230
AccDecn.lsu[0] = lsu0_high_distrib(gen);
} else {
AccDecn.lsu[0] = distrib(gen);
}
for (int i = 1; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
}
set_exponent(&AccDecn, exp, sign);
exp_test();
}
}
TEST_CASE("exp random"){
test_exp_random(-99);
}
TEST_CASE("exp large random"){
test_exp_random(1);
}
static void pow_test(){ // a^b
bmp::mpf_float::default_precision(50);
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // a
bmp::mpfr_float a_actual(Buf);
decn_to_str_complete(&BDecn);
CAPTURE(Buf); // b
bmp::mpfr_float b_actual(Buf);
//calculate result
pow_decn();
//calculate actual result
bmp::mpfr_float res_actual(pow(a_actual, b_actual));
//check overflow or underflow
if (decn_is_nan(&AccDecn)){
//check overflow or underflow
if (b_actual > 0) {
CHECK(log(res_actual) > 100);
} else {
CHECK(log(res_actual) < -100);
}
return;
}
//not over/underflow, get string and log calculated result
decn_to_str_complete(&AccDecn);
CAPTURE(Buf); // a^b
bmp::mpfr_float calculated(Buf);
//check relative error
double rel_tol = 4.5e-14;
if (a_actual > 1.0 && a_actual < 1.0001){
rel_tol = 1e-7;
} else if (a_actual > 0.9 && a_actual < 2.0){
rel_tol = 1.5e-10;
} else if (log(res_actual) > 100){
rel_tol = 1e-12;
}
CAPTURE(a_actual);
CAPTURE(rel_tol);
if (decn_is_zero(&AccDecn)) {
bmp::mpfr_float diff = abs(res_actual - calculated);
CHECK(diff < rel_tol);
} else {
bmp::mpfr_float rel_diff = abs((res_actual - calculated)/res_actual);
CHECK(rel_diff < rel_tol);
}
}
static void pow_test(
//input
const char* a_str, int a_exp,
const char* b_str, int b_exp
)
{
CAPTURE(a_str); CAPTURE(a_exp);
CAPTURE(b_str); CAPTURE(b_exp);
//compute power
build_decn_at(&BDecn, b_str, b_exp);
build_dec80(a_str, a_exp);
pow_test();
}
TEST_CASE("power"){
pow_test(
"3.14", 60,
"-1.5", -2
);
pow_test(
"3", 0,
"201", 0
);
pow_test(
"5", 0,
"0", 0
);
pow_test(
"5", 0,
"0", 2
);
pow_test(
"0", 0,
"5", 0
);
pow_test(
"0", 0,
"0", 0
);
}
static void power_test(int lsu0_low, int lsu0_high, int exp_low=-99, int exp_high=99){
std::default_random_engine gen;
std::uniform_int_distribution<int> lsu0_distrib(lsu0_low, lsu0_high);
std::uniform_int_distribution<int> distrib(0, 99);
std::uniform_int_distribution<int> exp_distrib(exp_low, exp_high);
std::uniform_int_distribution<int> sign_distrib(0,1);
for (int j = 0; j < NUM_RAND_TESTS; j++){
AccDecn.lsu[0] = lsu0_distrib(gen);
for (int i = 1; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
BDecn.lsu[i] = distrib(gen);
}
set_exponent(&AccDecn, exp_distrib(gen), 0);
//generate exponent for b to minimize chance of a^b overflowing:
// a^b <= 1e100
// b*log(a) <= log(1e100) = 100
// b <= 100/log(a)
// b_exponent <= log(100/log(a)) = log(100) - log(log(a))
// b_exponent <= 2 - log(log(a))
decn_to_str_complete(&AccDecn);
bmp::mpfr_float acc(Buf);
acc = 2.0 - log(log(acc));
double b_exponent_high_flt = acc.convert_to<double>();
int b_exponent_high = b_exponent_high_flt;
int b_exponent_low = -99;
//ensure b_exponent high in range
if (b_exponent_high > 99){
b_exponent_high = 99;
} else if (b_exponent_high < b_exponent_low){
b_exponent_high = b_exponent_low;
}
CAPTURE(b_exponent_low);
CAPTURE(b_exponent_high);
std::uniform_int_distribution<int> b_exp_distrib(b_exponent_low, b_exponent_high);
int b_exponent = b_exp_distrib(gen);
CAPTURE(b_exponent);
int b_neg = sign_distrib(gen);
set_exponent(&BDecn, b_exponent, b_neg);
pow_test();
}
}
TEST_CASE("power random"){
power_test(0, 99);
}
TEST_CASE("power random 0.9 to 0.99..."){
power_test(90, 99, -1, -1);
}
TEST_CASE("power random 1.0 to 2.0..."){
power_test(10, 20, 0, 0);
}

View File

@ -1,26 +1,41 @@
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <string> #include <string>
#include <boost/multiprecision/mpfr.hpp> #include <boost/multiprecision/mpfr.hpp>
#include <catch.hpp> #include <catch2/catch.hpp>
#include "decn.h" #include "decn.h"
#include "decn_tests.h"
namespace bmp = boost::multiprecision; namespace bmp = boost::multiprecision;
using Catch::Matchers::Equals; using Catch::Matchers::Equals;
static void trig_test(void (*operation)(void), bmp::mpfr_float (*mpfr_operation)(bmp::mpfr_float x), static void trig_test(void (*operation)(void), bmp::mpfr_float (*mpfr_operation)(bmp::mpfr_float x),
const char* a_str, int a_exp, double rtol, double atol) double rtol, double atol)
{ {
CAPTURE(a_str); CAPTURE(a_exp); //build mpfr float
build_dec80(a_str, a_exp); bmp::mpfr_float::default_precision(50);
decn_to_str_complete(&AccDecn);
CAPTURE(Buf);
bmp::mpfr_float a_actual(Buf);
//calculate
operation(); operation();
decn_to_str_complete(&AccDecn); decn_to_str_complete(&AccDecn);
CAPTURE(Buf); CAPTURE(Buf);
//calculate actual
bmp::mpfr_float::default_precision(50);
std::string a_full_str(a_str);
a_full_str += "e" + std::to_string(a_exp);
bmp::mpfr_float a_actual(a_full_str);
a_actual = mpfr_operation(a_actual); a_actual = mpfr_operation(a_actual);
CAPTURE(a_actual); CAPTURE(a_actual);
@ -32,50 +47,92 @@ static void trig_test(void (*operation)(void), bmp::mpfr_float (*mpfr_operation)
bmp::mpfr_float diff = abs(a_actual - calculated); bmp::mpfr_float diff = abs(a_actual - calculated);
CHECK(diff < atol); CHECK(diff < atol);
} }
} }
static void sin_test(
const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3) static void sin_test(double rtol=5e-3, double atol=1e-3)
{ {
trig_test(sin_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return sin(x);}, a_str, a_exp, rtol, atol); CAPTURE("sin test");
trig_test(sin_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return sin(x);}, rtol, atol);
} }
static void cos_test( static void sin_test(const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3)
const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3)
{ {
trig_test(cos_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return cos(x);}, a_str, a_exp, rtol, atol); CAPTURE(a_str); CAPTURE(a_exp);
build_dec80(a_str, a_exp);
sin_test(rtol, atol);
} }
static void tan_test(
const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3) static void cos_test(double rtol=5e-3, double atol=1e-3)
{ {
trig_test(tan_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return tan(x);}, a_str, a_exp, rtol, atol); CAPTURE("cos test");
trig_test(cos_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return cos(x);}, rtol, atol);
} }
static void atan_test( static void cos_test(const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3)
const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3)
{ {
trig_test(arctan_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return atan(x);}, a_str, a_exp, rtol, atol); CAPTURE(a_str); CAPTURE(a_exp);
build_dec80(a_str, a_exp);
cos_test(rtol, atol);
} }
static void asin_test(
const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3) static void tan_test(double rtol=5e-3, double atol=1e-3)
{ {
trig_test(arcsin_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return asin(x);}, a_str, a_exp, rtol, atol); trig_test(tan_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return tan(x);}, rtol, atol);
} }
static void acos_test( static void tan_test(const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3)
const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3)
{ {
trig_test(arccos_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return acos(x);}, a_str, a_exp, rtol, atol); CAPTURE(a_str); CAPTURE(a_exp);
build_dec80(a_str, a_exp);
tan_test(rtol, atol);
} }
static void atan_test(double rtol=5e-3, double atol=1e-3)
{
trig_test(arctan_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return atan(x);}, rtol, atol);
}
static void atan_test(const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3)
{
CAPTURE(a_str); CAPTURE(a_exp);
build_dec80(a_str, a_exp);
atan_test(rtol, atol);
}
static void asin_test(double rtol=5e-3, double atol=1e-3)
{
trig_test(arcsin_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return asin(x);}, rtol, atol);
}
static void asin_test(const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3)
{
CAPTURE(a_str); CAPTURE(a_exp);
build_dec80(a_str, a_exp);
asin_test(rtol, atol);
}
static void acos_test(double rtol=5e-3, double atol=1e-3)
{
trig_test(arccos_decn, [](bmp::mpfr_float x) -> bmp::mpfr_float {return acos(x);}, rtol, atol);
}
static void acos_test(const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3)
{
CAPTURE(a_str); CAPTURE(a_exp);
build_dec80(a_str, a_exp);
acos_test(rtol, atol);
}
const char * const pi = "3.141592653589793239"; const char * const pi = "3.141592653589793239";
const char * const pi_threequarters = "2.356194490192344929"; const char * const pi_threequarters = "2.356194490192344929";
const char * const pi_halfed = "1.570796326794896619"; const char * const pi_halved = "1.570796326794896619";
const char * const pi_quarted = ".7853981633974483096"; const char * const pi_quarter = ".7853981633974483096";
TEST_CASE("sin") { TEST_CASE("sin") {
@ -96,8 +153,8 @@ TEST_CASE("sin") {
sin_test("2.5", 0); sin_test("2.5", 0);
sin_test("3.0", 0); sin_test("3.0", 0);
sin_test(pi, 0, -1); sin_test(pi, 0, -1);
sin_test(pi_quarted, 0); sin_test(pi_quarter, 0);
sin_test(pi_halfed, 0); sin_test(pi_halved, 0);
sin_test(pi_threequarters, 0); sin_test(pi_threequarters, 0);
sin_test("1000.0", 0); sin_test("1000.0", 0);
sin_test("-0.5", 0); sin_test("-0.5", 0);
@ -129,8 +186,8 @@ TEST_CASE("cos") {
cos_test("2.5", 0); cos_test("2.5", 0);
cos_test("3.0", 0); cos_test("3.0", 0);
cos_test(pi, 0); cos_test(pi, 0);
cos_test(pi_quarted, 0); cos_test(pi_quarter, 0);
cos_test(pi_halfed, 0, -1); cos_test(pi_halved, 0, -1);
cos_test(pi_threequarters, 0); cos_test(pi_threequarters, 0);
cos_test("1000.0", 0); cos_test("1000.0", 0);
cos_test("-0.5", 0); cos_test("-0.5", 0);
@ -201,3 +258,219 @@ TEST_CASE("arccos") {
acos_test("0.9", 0); acos_test("0.9", 0);
acos_test("-0.9", 0); acos_test("-0.9", 0);
} }
static const int NUM_RAND_TRIG_TESTS = 4321; //trig tests are slow
TEST_CASE("sin random"){
std::default_random_engine gen;
std::uniform_int_distribution<int> distrib(0,99);
std::uniform_int_distribution<int> exp_distrib(-1,0); //restrict range for now
std::uniform_int_distribution<int> sign_distrib(0,1);
for (int j = 0; j < NUM_RAND_TRIG_TESTS; j++){
for (int i = 0; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
}
int exp = exp_distrib(gen);
int sign = sign_distrib(gen);
set_exponent(&AccDecn, exp, sign);
int lsu0 = AccDecn.lsu[0];
CAPTURE(lsu0);
CAPTURE(exp);
CAPTURE(sign);
if (exp == -1 && lsu0 == 0){
//very small
sin_test(40);
} else if ((exp == -1 && lsu0 < 10) || (exp == 0 && lsu0 == 0)){
//small
sin_test(0.4);
} else if ((exp == 0 && lsu0 == 31)){
//near pi
sin_test(0.2);
} else if ((exp == 0 && lsu0 == 62)){
//near 2pi
sin_test(0.2);
} else if ((exp == 0 && lsu0 > 62)){
//large
sin_test(0.1);
} else {
sin_test(0.02);
}
}
}
TEST_CASE("cos random"){
std::default_random_engine gen;
std::uniform_int_distribution<int> distrib(0,99);
std::uniform_int_distribution<int> exp_distrib(-1,0); //restrict range for now
std::uniform_int_distribution<int> sign_distrib(0,1);
for (int j = 0; j < NUM_RAND_TRIG_TESTS; j++){
for (int i = 0; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
}
int exp = exp_distrib(gen);
int sign = sign_distrib(gen);
set_exponent(&AccDecn, exp, sign);
int lsu0 = AccDecn.lsu[0];
CAPTURE(lsu0);
CAPTURE(exp);
CAPTURE(sign);
if (exp == 0 && lsu0 == 15){
//near pi/2
cos_test(0.4);
} else if (exp == 0 && lsu0 == 47){
//near 3/2 * pi
cos_test(0.4);
} else if (exp == 0 && lsu0 == 78){
//near 5/2 * pi
// cos_test(0.4);
cos_test(1.1); //actual rtol is much worse than 0.4, random test happens to hit a bad one
} else {
cos_test(0.02);
}
}
}
TEST_CASE("tan random"){
std::default_random_engine gen;
std::uniform_int_distribution<int> distrib(0,99);
std::uniform_int_distribution<int> exp_distrib(-1,0); //restrict range for now
std::uniform_int_distribution<int> sign_distrib(0,1);
for (int j = 0; j < NUM_RAND_TRIG_TESTS; j++){
for (int i = 0; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
}
int exp = exp_distrib(gen);
int sign = sign_distrib(gen);
set_exponent(&AccDecn, exp, sign);
int lsu0 = AccDecn.lsu[0];
CAPTURE(lsu0);
CAPTURE(exp);
CAPTURE(sign);
if (exp == -1 && lsu0 == 0){
//very small
tan_test(40);
} else if ((exp == -1 && lsu0 < 10) || (exp == 0 && lsu0 == 0)){
//small
tan_test(0.5);
} else if (exp == 0 && lsu0 == 15){
//near pi/2
tan_test(0.5);
} else if ((exp == 0 && lsu0 == 31)){
//near pi
tan_test(0.2);
} else if (exp == 0 && lsu0 == 47){
//near 3/2 * pi
tan_test(0.5);
} else if ((exp == 0 && lsu0 == 62)){
//near 2pi
tan_test(0.2);
} else if (exp == 0 && lsu0 == 78){
//near 5/2 * pi
// tan_test(0.5);
tan_test(0.6); //actual rtol is much worse than 0.4, random test happens to hit a bad one
} else if ((exp == 0 && lsu0 > 62)){
//large
tan_test(0.1);
} else {
tan_test(0.02);
}
}
}
TEST_CASE("atan random"){
std::default_random_engine gen;
std::uniform_int_distribution<int> distrib(0, 99);
std::uniform_int_distribution<int> exp_distrib(-1, 0); //restrict range for now
std::uniform_int_distribution<int> sign_distrib(0, 1);
for (int j = 0; j < NUM_RAND_TRIG_TESTS; j++){
for (int i = 0; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
}
int exp = exp_distrib(gen);
int sign = sign_distrib(gen);
set_exponent(&AccDecn, exp, sign);
remove_leading_zeros(&AccDecn);
int lsu0 = AccDecn.lsu[0];
exp = get_exponent(&AccDecn);
CAPTURE(lsu0);
CAPTURE(exp);
CAPTURE(sign);
if (exp <= -6){
//extremely small
atan_test(10000);
} else if (exp < -1 || (exp == -1 && lsu0 == 0)){
//very small
atan_test(100);
} else if ((exp == -1 && lsu0 < 10) || (exp == 0 && lsu0 == 0)){
//small
atan_test(3);
} else {
atan_test(0.02);
}
}
}
TEST_CASE("asin random"){
std::default_random_engine gen;
std::uniform_int_distribution<int> distrib(0, 99);
std::uniform_int_distribution<int> exp_distrib(-2, -1); //restrict range for now
std::uniform_int_distribution<int> sign_distrib(0, 1);
for (int j = 0; j < NUM_RAND_TRIG_TESTS; j++){
for (int i = 0; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
}
int exp = exp_distrib(gen);
int sign = sign_distrib(gen);
set_exponent(&AccDecn, exp, sign);
remove_leading_zeros(&AccDecn);
int lsu0 = AccDecn.lsu[0];
exp = get_exponent(&AccDecn);
CAPTURE(lsu0);
CAPTURE(exp);
CAPTURE(sign);
if (exp <= -7) {
//extremely small
asin_test(50000);
} else if (exp < -5) {
//very very small
asin_test(1000);
} else if (exp < -1 || (exp == -1 && lsu0 == 0)){
//very small
asin_test(100);
} else if ((exp == -1 && lsu0 < 10) || (exp == 0 && lsu0 == 0)){
//small
asin_test(0.5);
} else {
asin_test(0.02);
}
}
}
TEST_CASE("acos random"){
std::default_random_engine gen;
std::uniform_int_distribution<int> distrib(0, 99);
std::uniform_int_distribution<int> exp_distrib(-2, -1); //restrict range for now
std::uniform_int_distribution<int> sign_distrib(0, 1);
for (int j = 0; j < NUM_RAND_TRIG_TESTS; j++){
for (int i = 0; i < DEC80_NUM_LSU; i++){
AccDecn.lsu[i] = distrib(gen);
}
int exp = exp_distrib(gen);
int sign = sign_distrib(gen);
set_exponent(&AccDecn, exp, sign);
remove_leading_zeros(&AccDecn);
int lsu0 = AccDecn.lsu[0];
exp = get_exponent(&AccDecn);
CAPTURE(lsu0);
CAPTURE(exp);
CAPTURE(sign);
if ((exp == -1 && lsu0 == 99)){
//near 1
acos_test(10);
} else {
acos_test(0.02);
}
}
}

View File

@ -17,11 +17,13 @@ cmake .. -GNinja
ninja ninja
# run tests # run tests
src/decn/decn_tests ctest -j $(nproc)
# get coverage # get coverage
echo "Running lcov" echo "Running lcov"
lcov --capture --directory src/decn --output-file coverage.info lcov --capture --directory src/decn --output-file coverage.info
lcov --remove coverage.info "/usr/*" --output-file coverage.info
genhtml coverage.info --output-directory lcov genhtml coverage.info --output-directory lcov
echo "Running gcov" echo "Running gcov"
gcov -b src/decn/CMakeFiles/decn_cover.dir/decn.c.gcno gcov -b src/decn/CMakeFiles/decn_cover.dir/decn.c.gcno