diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7332049..be20ff8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,5 +25,7 @@ jobs: ${{ github.workspace }}/build/ ${{ github.workspace }}/build_qt/lcov/ ${{ github.workspace }}/build_qt/decn.c.gcov + ${{ github.workspace }}/build_qt/Testing/ ${{ github.workspace }}/main.hex if-no-files-found: error + diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b77b3c..cf0c4d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,20 +4,23 @@ project(stc_rpncalc C CXX) find_package(Qt5 COMPONENTS Widgets Qml Quick REQUIRED) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") -message(STATUS "using address sanitizer") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls") -link_libraries(asan) + message(STATUS "using address sanitizer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls") + link_libraries(asan) endif() set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type (for tests debug make sense)") # Compiler warnings if(MSVC) - add_compile_options(/W4 /WX) + add_compile_options(/W4 /WX) else() - add_compile_options(-Wall -Wextra -pedantic) + add_compile_options(-Wall -Wextra -pedantic) endif() +# CTest Catch2 tests +enable_testing() + # Directory with source code add_subdirectory(src) add_subdirectory(qt_gui) diff --git a/Dockerfile b/Dockerfile index 253b0b0..123f28c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,6 @@ FROM ubuntu:18.04 RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ build-essential \ - catch \ clang \ cmake \ git \ @@ -13,4 +12,10 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ ninja-build \ qtdeclarative5-dev \ 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 diff --git a/README.md b/README.md index e17c600..2c7e4eb 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ The keys on the *original* calculator map as follows: - acts as acos(x) when shifted down - `3 `: acts as tan(x) when shifted - 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 degrees when shifted down - `+ `: 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/ - 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. - - build similarly to other cmake projects: + - build similarly to other cmake projects, see [Dockerfile](Dockerfile) for build dependencies: - `mkdir build_qt && cd build_qt` - `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) @@ -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). -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) - 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: 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). diff --git a/src/decn/CMakeLists.txt b/src/decn/CMakeLists.txt index 77ef315..d627d94 100644 --- a/src/decn/CMakeLists.txt +++ b/src/decn/CMakeLists.txt @@ -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 add_library(coverage_config INTERFACE) 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) target_link_libraries(decn_cover PUBLIC coverage_config) -add_executable(decn_test decn_test.c ../utils.c) -target_link_libraries(decn_test decn_cover coverage_config Catch) +# old tests (compare output with reference "golden" output file) +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) -target_link_libraries(decn_tests decn_cover coverage_config mpfr Catch) +# catch2 unit tests +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 add_subdirectory(proto) diff --git a/src/decn/catch_main.cpp b/src/decn/catch_main.cpp index f930f49..54bc23c 100644 --- a/src/decn/catch_main.cpp +++ b/src/decn/catch_main.cpp @@ -21,4 +21,4 @@ #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file -#include "catch.hpp" +#include diff --git a/src/decn/decn.c b/src/decn/decn.c index c44a87a..53f5ae1 100644 --- a/src/decn/decn.c +++ b/src/decn/decn.c @@ -149,7 +149,7 @@ exp_t get_exponent(const dec80* const x){ #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 if (num_is_neg){ 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 is_negative = (x->exponent < 0); exp_t exponent = get_exponent(x); diff --git a/src/decn/decn.h b/src/decn/decn.h index 46f649e..ea8b888 100644 --- a/src/decn/decn.h +++ b/src/decn/decn.h @@ -60,6 +60,11 @@ typedef struct { //remove sign bit, and return 15 bit exponent sign-extended to 16 bits 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); extern dec80 AccDecn; diff --git a/src/decn/decn_tests.cpp b/src/decn/decn_tests.cpp index 912a2ab..550d993 100644 --- a/src/decn/decn_tests.cpp +++ b/src/decn/decn_tests.cpp @@ -21,11 +21,14 @@ #include +#include #include -#include +#include #include "decn.h" #include "../utils.h" +#include "decn_tests.h" + namespace bmp = boost::multiprecision; using Catch::Matchers::Equals; @@ -258,333 +261,6 @@ TEST_CASE("multiply"){ 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"){ u32str(0, &Buf[0], 10); CHECK_THAT(Buf, Equals("0")); diff --git a/src/decn/decn_tests.h b/src/decn/decn_tests.h new file mode 100644 index 0000000..15ecdf6 --- /dev/null +++ b/src/decn/decn_tests.h @@ -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 . + +/* + * decn_tests.h + * + * Created on: Oct 26, 2020 + */ + + +#ifndef DECN_TESTS_H_ +#define DECN_TESTS_H_ + + +static const int NUM_RAND_TESTS = 123456; + + +#endif diff --git a/src/decn/decn_tests_div_sqrt.cpp b/src/decn/decn_tests_div_sqrt.cpp new file mode 100644 index 0000000..ccc874d --- /dev/null +++ b/src/decn/decn_tests_div_sqrt.cpp @@ -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 . + +/* + * 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 +#include +#include +#include +#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 distrib(0, 99); + std::uniform_int_distribution 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 distribution(0,99); + std::uniform_int_distribution exp_distrib(-99,99); + std::uniform_int_distribution 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(); + } +} diff --git a/src/decn/decn_tests_transcendental.cpp b/src/decn/decn_tests_transcendental.cpp new file mode 100644 index 0000000..2d70631 --- /dev/null +++ b/src/decn/decn_tests_transcendental.cpp @@ -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 . + +/* + * decn_tests_transcendental.cpp + * + * Unit tests using https://github.com/catchorg/Catch2 + * + * separate out transcendental function tests + * + * Created on: Oct 26, 2020 + */ + + +#include +#include +#include +#include +#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 distrib(0,99); + std::uniform_int_distribution exp_distrib(-99,99); + std::uniform_int_distribution 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 lsu0_distrib(lsu0_low, lsu0_high); + std::uniform_int_distribution distrib(0,99); + std::uniform_int_distribution exp_distrib(-99,99); + std::uniform_int_distribution 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 distrib(0, 99); + std::uniform_int_distribution lsu0_high_distrib(0, 23); + std::uniform_int_distribution exp_distrib(exp_distrib_low, 2); + std::uniform_int_distribution 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 lsu0_distrib(lsu0_low, lsu0_high); + std::uniform_int_distribution distrib(0, 99); + std::uniform_int_distribution exp_distrib(exp_low, exp_high); + std::uniform_int_distribution 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(); + 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 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); +} diff --git a/src/decn/decn_tests_trig.cpp b/src/decn/decn_tests_trig.cpp index 534e7bf..af1166d 100644 --- a/src/decn/decn_tests_trig.cpp +++ b/src/decn/decn_tests_trig.cpp @@ -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 . + #include #include -#include +#include #include "decn.h" + +#include "decn_tests.h" + namespace bmp = boost::multiprecision; using Catch::Matchers::Equals; 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_dec80(a_str, a_exp); + //build mpfr float + bmp::mpfr_float::default_precision(50); + decn_to_str_complete(&AccDecn); + CAPTURE(Buf); + bmp::mpfr_float a_actual(Buf); + //calculate operation(); decn_to_str_complete(&AccDecn); CAPTURE(Buf); - - 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); + //calculate actual a_actual = mpfr_operation(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); 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( - const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3) +static void sin_test(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( - const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3) +static void cos_test(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( - const char* a_str, int a_exp, double rtol=5e-3, double atol=1e-3) +static void tan_test(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_threequarters = "2.356194490192344929"; -const char * const pi_halfed = "1.570796326794896619"; -const char * const pi_quarted = ".7853981633974483096"; +const char * const pi_halved = "1.570796326794896619"; +const char * const pi_quarter = ".7853981633974483096"; TEST_CASE("sin") { @@ -96,8 +153,8 @@ TEST_CASE("sin") { sin_test("2.5", 0); sin_test("3.0", 0); sin_test(pi, 0, -1); - sin_test(pi_quarted, 0); - sin_test(pi_halfed, 0); + sin_test(pi_quarter, 0); + sin_test(pi_halved, 0); sin_test(pi_threequarters, 0); sin_test("1000.0", 0); sin_test("-0.5", 0); @@ -129,8 +186,8 @@ TEST_CASE("cos") { cos_test("2.5", 0); cos_test("3.0", 0); cos_test(pi, 0); - cos_test(pi_quarted, 0); - cos_test(pi_halfed, 0, -1); + cos_test(pi_quarter, 0); + cos_test(pi_halved, 0, -1); cos_test(pi_threequarters, 0); cos_test("1000.0", 0); cos_test("-0.5", 0); @@ -201,3 +258,219 @@ TEST_CASE("arccos") { 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 distrib(0,99); + std::uniform_int_distribution exp_distrib(-1,0); //restrict range for now + std::uniform_int_distribution 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 distrib(0,99); + std::uniform_int_distribution exp_distrib(-1,0); //restrict range for now + std::uniform_int_distribution 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 distrib(0,99); + std::uniform_int_distribution exp_distrib(-1,0); //restrict range for now + std::uniform_int_distribution 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 distrib(0, 99); + std::uniform_int_distribution exp_distrib(-1, 0); //restrict range for now + std::uniform_int_distribution 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 distrib(0, 99); + std::uniform_int_distribution exp_distrib(-2, -1); //restrict range for now + std::uniform_int_distribution 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 distrib(0, 99); + std::uniform_int_distribution exp_distrib(-2, -1); //restrict range for now + std::uniform_int_distribution 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); + } + } +} diff --git a/steps/desktop_build_check.sh b/steps/desktop_build_check.sh index 94601a4..80aadd7 100755 --- a/steps/desktop_build_check.sh +++ b/steps/desktop_build_check.sh @@ -17,11 +17,13 @@ cmake .. -GNinja ninja # run tests -src/decn/decn_tests +ctest -j $(nproc) # get coverage echo "Running lcov" lcov --capture --directory src/decn --output-file coverage.info +lcov --remove coverage.info "/usr/*" --output-file coverage.info genhtml coverage.info --output-directory lcov echo "Running gcov" -gcov -b src/decn/CMakeFiles/decn_cover.dir/decn.c.gcno \ No newline at end of file +gcov -b src/decn/CMakeFiles/decn_cover.dir/decn.c.gcno +