diff --git a/CMakeLists.txt b/CMakeLists.txt index a9c9cd0..9bf35de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.1) # 3rd party tools find_package(Qt5 COMPONENTS Widgets Qml Quick REQUIRED) diff --git a/src/decn/CMakeLists.txt b/src/decn/CMakeLists.txt index 3643b00..625c1ec 100644 --- a/src/decn/CMakeLists.txt +++ b/src/decn/CMakeLists.txt @@ -1,12 +1,21 @@ +#code coverage +add_library(coverage_config INTERFACE) +target_compile_options(coverage_config INTERFACE -O0 -g --coverage) +target_link_libraries(coverage_config INTERFACE --coverage) + # decn library add_library(decn decn.c) +# decn library with coverage +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) +target_link_libraries(decn_test decn_cover coverage_config) add_executable(decn_tests catch_main.cpp decn_tests.cpp ../utils.c) -target_link_libraries(decn_tests decn mpfr) +target_link_libraries(decn_tests decn_cover coverage_config mpfr) # decn prototyping add_subdirectory(proto) diff --git a/src/decn/decn.c b/src/decn/decn.c index 2225398..ed5a116 100644 --- a/src/decn/decn.c +++ b/src/decn/decn.c @@ -30,9 +30,9 @@ // #define DEBUG_MULT // #define DEBUG_MULT_ALL //even more verbose // #define DEBUG_DIV -#define DEBUG_LOG +// #define DEBUG_LOG // #define DEBUG_LOG_ALL //even more verbose -#define DEBUG_EXP +// #define DEBUG_EXP // #define DEBUG_EXP_ALL //even more verbose #ifndef DESKTOP @@ -211,6 +211,7 @@ void build_dec80(__xdata const char* signif_str, exp_t exponent){ return; } else if (signif_str[0] == '-'){ curr_sign = SIGN_NEG_ZERO; + i++; } //go through digits @@ -293,7 +294,7 @@ void build_dec80(__xdata const char* signif_str, exp_t exponent){ return; } else { //not zero - int8_t new_exponent; + exp_t new_exponent = exponent; //write out saved nibble, if it exists // (saved while nibble_i even, nibble_i then incremented to odd) if (nibble_i & 1){ //odd @@ -307,37 +308,28 @@ void build_dec80(__xdata const char* signif_str, exp_t exponent){ if (num_lr_points > 0){ //left count exists assert(DEC80_NUM_LSU*2 > num_lr_points); new_exponent = exponent + (num_lr_points - 1); //1 digit left of implicit point - //check for overflow -#ifdef EXTRA_CHECKS - if (new_exponent < exponent || exponent > DEC80_MAX_EXP){ - #ifdef DEBUG - printf(" overflow (new_exp, exp)=(%d,%d)\n", - new_exponent, exponent); - #endif - set_dec80_NaN(&AccDecn); - return; - } -#endif + //overflow is checked later, should be impossible to overflow int16_t: + assert(new_exponent >= exponent); } else if (num_lr_points < 0) { //right count exists // (num_lr_points represents exponent shift) // (this ends up being a subtraction) new_exponent = exponent + num_lr_points; - //check for underflow -#ifdef EXTRA_CHECKS - if (new_exponent > exponent || exponent < DEC80_MIN_EXP){ - #ifdef DEBUG - printf(" underflow (new_exp, exp)=(%d,%d)\n", - new_exponent, exponent); - #endif - set_dec80_NaN(&AccDecn); - return; - } -#endif - } else { - //no change - new_exponent = exponent; + //underflow is checked later, should be impossible to overflow int16_t: + assert(new_exponent <= exponent); } + + //check for over/underflow of exponent exponent = new_exponent; +#ifdef EXTRA_CHECKS + if (exponent > DEC80_MAX_EXP || exponent < DEC80_MIN_EXP){ + #ifdef DEBUG + printf(" over/underflow (new_exp, exp)=(%d,%d)\n", + new_exponent, exponent); + #endif + set_dec80_NaN(&AccDecn); + return; + } +#endif //set negative bit set_exponent(&AccDecn, exponent, IS_NEG(curr_sign)); //normalize @@ -354,6 +346,12 @@ void build_dec80(__xdata const char* signif_str, exp_t exponent){ return; } + } else { //invalid character +#ifdef DEBUG + printf(" invalid character %c at i=%d\n", signif_str[i], i); +#endif + set_dec80_NaN(&AccDecn); + return; } i++; assert(i < DECN_BUF_SIZE); @@ -439,7 +437,7 @@ static int8_t compare_magn(void){ //returns ab: 1 //compare signifcands while tracking magnitude for ( a_i = 0, b_i = 0; - a_i < DEC80_NUM_LSU && b_i < DEC80_NUM_LSU; + a_i < DEC80_NUM_LSU; a_i++, b_i++, a_exp+=2, b_exp+=2 ) { @@ -662,26 +660,17 @@ void add_decn(void){ uint8_t digit100 = AccDecn.lsu[i] + BDecn.lsu[i] + carry; AccDecn.lsu[i] = digit100 % 100; carry = digit100 / 100; - assert(carry < 100); + assert(carry <= 1); } //may need to rescale number if (carry > 0){ + assert(carry == 1); exp_t curr_exp = get_exponent(&AccDecn); rel = (AccDecn.exponent < 0); //is_neg? -#ifdef DEBUG_ADD - printf(" carry out: %d", carry); -#endif //shift right - if (carry < 10){ - shift_right(&AccDecn); - AccDecn.lsu[0] += carry*10; //carry gets shifted into most significant digit - curr_exp++; - } else { - shift_right(&AccDecn); - shift_right(&AccDecn); - AccDecn.lsu[0] = carry; - curr_exp+=2; - } + shift_right(&AccDecn); + AccDecn.lsu[0] += 10; //carry gets shifted into most significant digit + curr_exp++; //track sign set_exponent(&AccDecn, curr_exp, rel); //rel==is_neg? } @@ -715,7 +704,8 @@ void mult_decn(void){ //calculate new exponent new_exponent = get_exponent(&AccDecn) + get_exponent(&BDecn); #ifdef DEBUG_MULT - printf("\n new exponent: %d, is_neg: %u", new_exponent, is_neg); + printf("\n a_exp: %d, b_exp: %d", get_exponent(&AccDecn), get_exponent(&BDecn)); + printf("\n new exponent: %d, is_neg: %u", new_exponent, is_neg); #endif //do multiply for (i = DEC80_NUM_LSU - 1; i >= 0; i--){ @@ -1079,13 +1069,11 @@ void exp_decn(void){ //check if in range copy_decn(&SAVED, &AccDecn); //save = accum set_dec80_zero(&BDecn); - BDecn.lsu[0] = 23; - BDecn.lsu[1] = 02; - BDecn.lsu[2] = 58; - BDecn.lsu[2] = 51; - BDecn.exponent = 2; //b = 230.25851 + BDecn.lsu[0] = 29; + BDecn.lsu[1] = 47; + BDecn.exponent = 2; //b = 294.7 negate_decn(&BDecn); - add_decn(); //accum = x - 230.25851 (should be negative if in range) + add_decn(); //accum = x - 294.7 (should be negative if in range) if (!(AccDecn.exponent < 0)){ //if not negative set_dec80_NaN(&AccDecn); return; @@ -1249,7 +1237,12 @@ static void set_str_error(void){ Buf[5] = '\0'; } -int8_t decn_to_str(const dec80* x){ +#ifdef DESKTOP +int +#else +int8_t +#endif +decn_to_str(const dec80* x){ #define INSERT_DOT() Buf[i++]='.' uint8_t i = 0; uint8_t digit100; @@ -1377,7 +1370,11 @@ int8_t decn_to_str(const dec80* x){ //print exponent if (use_sci){ //check for overflow +#ifdef DESKTOP + if (exponent > DEC80_MAX_EXP || exponent < DEC80_MIN_EXP){ +#else if (exponent > DECN_MAX_PRINT_EXP || exponent < DECN_MIN_PRINT_EXP){ +#endif set_str_error(); return 0; } @@ -1398,7 +1395,7 @@ int8_t decn_to_str(const dec80* x){ #ifdef DESKTOP //complete string including exponent void decn_to_str_complete(const dec80* x){ - int8_t exponent = decn_to_str(x); + int exponent = decn_to_str(x); int i; //find end of string for (i = 0; Buf[i] != '\0'; i++); diff --git a/src/decn/decn.h b/src/decn/decn.h index 93da64b..a0f2b70 100644 --- a/src/decn/decn.h +++ b/src/decn/decn.h @@ -107,7 +107,13 @@ void exp10_decn(void); //Buf should hold at least 18 + 4 + 5 + 1 = 28 #define DECN_BUF_SIZE 28 extern __xdata char Buf[DECN_BUF_SIZE]; -int8_t decn_to_str(const dec80* x); + +#ifdef DESKTOP +int +#else +int8_t +#endif +decn_to_str(const dec80* x); #ifdef DESKTOP //complete string including exponent diff --git a/src/decn/decn_tests.cpp b/src/decn/decn_tests.cpp index 8af22c4..970c4ea 100644 --- a/src/decn/decn_tests.cpp +++ b/src/decn/decn_tests.cpp @@ -20,10 +20,11 @@ */ -#include +#include #include #include #include "decn.h" +#include "../utils.h" namespace bmp = boost::multiprecision; @@ -46,6 +47,119 @@ TEST_CASE("build decn"){ negate_decn(&AccDecn); decn_to_str_complete(&AccDecn); CHECK_THAT(Buf, Equals("-9234.567890123456")); + + //small positive + build_dec80(".1", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("100.")); + + build_dec80("0.1", -1); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("0.01")); + + build_dec80("0.01", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("10.")); + + //zero + build_dec80(".", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("0")); + + build_dec80(".0", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("0")); + + build_dec80("0.", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("0")); + + build_dec80("-0.0", -1); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("0")); + + build_dec80("0", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("0")); + + //small negative + build_dec80("-.1", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("-100.")); + + build_dec80("-0.1", -1); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("-0.01")); + + build_dec80("-0.001", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("-1.")); + + //empty string -> 0 + build_dec80("", 90); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("0")); + + //too many . + build_dec80("..", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("Error")); + + //very long (truncated) + build_dec80("12345678901234567890", -2); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("123456789012345678.")); + + //overflow + build_dec80("100", DEC80_MAX_EXP-1); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("Error")); + + build_dec80("1", DEC80_MAX_EXP+1); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("Error")); + + build_dec80("0.1", DEC80_MAX_EXP+2); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("Error")); + + //underflow + build_dec80("10", DEC80_MIN_EXP-2); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("Error")); + + build_dec80("1", DEC80_MIN_EXP-1); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("Error")); + + build_dec80("0.3", DEC80_MIN_EXP); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("Error")); + CHECK(decn_is_nan(&AccDecn) == 1); + + //left/right count + build_dec80("-100.001", 3); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("-100001.")); + + //invalid + build_dec80(":", 0); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("Error")); + + //special number that is not NaN + AccDecn.lsu[1] = 3; + CHECK(decn_is_nan(&AccDecn) == 0); +} + +TEST_CASE("build_large"){ + int large_exp = DEC80_MAX_EXP/2 - 50; + build_dec80("9.99", large_exp); + decn_to_str_complete(&AccDecn); + CHECK(AccDecn.exponent == large_exp); + std::string expected = "9.99E"; + expected += std::to_string(large_exp); + CHECK_THAT(Buf, Equals(expected)); } TEST_CASE("small fractions >= 1/10"){ @@ -91,6 +205,24 @@ TEST_CASE("add"){ decn_to_str_complete(&AccDecn); //compare result of new acc - b CHECK_THAT(Buf, Equals("-9234.567890123456")); //acc-b + + //add 0 + build_decn_at(&BDecn, "0", 0); + add_decn(); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("-9234.567890123456")); //same + + //carry into MSB + build_dec80( "-82345678901234567.8", -1); + build_decn_at(&BDecn, "-87654321098765432.2", -1); + add_decn(); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf,Equals("-17000000000000000.")); //acc+b + + //don't negate NaN + set_dec80_NaN(&AccDecn); + negate_decn(&AccDecn); + CHECK(decn_is_nan(&AccDecn)); } TEST_CASE("multiply"){ @@ -103,6 +235,14 @@ TEST_CASE("multiply"){ mult_decn(); decn_to_str_complete(&AccDecn); CHECK_THAT(Buf, Equals("-8527724.41172991849")); //acc*b + + //overflow + build_dec80("9.99", DEC80_MAX_EXP/2); + build_decn_at(&BDecn, "9.99", DEC80_MAX_EXP/2); + mult_decn(); + decn_to_str_complete(&AccDecn); + CHECK_THAT(Buf, Equals("Error")); //acc*b + } static void div_test( @@ -126,7 +266,6 @@ static void div_test( //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); @@ -135,12 +274,24 @@ static void div_test( // CAPTURE(b_full_str); bmp::mpfr_float a_actual(a_full_str); bmp::mpfr_float b_actual(b_full_str); - a_actual /= b_actual; - bmp::mpfr_float rel_diff = abs((a_actual - calculated) / a_actual); - CHECK(rel_diff < 1e-17); + 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 @@ -198,22 +349,29 @@ static void log_test( //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); - // CAPTURE(x_full_str); + x_full_str += "e" + std::to_string(x_exp); + CAPTURE(x_full_str); bmp::mpfr_float x_actual(x_full_str); - if (base10){ - x_actual = log10(x_actual); - } else { - x_actual = log(x_actual); - } CAPTURE(x_actual); - bmp::mpfr_float rel_diff = abs((x_actual - calculated) / x_actual); - CHECK(rel_diff < 3e-16); //TODO + 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); @@ -221,19 +379,27 @@ TEST_CASE("log"){ 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 + double epsilon=6e-16, + bool base10=false ) { CAPTURE(x_str); CAPTURE(x_exp); + CAPTURE(base10); build_dec80(x_str, x_exp); - exp_decn(); + 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); @@ -241,12 +407,24 @@ static void exp_test( 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); @@ -257,6 +435,25 @@ TEST_CASE("exp"){ 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( @@ -306,3 +503,8 @@ TEST_CASE("power"){ "201", 0 ); } + +TEST_CASE("u32str corner"){ + u32str(0, &Buf[0], 10); + CHECK_THAT(Buf, Equals("0")); +}