From 4908efcb9650682d5e1c0ed930e47ddec82ccbcf Mon Sep 17 00:00:00 2001 From: Rudy Huyn Date: Sun, 31 Mar 2019 16:53:15 -0700 Subject: [PATCH] Add unit tests + fix issue when a mod b == 0 with b negative --- src/CalcManager/CEngine/Rational.cpp | 12 ++ src/CalcManager/CEngine/RationalMath.cpp | 13 +- src/CalcManager/Header Files/CalcEngine.h | 4 +- src/CalcManager/Header Files/RationalMath.h | 2 +- src/CalcManager/Ratpack/logic.cpp | 127 ++++++++++---------- src/CalculatorUnitTests/CalcEngineTests.cpp | 2 +- src/CalculatorUnitTests/CalcInputTest.cpp | 2 +- src/CalculatorUnitTests/RationalTest.cpp | 117 ++++++++++++++++-- 8 files changed, 195 insertions(+), 84 deletions(-) diff --git a/src/CalcManager/CEngine/Rational.cpp b/src/CalcManager/CEngine/Rational.cpp index 865ff09c..9ca5ccb8 100644 --- a/src/CalcManager/CEngine/Rational.cpp +++ b/src/CalcManager/CEngine/Rational.cpp @@ -182,6 +182,12 @@ namespace CalcEngine return *this; } + /// + /// Calculate the remainder after division, the sign of the result will match the sign of the current object. + /// + /// + /// This function has the same behavior than the standard C/C++ operator '%', to calculate the modulus after division instead, use instead. + /// Rational& Rational::operator%=(Rational const& rhs) { PRAT lhsRat = this->ToPRAT(); @@ -342,6 +348,12 @@ namespace CalcEngine return lhs; } + /// + /// Calculate the remainder after division, the sign of the result will match the sign of a. + /// + /// + /// This function has the same behavior than the standard C/C++ operator '%', to calculate the modulus after division instead, use instead. + /// Rational operator%(Rational lhs, Rational const& rhs) { lhs %= rhs; diff --git a/src/CalcManager/CEngine/RationalMath.cpp b/src/CalcManager/CEngine/RationalMath.cpp index a3cffb7a..2e423898 100644 --- a/src/CalcManager/CEngine/RationalMath.cpp +++ b/src/CalcManager/CEngine/RationalMath.cpp @@ -388,11 +388,16 @@ Rational RationalMath::ATanh(Rational const& rat) return result; } - -Rational RationalMath::Mod(Rational const& base, Rational const& n) +/// +/// Calculate the modulus after division, the sign of the result will match the sign of b. +/// +/// +/// When one of the operand is negative, the result will differ from the C/C++ operator '%', use instead to calculate the remainder after division. +/// +Rational RationalMath::Mod(Rational const& a, Rational const& b) { - PRAT prat = base.ToPRAT(); - PRAT pn = n.ToPRAT(); + PRAT prat = a.ToPRAT(); + PRAT pn = b.ToPRAT(); try { diff --git a/src/CalcManager/Header Files/CalcEngine.h b/src/CalcManager/Header Files/CalcEngine.h index 0ad8252f..8ba0d923 100644 --- a/src/CalcManager/Header Files/CalcEngine.h +++ b/src/CalcManager/Header Files/CalcEngine.h @@ -45,7 +45,7 @@ namespace CalculationManager class IResourceProvider; } -namespace CalculatorUnitTests +namespace CalculatorEngineTests { class CalcEngineTests; } @@ -159,5 +159,5 @@ private: static void ChangeBaseConstants(uint32_t radix, int maxIntDigits, int32_t precision); void BaseOrPrecisionChanged(); - friend class CalculatorUnitTests::CalcEngineTests; + friend class CalculatorEngineTests::CalcEngineTests; }; diff --git a/src/CalcManager/Header Files/RationalMath.h b/src/CalcManager/Header Files/RationalMath.h index 7a9f86ba..59500573 100644 --- a/src/CalcManager/Header Files/RationalMath.h +++ b/src/CalcManager/Header Files/RationalMath.h @@ -13,7 +13,7 @@ namespace CalcEngine::RationalMath Rational Pow(Rational const& base, Rational const& pow); Rational Root(Rational const& base, Rational const& root); Rational Fact(Rational const& rat); - Rational Mod(Rational const& base, Rational const& n); + Rational Mod(Rational const& a, Rational const& b); Rational Exp(Rational const& rat); Rational Log(Rational const& rat); diff --git a/src/CalcManager/Ratpack/logic.cpp b/src/CalcManager/Ratpack/logic.cpp index 41622b51..223769ee 100644 --- a/src/CalcManager/Ratpack/logic.cpp +++ b/src/CalcManager/Ratpack/logic.cpp @@ -18,54 +18,54 @@ using namespace std; -void lshrat( PRAT *pa, PRAT b, uint32_t radix, int32_t precision) +void lshrat(PRAT *pa, PRAT b, uint32_t radix, int32_t precision) { - PRAT pwr= nullptr; + PRAT pwr = nullptr; int32_t intb; intrat(pa, radix, precision); - if ( !zernum( (*pa)->pp ) ) - { + if (!zernum((*pa)->pp)) + { // If input is zero we're done. - if ( rat_gt( b, rat_max_exp, precision) ) - { + if (rat_gt(b, rat_max_exp, precision)) + { // Don't attempt lsh of anything big - throw( CALC_E_DOMAIN ); - } + throw(CALC_E_DOMAIN); + } intb = rattoi32(b, radix, precision); - DUPRAT(pwr,rat_two); + DUPRAT(pwr, rat_two); ratpowi32(&pwr, intb, precision); mulrat(pa, pwr, precision); destroyrat(pwr); - } + } } -void rshrat( PRAT *pa, PRAT b, uint32_t radix, int32_t precision) +void rshrat(PRAT *pa, PRAT b, uint32_t radix, int32_t precision) { - PRAT pwr= nullptr; + PRAT pwr = nullptr; int32_t intb; intrat(pa, radix, precision); - if ( !zernum( (*pa)->pp ) ) - { + if (!zernum((*pa)->pp)) + { // If input is zero we're done. - if ( rat_lt( b, rat_min_exp, precision) ) - { + if (rat_lt(b, rat_min_exp, precision)) + { // Don't attempt rsh of anything big and negative. - throw( CALC_E_DOMAIN ); - } + throw(CALC_E_DOMAIN); + } intb = rattoi32(b, radix, precision); - DUPRAT(pwr,rat_two); + DUPRAT(pwr, rat_two); ratpowi32(&pwr, intb, precision); divrat(pa, pwr, precision); destroyrat(pwr); - } + } } -void boolrat( PRAT *pa, PRAT b, int func, uint32_t radix, int32_t precision); -void boolnum( PNUMBER *pa, PNUMBER b, int func ); +void boolrat(PRAT *pa, PRAT b, int func, uint32_t radix, int32_t precision); +void boolnum(PNUMBER *pa, PNUMBER b, int func); enum { @@ -74,22 +74,22 @@ enum { FUNC_XOR } BOOL_FUNCS; -void andrat( PRAT *pa, PRAT b, uint32_t radix, int32_t precision) +void andrat(PRAT *pa, PRAT b, uint32_t radix, int32_t precision) { - boolrat( pa, b, FUNC_AND, radix, precision); + boolrat(pa, b, FUNC_AND, radix, precision); } -void orrat( PRAT *pa, PRAT b, uint32_t radix, int32_t precision) +void orrat(PRAT *pa, PRAT b, uint32_t radix, int32_t precision) { - boolrat( pa, b, FUNC_OR, radix, precision); + boolrat(pa, b, FUNC_OR, radix, precision); } -void xorrat( PRAT *pa, PRAT b, uint32_t radix, int32_t precision) +void xorrat(PRAT *pa, PRAT b, uint32_t radix, int32_t precision) { - boolrat( pa, b, FUNC_XOR, radix, precision); + boolrat(pa, b, FUNC_XOR, radix, precision); } //--------------------------------------------------------------------------- @@ -104,15 +104,15 @@ void xorrat( PRAT *pa, PRAT b, uint32_t radix, int32_t precision) // //--------------------------------------------------------------------------- -void boolrat( PRAT *pa, PRAT b, int func, uint32_t radix, int32_t precision) +void boolrat(PRAT *pa, PRAT b, int func, uint32_t radix, int32_t precision) { - PRAT tmp= nullptr; - intrat( pa, radix, precision); - DUPRAT(tmp,b); - intrat( &tmp, radix, precision); + PRAT tmp = nullptr; + intrat(pa, radix, precision); + DUPRAT(tmp, b); + intrat(&tmp, radix, precision); - boolnum( &((*pa)->pp), tmp->pp, func ); + boolnum(&((*pa)->pp), tmp->pp, func); destroyrat(tmp); } @@ -130,11 +130,11 @@ void boolrat( PRAT *pa, PRAT b, int func, uint32_t radix, int32_t precision) // //--------------------------------------------------------------------------- -void boolnum( PNUMBER *pa, PNUMBER b, int func ) +void boolnum(PNUMBER *pa, PNUMBER b, int func) { - PNUMBER c= nullptr; - PNUMBER a= nullptr; + PNUMBER c = nullptr; + PNUMBER a = nullptr; MANTTYPE *pcha; MANTTYPE *pchb; MANTTYPE *pchc; @@ -143,26 +143,26 @@ void boolnum( PNUMBER *pa, PNUMBER b, int func ) MANTTYPE da; MANTTYPE db; - a=*pa; - cdigits = max( a->cdigit+a->exp, b->cdigit+b->exp ) - - min( a->exp, b->exp ); - createnum( c, cdigits ); - c->exp = min( a->exp, b->exp ); + a = *pa; + cdigits = max(a->cdigit + a->exp, b->cdigit + b->exp) - + min(a->exp, b->exp); + createnum(c, cdigits); + c->exp = min(a->exp, b->exp); mexp = c->exp; c->cdigit = cdigits; pcha = a->mant; pchb = b->mant; pchc = c->mant; - for ( ;cdigits > 0; cdigits--, mexp++ ) + for (; cdigits > 0; cdigits--, mexp++) + { + da = (((mexp >= a->exp) && (cdigits + a->exp - c->exp > + (c->cdigit - a->cdigit))) ? + *pcha++ : 0); + db = (((mexp >= b->exp) && (cdigits + b->exp - c->exp > + (c->cdigit - b->cdigit))) ? + *pchb++ : 0); + switch (func) { - da = ( ( ( mexp >= a->exp ) && ( cdigits + a->exp - c->exp > - (c->cdigit - a->cdigit) ) ) ? - *pcha++ : 0 ); - db = ( ( ( mexp >= b->exp ) && ( cdigits + b->exp - c->exp > - (c->cdigit - b->cdigit) ) ) ? - *pchb++ : 0 ); - switch ( func ) - { case FUNC_AND: *pchc++ = da & db; break; @@ -172,15 +172,15 @@ void boolnum( PNUMBER *pa, PNUMBER b, int func ) case FUNC_XOR: *pchc++ = da ^ db; break; - } } + } c->sign = a->sign; - while ( c->cdigit > 1 && *(--pchc) == 0 ) - { + while (c->cdigit > 1 && *(--pchc) == 0) + { c->cdigit--; - } - destroynum( *pa ); - *pa=c; + } + destroynum(*pa); + *pa = c; } //----------------------------------------------------------------------------- @@ -191,8 +191,9 @@ void boolnum( PNUMBER *pa, PNUMBER b, int func ) // // RETURN: None, changes pointer. // -// DESCRIPTION: Calculate the remainder of *pa / b, equivalent of 'pa % b' in C; -// NOTE: produces a result that is either zero or has the same sign as the dividend. +// DESCRIPTION: Calculate the remainder of *pa / b, +// equivalent of 'pa % b' in C/C++ and produces a result +// that is either zero or has the same sign as the dividend. // //----------------------------------------------------------------------------- @@ -226,8 +227,10 @@ void remrat(PRAT *pa, PRAT b) // // RETURN: None, changes pointer. // -// DESCRIPTION: Calculate the remainder of *pa / b, equivalent of 'pa modulo b' in arithmetic -// NOTE: produces a result that is either zero or has the same sign as the divisor. +// DESCRIPTION: Calculate the remainder of *pa / b, with the sign of the result +// either zero or has the same sign as the divisor. +// NOTE: When *pa or b are negative, the result won't be the same as +// the C/C++ operator %, use remrat if it's the behavior you expect. // //----------------------------------------------------------------------------- @@ -249,7 +252,7 @@ void modrat(PRAT *pa, PRAT b) remnum(&((*pa)->pp), tmp->pp, BASEX); mulnumx(&((*pa)->pq), tmp->pq); - if (needAdjust) + if (needAdjust && !zerrat(*pa)) { addrat(pa, b, BASEX); } diff --git a/src/CalculatorUnitTests/CalcEngineTests.cpp b/src/CalculatorUnitTests/CalcEngineTests.cpp index d572a442..9109c8d6 100644 --- a/src/CalculatorUnitTests/CalcEngineTests.cpp +++ b/src/CalculatorUnitTests/CalcEngineTests.cpp @@ -13,7 +13,7 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; static constexpr size_t MAX_HISTORY_SIZE = 20; -namespace CalculatorUnitTests +namespace CalculatorEngineTests { TEST_CLASS(CalcEngineTests) { diff --git a/src/CalculatorUnitTests/CalcInputTest.cpp b/src/CalculatorUnitTests/CalcInputTest.cpp index 551fbd71..4d224a3e 100644 --- a/src/CalculatorUnitTests/CalcInputTest.cpp +++ b/src/CalculatorUnitTests/CalcInputTest.cpp @@ -8,7 +8,7 @@ using namespace std; using namespace CalculationManager; using namespace Microsoft::VisualStudio::CppUnitTestFramework; -namespace CalculatorUnitTests +namespace CalculatorEngineTests { TEST_CLASS(CalcInputTest) { diff --git a/src/CalculatorUnitTests/RationalTest.cpp b/src/CalculatorUnitTests/RationalTest.cpp index def0571e..d7d20231 100644 --- a/src/CalculatorUnitTests/RationalTest.cpp +++ b/src/CalculatorUnitTests/RationalTest.cpp @@ -10,7 +10,7 @@ using namespace CalcEngine; using namespace CalcEngine::RationalMath; using namespace Microsoft::VisualStudio::CppUnitTestFramework; -namespace CalculatorManagerTest +namespace CalculatorEngineTests { TEST_CLASS(RationalTest) { @@ -44,10 +44,44 @@ namespace CalculatorManagerTest VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-8113"); res = Mod(Rational(-643), Rational(-8756)); VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-643"); - + res = Mod(Rational(1000), Rational(250)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0"); + res = Mod(Rational(1000), Rational(-250)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0"); //Test with Zero res = Mod(Rational(343654332), Rational(0)); VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"343654332"); + res = Mod(Rational(0), Rational(8756)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0"); + res = Mod(Rational(0), Rational(-242)); + auto dfd = res.ToString(10, FMT_FLOAT, 128); + VERIFY_ARE_EQUAL(dfd, L"0"); + res = Mod(Rational(0), Rational(0)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0"); + res = Mod(Rational(Number(1, 0, { 23242 }), Number(1, 0, { 2 })), Rational(Number(1, 0, { 0 }), Number(1, 0, { 23 }))); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"11621"); + + //Test with rational numbers + res = Mod(Rational(Number(1, 0, { 250 }), Number(1, 0, { 100 })), Rational(89)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"2.5"); + res = Mod(Rational(Number(1, 0, { 3330 }), Number(1, 0, { 1332 })), Rational(1)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0.5"); + res = Mod(Rational(Number(1, 0, { 12250 }), Number(1, 0, { 100 })), Rational(10)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"2.5"); + res = Mod(Rational(Number(-1, 0, { 12250 }), Number(1, 0, { 100 })), Rational(10)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"7.5"); + res = Mod(Rational(Number(-1, 0, { 12250 }), Number(1, 0, { 100 })), Rational(-10)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-2.5"); + res = Mod(Rational(Number(1, 0, { 12250 }), Number(1, 0, { 100 })), Rational(-10)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-7.5"); + res = Mod(Rational(Number(1, 0, { 1000 }), Number(1, 0, { 3 })), Rational(1)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"0.33333333"); + res = Mod(Rational(Number(1, 0, { 1000 }), Number(1, 0, { 3 })), Rational(-10)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"-6.6666667"); + res = Mod(Rational(834345), Rational(Number(1, 0, { 103 }), Number(1, 0, { 100 }))); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"0.71"); + res = Mod(Rational(834345), Rational(Number(-1, 0, { 103 }), Number(1, 0, { 100 }))); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"-0.32"); } TEST_METHOD(RemainderTest) @@ -76,23 +110,80 @@ namespace CalculatorManagerTest res = Rational(-643) % Rational(-8756); VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-643"); + res = Rational(-124) % Rational(-124); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0"); + res = Rational(24) % Rational(24); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0"); + //Test with Zero - try + res = Rational(0) % Rational(3654); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0"); + + res = Rational(0) % Rational(-242); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0"); + for (auto number : { 343654332, 0, -23423 }) { - res = Rational(343654332) % Rational(0); - Assert::Fail(); - } - catch (uint32_t t) - { - if (t != CALC_E_INDEFINITE) + try + { + res = Rational(number) % Rational(0); + Assert::Fail(); + } + catch (uint32_t t) + { + if (t != CALC_E_INDEFINITE) + { + Assert::Fail(); + } + } + catch (...) + { + Assert::Fail(); + } + + try + { + res = Rational(Number(1, number, { 0 }), Number(1, 0, { 2 })) % Rational(Number(1, 0, { 0 }), Number(1, 0, { 23 })); + Assert::Fail(); + } + catch (uint32_t t) + { + if (t != CALC_E_INDEFINITE) + { + Assert::Fail(); + } + } + catch (...) { Assert::Fail(); } } - catch (...) - { - Assert::Fail(); - } + + //Test with rational numbers + res = Rational(Number(1, 0, { 250 }), Number(1, 0, { 100 })) % Rational(89); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"2.5"); + res = Rational(Number(1, 0, { 3330 }), Number(1, 0, { 1332 })) % Rational(1); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"0.5"); + res = Rational(Number(1, 0, { 12250 }), Number(1, 0, { 100 })) % Rational(10); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"2.5"); + res = Rational(Number(-1, 0, { 12250 }), Number(1, 0, { 100 })) % Rational(10); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-2.5"); + res = Rational(Number(-1, 0, { 12250 }), Number(1, 0, { 100 })) % Rational(-10); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-2.5"); + res = Rational(Number(1, 0, { 12250 }), Number(1, 0, { 100 })) % Rational(-10); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"2.5"); + res = Rational(Number(1, 0, { 1000 }), Number(1, 0, { 3 })) % Rational(1); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"0.33333333"); + res = Rational(Number(1, 0, { 1000 }), Number(1, 0, { 3 })) % Rational(-10); + auto sdsdas = res.ToString(10, FMT_FLOAT, 8); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"3.3333333"); + res = Rational(Number(-1, 0, { 1000 }), Number(1, 0, { 3 })) % Rational(-10); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"-3.3333333"); + res = Rational(834345) % Rational(Number(1, 0, { 103 }), Number(1, 0, { 100 })); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"0.71"); + res = Rational(834345) % Rational(Number(-1, 0, { 103 }), Number(1, 0, { 100 })); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"0.71"); + res = Rational(-834345) % Rational(Number(1, 0, { 103 }), Number(1, 0, { 100 })); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"-0.71"); } }; }