From 7a7ceb58882828096cac87494f5a1ffb5359e212 Mon Sep 17 00:00:00 2001 From: Rudy Huyn Date: Tue, 16 Apr 2019 17:17:24 -0700 Subject: [PATCH] Modify how modulo is calculated in Normal and Scientific mode. (#412) ## Fixes #111 > The modulo operator on this calculator gives the result that is different to the most used calculators. The current `modrate` function is the equivalent of rem(...)/remainder(...), not mod(...)/modulo(...) available in some popular Math apps. ### Description of the changes: - rename `modrate` in `remrate` to be more accurate. - add `modrate`, calculating modulo similarly to Matlab, Bing, Google calculator, Maxima, Wolfram Alpha and Microsoft Excel - Add `RationalMath::Mod` using `modrate` as an alternative to `Rational::operator%` using `remrate` - Add a helper `SIGN` to retrieve the sign of a `Rational`. - modify `CalcEngine` to use `modrate` in Normal and Scientific mode and `remrate` in Programmer mode. ### How changes were validated: - manually and unit tests added --- src/CalcManager/CEngine/Rational.cpp | 15 +- src/CalcManager/CEngine/RationalMath.cpp | 30 +++ src/CalcManager/CEngine/scioper.cpp | 28 ++- src/CalcManager/Header Files/CalcEngine.h | 4 +- src/CalcManager/Header Files/RationalMath.h | 1 + src/CalcManager/Ratpack/exp.cpp | 2 +- src/CalcManager/Ratpack/fact.cpp | 4 +- src/CalcManager/Ratpack/itrans.cpp | 14 +- src/CalcManager/Ratpack/logic.cpp | 186 +++++++++------ src/CalcManager/Ratpack/ratpak.h | 6 +- src/CalcManager/Ratpack/support.cpp | 16 +- src/CalculatorUnitTests/CalcEngineTests.cpp | 2 +- src/CalculatorUnitTests/CalcInputTest.cpp | 2 +- .../CalculatorUnitTests.vcxproj | 1 + .../CalculatorUnitTests.vcxproj.filters | 1 + src/CalculatorUnitTests/RationalTest.cpp | 225 ++++++++++++++++++ 16 files changed, 429 insertions(+), 108 deletions(-) create mode 100644 src/CalculatorUnitTests/RationalTest.cpp diff --git a/src/CalcManager/CEngine/Rational.cpp b/src/CalcManager/CEngine/Rational.cpp index fd86b72..c93c79a 100644 --- a/src/CalcManager/CEngine/Rational.cpp +++ b/src/CalcManager/CEngine/Rational.cpp @@ -182,6 +182,13 @@ namespace CalcEngine return *this; } + /// + /// Calculate the remainder after division, the sign of a result will match the sign of the current object. + /// + /// + /// This function has the same behavior as the standard C/C++ operator '%' + /// to calculate the modulus after division instead, use instead. + /// Rational& Rational::operator%=(Rational const& rhs) { PRAT lhsRat = this->ToPRAT(); @@ -189,7 +196,7 @@ namespace CalcEngine try { - modrat(&lhsRat, rhsRat); + remrat(&lhsRat, rhsRat); destroyrat(rhsRat); } catch (uint32_t error) @@ -342,6 +349,12 @@ namespace CalcEngine return lhs; } + /// + /// Calculate the remainder after division, the sign of a result will match the sign of lhs. + /// + /// + /// This function has the same behavior as 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 37dc792..f5de74f 100644 --- a/src/CalcManager/CEngine/RationalMath.cpp +++ b/src/CalcManager/CEngine/RationalMath.cpp @@ -387,3 +387,33 @@ Rational RationalMath::ATanh(Rational const& rat) return result; } + +/// +/// 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 = a.ToPRAT(); + PRAT pn = b.ToPRAT(); + + try + { + modrat(&prat, pn); + destroyrat(pn); + } + catch (uint32_t error) + { + destroyrat(prat); + destroyrat(pn); + throw(error); + } + + auto res = Rational{ prat }; + destroyrat(prat); + return res; +} diff --git a/src/CalcManager/CEngine/scioper.cpp b/src/CalcManager/CEngine/scioper.cpp index b09083c..9ced702 100644 --- a/src/CalcManager/CEngine/scioper.cpp +++ b/src/CalcManager/CEngine/scioper.cpp @@ -78,7 +78,7 @@ CalcEngine::Rational CCalcEngine::DoOperation(int operation, CalcEngine::Rationa case IDC_DIV: case IDC_MOD: { - int iNumeratorSign = 1, iDenominatorSign = 1, iFinalSign = 1; + int iNumeratorSign = 1, iDenominatorSign = 1; auto temp = result; result = rhs; @@ -107,20 +107,30 @@ CalcEngine::Rational CCalcEngine::DoOperation(int operation, CalcEngine::Rationa if (operation == IDC_DIV) { - iFinalSign = iNumeratorSign * iDenominatorSign; result /= temp; + if (m_fIntegerMode && (iNumeratorSign * iDenominatorSign) == -1) + { + result = -(Integer(result)); + } } else { - iFinalSign = iNumeratorSign; - result %= temp; - } + if (m_fIntegerMode) + { + // Programmer mode, use remrat (remainder after division) + result %= temp; - if (m_fIntegerMode && iFinalSign == -1) - { - result = -(Integer(result)); + if (iNumeratorSign == -1) + { + result = -(Integer(result)); + } + } + else + { + //other modes, use modrat (modulus after division) + result = Mod(result, temp); + } } - break; } diff --git a/src/CalcManager/Header Files/CalcEngine.h b/src/CalcManager/Header Files/CalcEngine.h index db906e5..c84f2d5 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; } @@ -160,5 +160,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 b52c1c5..5950057 100644 --- a/src/CalcManager/Header Files/RationalMath.h +++ b/src/CalcManager/Header Files/RationalMath.h @@ -13,6 +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& a, Rational const& b); Rational Exp(Rational const& rat); Rational Log(Rational const& rat); diff --git a/src/CalcManager/Ratpack/exp.cpp b/src/CalcManager/Ratpack/exp.cpp index c8691cf..9563740 100644 --- a/src/CalcManager/Ratpack/exp.cpp +++ b/src/CalcManager/Ratpack/exp.cpp @@ -408,7 +408,7 @@ void powratNumeratorDenominator(PRAT *px, PRAT y, uint32_t radix, int32_t precis //--------------------------------------------------------------------------- void powratcomp(PRAT *px, PRAT y, uint32_t radix, int32_t precision) { - int32_t sign = ((*px)->pp->sign * (*px)->pq->sign); + int32_t sign = SIGN(*px); // Take the absolute value (*px)->pp->sign = 1; diff --git a/src/CalcManager/Ratpack/fact.cpp b/src/CalcManager/Ratpack/fact.cpp index c974b00..276151a 100644 --- a/src/CalcManager/Ratpack/fact.cpp +++ b/src/CalcManager/Ratpack/fact.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. //----------------------------------------------------------------------------- @@ -216,7 +216,7 @@ void factrat( PRAT *px, uint32_t radix, int32_t precision) // Check for negative integers and throw an error. if ( ( zerrat(frac) || ( LOGRATRADIX(frac) <= -precision) ) && - ( (*px)->pp->sign * (*px)->pq->sign == -1 ) ) + ( SIGN(*px) == -1 ) ) { throw CALC_E_DOMAIN; } diff --git a/src/CalcManager/Ratpack/itrans.cpp b/src/CalcManager/Ratpack/itrans.cpp index 1ecbf4a..0d2a297 100644 --- a/src/CalcManager/Ratpack/itrans.cpp +++ b/src/CalcManager/Ratpack/itrans.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. //----------------------------------------------------------------------------- @@ -92,11 +92,9 @@ void asinanglerat( _Inout_ PRAT *pa, ANGLE_TYPE angletype, uint32_t radix, int32 void asinrat( PRAT *px, uint32_t radix, int32_t precision) { - int32_t sgn; PRAT pret= nullptr; PRAT phack= nullptr; - - sgn = (*px)->pp->sign* (*px)->pq->sign; + int32_t sgn = SIGN(*px); (*px)->pp->sign = 1; (*px)->pq->sign = 1; @@ -204,9 +202,7 @@ void _acosrat( PRAT *px, int32_t precision) void acosrat( PRAT *px, uint32_t radix, int32_t precision) { - int32_t sgn; - - sgn = (*px)->pp->sign*(*px)->pq->sign; + int32_t sgn = SIGN(*px); (*px)->pp->sign = 1; (*px)->pq->sign = 1; @@ -291,10 +287,8 @@ void _atanrat( PRAT *px, int32_t precision) void atanrat( PRAT *px, uint32_t radix, int32_t precision) { - int32_t sgn; PRAT tmpx= nullptr; - - sgn = (*px)->pp->sign * (*px)->pq->sign; + int32_t sgn = SIGN(*px); (*px)->pp->sign = 1; (*px)->pq->sign = 1; diff --git a/src/CalcManager/Ratpack/logic.cpp b/src/CalcManager/Ratpack/logic.cpp index c7f8a3a..6444898 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,51 @@ 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; +} + +//----------------------------------------------------------------------------- +// +// FUNCTION: remrat +// +// ARGUMENTS: pointer to a rational a second rational. +// +// RETURN: None, changes pointer. +// +// 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. +// +//----------------------------------------------------------------------------- + +void remrat(PRAT *pa, PRAT b) + +{ + if (zerrat(b)) + { + throw CALC_E_INDEFINITE; + } + + PRAT tmp = nullptr; + DUPRAT(tmp, b); + + mulnumx(&((*pa)->pp), tmp->pq); + mulnumx(&(tmp->pp), (*pa)->pq); + remnum(&((*pa)->pp), tmp->pp, BASEX); + mulnumx(&((*pa)->pq), tmp->pq); + + // Get *pa back in the integer over integer form. + RENORMALIZE(*pa); + + destroyrat(tmp); } //----------------------------------------------------------------------------- @@ -191,28 +227,38 @@ void boolnum( PNUMBER *pa, PNUMBER b, int func ) // // RETURN: None, changes pointer. // -// DESCRIPTION: Does the rational equivalent of frac(*pa); +// 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. // //----------------------------------------------------------------------------- -void modrat( PRAT *pa, PRAT b ) - +void modrat(PRAT *pa, PRAT b) { + //contrary to remrat(X, 0) returning 0, modrat(X, 0) must return X + if (zerrat(b)) + { + return; + } + PRAT tmp = nullptr; + DUPRAT(tmp, b); - if ( zerrat( b ) ) - { - throw CALC_E_INDEFINITE; - } - DUPRAT(tmp,b); + auto needAdjust = (SIGN(*pa) == -1 ? (SIGN(b) == 1) : (SIGN(b) == -1)); - mulnumx( &((*pa)->pp), tmp->pq ); - mulnumx( &(tmp->pp), (*pa)->pq ); - remnum( &((*pa)->pp), tmp->pp, BASEX ); - mulnumx( &((*pa)->pq), tmp->pq ); + mulnumx(&((*pa)->pp), tmp->pq); + mulnumx(&(tmp->pp), (*pa)->pq); + remnum(&((*pa)->pp), tmp->pp, BASEX); + mulnumx(&((*pa)->pq), tmp->pq); + + if (needAdjust && !zerrat(*pa)) + { + addrat(pa, b, BASEX); + } // Get *pa back in the integer over integer form. RENORMALIZE(*pa); - destroyrat( tmp ); + destroyrat(tmp); } diff --git a/src/CalcManager/Ratpack/ratpak.h b/src/CalcManager/Ratpack/ratpak.h index 75ac28d..d9bc11c 100644 --- a/src/CalcManager/Ratpack/ratpak.h +++ b/src/CalcManager/Ratpack/ratpak.h @@ -148,6 +148,9 @@ extern PRAT rat_min_i32; #define LOGNUM2(pnum) ((pnum)->cdigit+(pnum)->exp) #define LOGRAT2(prat) (LOGNUM2((prat)->pp)-LOGNUM2((prat)->pq)) +// SIGN returns the sign of the rational +#define SIGN(prat) ((prat)->pp->sign*(prat)->pq->sign) + #if defined( DEBUG_RATPAK ) //----------------------------------------------------------------------------- // @@ -423,7 +426,8 @@ extern void divnumx( _Inout_ PNUMBER *pa, _In_ PNUMBER b, int32_t precision); extern void divrat( _Inout_ PRAT *pa, _In_ PRAT b, int32_t precision); extern void fracrat( _Inout_ PRAT *pa , uint32_t radix, int32_t precision); extern void factrat( _Inout_ PRAT *pa, uint32_t radix, int32_t precision); -extern void modrat( _Inout_ PRAT *pa, _In_ PRAT b ); +extern void remrat(_Inout_ PRAT *pa, _In_ PRAT b); +extern void modrat(_Inout_ PRAT *pa, _In_ PRAT b); extern void gcdrat( _Inout_ PRAT *pa, int32_t precision); extern void intrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision); extern void mulnum( _Inout_ PNUMBER *pa, _In_ PNUMBER b, uint32_t radix); diff --git a/src/CalcManager/Ratpack/support.cpp b/src/CalcManager/Ratpack/support.cpp index ebee00c..22683b4 100644 --- a/src/CalcManager/Ratpack/support.cpp +++ b/src/CalcManager/Ratpack/support.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. //---------------------------------------------------------------------------- @@ -296,7 +296,7 @@ void intrat( PRAT *px, uint32_t radix, int32_t precision) // Subtract the fractional part of the rational PRAT pret = nullptr; DUPRAT(pret,*px); - modrat( &pret, rat_one ); + remrat( &pret, rat_one ); subrat( px, pret, precision); destroyrat( pret ); @@ -348,8 +348,7 @@ bool rat_ge( PRAT a, PRAT b, int32_t precision) b->pp->sign *= -1; addrat( &rattmp, b, precision); b->pp->sign *= -1; - bool bret = ( zernum( rattmp->pp ) || - rattmp->pp->sign * rattmp->pq->sign == 1 ); + bool bret = ( zernum( rattmp->pp ) || SIGN(rattmp) == 1 ); destroyrat( rattmp ); return( bret ); } @@ -374,8 +373,7 @@ bool rat_gt( PRAT a, PRAT b, int32_t precision) b->pp->sign *= -1; addrat( &rattmp, b, precision); b->pp->sign *= -1; - bool bret = ( !zernum( rattmp->pp ) && - rattmp->pp->sign * rattmp->pq->sign == 1 ); + bool bret = ( !zernum( rattmp->pp ) && SIGN(rattmp) == 1 ); destroyrat( rattmp ); return( bret ); } @@ -400,8 +398,7 @@ bool rat_le( PRAT a, PRAT b, int32_t precision) b->pp->sign *= -1; addrat( &rattmp, b, precision); b->pp->sign *= -1; - bool bret = ( zernum( rattmp->pp ) || - rattmp->pp->sign * rattmp->pq->sign == -1 ); + bool bret = ( zernum( rattmp->pp ) || SIGN(rattmp) == -1 ); destroyrat( rattmp ); return( bret ); } @@ -426,8 +423,7 @@ bool rat_lt( PRAT a, PRAT b, int32_t precision) b->pp->sign *= -1; addrat( &rattmp, b, precision); b->pp->sign *= -1; - bool bret = ( !zernum( rattmp->pp ) && - rattmp->pp->sign * rattmp->pq->sign == -1 ); + bool bret = ( !zernum( rattmp->pp ) && SIGN(rattmp) == -1 ); destroyrat( rattmp ); return( bret ); } diff --git a/src/CalculatorUnitTests/CalcEngineTests.cpp b/src/CalculatorUnitTests/CalcEngineTests.cpp index d572a44..9109c8d 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 551fbd7..4d224a3 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/CalculatorUnitTests.vcxproj b/src/CalculatorUnitTests/CalculatorUnitTests.vcxproj index d199044..0afdfd1 100644 --- a/src/CalculatorUnitTests/CalculatorUnitTests.vcxproj +++ b/src/CalculatorUnitTests/CalculatorUnitTests.vcxproj @@ -248,6 +248,7 @@ + diff --git a/src/CalculatorUnitTests/CalculatorUnitTests.vcxproj.filters b/src/CalculatorUnitTests/CalculatorUnitTests.vcxproj.filters index 57ac4fe..20400d4 100644 --- a/src/CalculatorUnitTests/CalculatorUnitTests.vcxproj.filters +++ b/src/CalculatorUnitTests/CalculatorUnitTests.vcxproj.filters @@ -29,6 +29,7 @@ Mocks + diff --git a/src/CalculatorUnitTests/RationalTest.cpp b/src/CalculatorUnitTests/RationalTest.cpp new file mode 100644 index 0000000..c19692f --- /dev/null +++ b/src/CalculatorUnitTests/RationalTest.cpp @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "pch.h" +#include +#include "Header Files/Rational.h" +#include "Header Files/RationalMath.h" + +using namespace CalcEngine; +using namespace CalcEngine::RationalMath; +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace CalculatorEngineTests +{ + TEST_CLASS(RationalTest) + { + public: + TEST_CLASS_INITIALIZE(CommonSetup) + { + ChangeConstants(10, 128); + } + + TEST_METHOD(TestModuloOperandsNotModified) + { + // Verify results but also check that operands are not modified + Rational rat25(25); + Rational ratminus25(-25); + Rational rat4(4); + Rational ratminus4(-4); + Rational res = Mod(rat25, rat4); + VERIFY_ARE_EQUAL(res, 1); + VERIFY_ARE_EQUAL(rat25, 25); + VERIFY_ARE_EQUAL(rat4, 4); + res = Mod(rat25, ratminus4); + VERIFY_ARE_EQUAL(res, -3); + VERIFY_ARE_EQUAL(rat25, 25); + VERIFY_ARE_EQUAL(ratminus4, -4); + res = Mod(ratminus25, ratminus4); + VERIFY_ARE_EQUAL(res, -1); + VERIFY_ARE_EQUAL(ratminus25, -25); + VERIFY_ARE_EQUAL(ratminus4, -4); + res = Mod(ratminus25, rat4); + VERIFY_ARE_EQUAL(res, 3); + VERIFY_ARE_EQUAL(ratminus25, -25); + VERIFY_ARE_EQUAL(rat4, 4); + } + + TEST_METHOD(TestModuloInteger) + { + // Check with integers + auto res = Mod(Rational(426), Rational(56478)); + VERIFY_ARE_EQUAL(res, 426); + res = Mod(Rational(56478), Rational(426)); + VERIFY_ARE_EQUAL(res, 246); + res = Mod(Rational(-643), Rational(8756)); + VERIFY_ARE_EQUAL(res, 8113); + res = Mod(Rational(643), Rational(-8756)); + VERIFY_ARE_EQUAL(res, -8113); + res = Mod(Rational(-643), Rational(-8756)); + VERIFY_ARE_EQUAL(res, -643); + res = Mod(Rational(1000), Rational(250)); + VERIFY_ARE_EQUAL(res, 0); + res = Mod(Rational(1000), Rational(-250)); + VERIFY_ARE_EQUAL(res, 0); + } + + TEST_METHOD(TestModuloZero) + { + // Test with Zero + auto res = Mod(Rational(343654332), Rational(0)); + VERIFY_ARE_EQUAL(res, 343654332); + res = Mod(Rational(0), Rational(8756)); + VERIFY_ARE_EQUAL(res, 0); + res = Mod(Rational(0), Rational(-242)); + VERIFY_ARE_EQUAL(res, 0); + res = Mod(Rational(0), Rational(0)); + VERIFY_ARE_EQUAL(res, 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, 11621); + } + + TEST_METHOD(TestModuloRational) + { + // Test with rational numbers + auto res = Mod(Rational(Number(1, 0, { 250 }), Number(1, 0, { 100 })), Rational(89)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"2.5"); + res = Mod(Rational(Number(1, 0, { 3330 }), Number(1, 0, { 1332 })), Rational(1)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"0.5"); + res = Mod(Rational(Number(1, 0, { 12250 }), Number(1, 0, { 100 })), Rational(10)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"2.5"); + res = Mod(Rational(Number(-1, 0, { 12250 }), Number(1, 0, { 100 })), Rational(10)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"7.5"); + res = Mod(Rational(Number(-1, 0, { 12250 }), Number(1, 0, { 100 })), Rational(-10)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"-2.5"); + res = Mod(Rational(Number(1, 0, { 12250 }), Number(1, 0, { 100 })), Rational(-10)); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), 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(TestRemainderOperandsNotModified) + { + //Verify results but also check that operands are not modified + Rational rat25(25); + Rational ratminus25(-25); + Rational rat4(4); + Rational ratminus4(-4); + Rational res = rat25 % rat4; + VERIFY_ARE_EQUAL(res, 1); + VERIFY_ARE_EQUAL(rat25, 25); + VERIFY_ARE_EQUAL(rat4, 4); + res = rat25 % ratminus4; + VERIFY_ARE_EQUAL(res, 1); + VERIFY_ARE_EQUAL(rat25, 25); + VERIFY_ARE_EQUAL(ratminus4, -4); + res = ratminus25 % ratminus4; + VERIFY_ARE_EQUAL(res, -1); + VERIFY_ARE_EQUAL(ratminus25, -25); + VERIFY_ARE_EQUAL(ratminus4, -4); + res = ratminus25 % rat4; + VERIFY_ARE_EQUAL(res, -1); + VERIFY_ARE_EQUAL(ratminus25, -25); + VERIFY_ARE_EQUAL(rat4, 4); + } + + TEST_METHOD(TestRemainderInteger) + { + // Check with integers + auto res = Rational(426) % Rational(56478); + VERIFY_ARE_EQUAL(res, 426); + res = Rational(56478) % Rational(426); + VERIFY_ARE_EQUAL(res, 246); + res = Rational(-643) % Rational(8756); + VERIFY_ARE_EQUAL(res, -643); + res = Rational(643) % Rational(-8756); + VERIFY_ARE_EQUAL(res, 643); + res = Rational(-643) % Rational(-8756); + VERIFY_ARE_EQUAL(res, -643); + res = Rational(-124) % Rational(-124); + VERIFY_ARE_EQUAL(res, 0); + res = Rational(24) % Rational(24); + VERIFY_ARE_EQUAL(res, 0); + } + + TEST_METHOD(TestRemainderZero) + { + // Test with Zero + auto res = Rational(0) % Rational(3654); + VERIFY_ARE_EQUAL(res, 0); + res = Rational(0) % Rational(-242); + VERIFY_ARE_EQUAL(res, 0); + for (auto number : { 343654332, 0, -23423 }) + { + 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(); + } + } + } + + TEST_METHOD(TestRemainderRational) + { + //Test with rational numbers + auto res = Rational(Number(1, 0, { 250 }), Number(1, 0, { 100 })) % Rational(89); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"2.5"); + res = Rational(Number(1, 0, { 3330 }), Number(1, 0, { 1332 })) % Rational(1); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"0.5"); + res = Rational(Number(1, 0, { 12250 }), Number(1, 0, { 100 })) % Rational(10); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"2.5"); + res = Rational(Number(-1, 0, { 12250 }), Number(1, 0, { 100 })) % Rational(10); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"-2.5"); + res = Rational(Number(-1, 0, { 12250 }), Number(1, 0, { 100 })) % Rational(-10); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), L"-2.5"); + res = Rational(Number(1, 0, { 12250 }), Number(1, 0, { 100 })) % Rational(-10); + VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 8), 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); + 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"); + } + }; +}