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
This commit is contained in:
Rudy Huyn 2019-04-16 17:17:24 -07:00 committed by Daniel Belcher
parent ad25feda6b
commit 7a7ceb5888
16 changed files with 429 additions and 108 deletions

View File

@ -182,6 +182,13 @@ namespace CalcEngine
return *this; return *this;
} }
/// <summary>
/// Calculate the remainder after division, the sign of a result will match the sign of the current object.
/// </summary>
/// <remarks>
/// This function has the same behavior as the standard C/C++ operator '%'
/// to calculate the modulus after division instead, use <see cref="RationalMath::Mod"/> instead.
/// </remarks>
Rational& Rational::operator%=(Rational const& rhs) Rational& Rational::operator%=(Rational const& rhs)
{ {
PRAT lhsRat = this->ToPRAT(); PRAT lhsRat = this->ToPRAT();
@ -189,7 +196,7 @@ namespace CalcEngine
try try
{ {
modrat(&lhsRat, rhsRat); remrat(&lhsRat, rhsRat);
destroyrat(rhsRat); destroyrat(rhsRat);
} }
catch (uint32_t error) catch (uint32_t error)
@ -342,6 +349,12 @@ namespace CalcEngine
return lhs; return lhs;
} }
/// <summary>
/// Calculate the remainder after division, the sign of a result will match the sign of lhs.
/// </summary>
/// <remarks>
/// This function has the same behavior as the standard C/C++ operator '%', to calculate the modulus after division instead, use <see cref="Rational::operator%"/> instead.
/// </remarks>
Rational operator%(Rational lhs, Rational const& rhs) Rational operator%(Rational lhs, Rational const& rhs)
{ {
lhs %= rhs; lhs %= rhs;

View File

@ -387,3 +387,33 @@ Rational RationalMath::ATanh(Rational const& rat)
return result; return result;
} }
/// <summary>
/// Calculate the modulus after division, the sign of the result will match the sign of b.
/// </summary>
/// <remarks>
/// When one of the operand is negative
/// the result will differ from the C/C++ operator '%'
/// use <see cref="Rational::operator%"/> instead to calculate the remainder after division.
/// </remarks>
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;
}

View File

@ -78,7 +78,7 @@ CalcEngine::Rational CCalcEngine::DoOperation(int operation, CalcEngine::Rationa
case IDC_DIV: case IDC_DIV:
case IDC_MOD: case IDC_MOD:
{ {
int iNumeratorSign = 1, iDenominatorSign = 1, iFinalSign = 1; int iNumeratorSign = 1, iDenominatorSign = 1;
auto temp = result; auto temp = result;
result = rhs; result = rhs;
@ -107,20 +107,30 @@ CalcEngine::Rational CCalcEngine::DoOperation(int operation, CalcEngine::Rationa
if (operation == IDC_DIV) if (operation == IDC_DIV)
{ {
iFinalSign = iNumeratorSign * iDenominatorSign;
result /= temp; result /= temp;
} if (m_fIntegerMode && (iNumeratorSign * iDenominatorSign) == -1)
else
{
iFinalSign = iNumeratorSign;
result %= temp;
}
if (m_fIntegerMode && iFinalSign == -1)
{ {
result = -(Integer(result)); result = -(Integer(result));
} }
}
else
{
if (m_fIntegerMode)
{
// Programmer mode, use remrat (remainder after division)
result %= temp;
if (iNumeratorSign == -1)
{
result = -(Integer(result));
}
}
else
{
//other modes, use modrat (modulus after division)
result = Mod(result, temp);
}
}
break; break;
} }

View File

@ -45,7 +45,7 @@ namespace CalculationManager
class IResourceProvider; class IResourceProvider;
} }
namespace CalculatorUnitTests namespace CalculatorEngineTests
{ {
class CalcEngineTests; class CalcEngineTests;
} }
@ -160,5 +160,5 @@ private:
static void ChangeBaseConstants(uint32_t radix, int maxIntDigits, int32_t precision); static void ChangeBaseConstants(uint32_t radix, int maxIntDigits, int32_t precision);
void BaseOrPrecisionChanged(); void BaseOrPrecisionChanged();
friend class CalculatorUnitTests::CalcEngineTests; friend class CalculatorEngineTests::CalcEngineTests;
}; };

View File

@ -13,6 +13,7 @@ namespace CalcEngine::RationalMath
Rational Pow(Rational const& base, Rational const& pow); Rational Pow(Rational const& base, Rational const& pow);
Rational Root(Rational const& base, Rational const& root); Rational Root(Rational const& base, Rational const& root);
Rational Fact(Rational const& rat); Rational Fact(Rational const& rat);
Rational Mod(Rational const& a, Rational const& b);
Rational Exp(Rational const& rat); Rational Exp(Rational const& rat);
Rational Log(Rational const& rat); Rational Log(Rational const& rat);

View File

@ -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) 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 // Take the absolute value
(*px)->pp->sign = 1; (*px)->pp->sign = 1;

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved. // Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. // 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. // Check for negative integers and throw an error.
if ( ( zerrat(frac) || ( LOGRATRADIX(frac) <= -precision) ) && if ( ( zerrat(frac) || ( LOGRATRADIX(frac) <= -precision) ) &&
( (*px)->pp->sign * (*px)->pq->sign == -1 ) ) ( SIGN(*px) == -1 ) )
{ {
throw CALC_E_DOMAIN; throw CALC_E_DOMAIN;
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved. // Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. // 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) void asinrat( PRAT *px, uint32_t radix, int32_t precision)
{ {
int32_t sgn;
PRAT pret= nullptr; PRAT pret= nullptr;
PRAT phack= nullptr; PRAT phack= nullptr;
int32_t sgn = SIGN(*px);
sgn = (*px)->pp->sign* (*px)->pq->sign;
(*px)->pp->sign = 1; (*px)->pp->sign = 1;
(*px)->pq->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) void acosrat( PRAT *px, uint32_t radix, int32_t precision)
{ {
int32_t sgn; int32_t sgn = SIGN(*px);
sgn = (*px)->pp->sign*(*px)->pq->sign;
(*px)->pp->sign = 1; (*px)->pp->sign = 1;
(*px)->pq->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) void atanrat( PRAT *px, uint32_t radix, int32_t precision)
{ {
int32_t sgn;
PRAT tmpx= nullptr; PRAT tmpx= nullptr;
int32_t sgn = SIGN(*px);
sgn = (*px)->pp->sign * (*px)->pq->sign;
(*px)->pp->sign = 1; (*px)->pp->sign = 1;
(*px)->pq->sign = 1; (*px)->pq->sign = 1;

View File

@ -185,25 +185,27 @@ void boolnum( PNUMBER *pa, PNUMBER b, int func )
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// //
// FUNCTION: modrat // FUNCTION: remrat
// //
// ARGUMENTS: pointer to a rational a second rational. // ARGUMENTS: pointer to a rational a second rational.
// //
// RETURN: None, changes pointer. // RETURN: None, changes pointer.
// //
// DESCRIPTION: Does the rational equivalent of frac(*pa); // 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 modrat( PRAT *pa, PRAT b ) void remrat(PRAT *pa, PRAT b)
{ {
PRAT tmp = nullptr;
if (zerrat(b)) if (zerrat(b))
{ {
throw CALC_E_INDEFINITE; throw CALC_E_INDEFINITE;
} }
PRAT tmp = nullptr;
DUPRAT(tmp, b); DUPRAT(tmp, b);
mulnumx(&((*pa)->pp), tmp->pq); mulnumx(&((*pa)->pp), tmp->pq);
@ -216,3 +218,47 @@ void modrat( PRAT *pa, PRAT b )
destroyrat(tmp); destroyrat(tmp);
} }
//-----------------------------------------------------------------------------
//
// FUNCTION: modrat
//
// ARGUMENTS: pointer to a rational a second rational.
//
// RETURN: None, changes pointer.
//
// 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)
{
//contrary to remrat(X, 0) returning 0, modrat(X, 0) must return X
if (zerrat(b))
{
return;
}
PRAT tmp = nullptr;
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);
if (needAdjust && !zerrat(*pa))
{
addrat(pa, b, BASEX);
}
// Get *pa back in the integer over integer form.
RENORMALIZE(*pa);
destroyrat(tmp);
}

View File

@ -148,6 +148,9 @@ extern PRAT rat_min_i32;
#define LOGNUM2(pnum) ((pnum)->cdigit+(pnum)->exp) #define LOGNUM2(pnum) ((pnum)->cdigit+(pnum)->exp)
#define LOGRAT2(prat) (LOGNUM2((prat)->pp)-LOGNUM2((prat)->pq)) #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 ) #if defined( DEBUG_RATPAK )
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// //
@ -423,6 +426,7 @@ 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 divrat( _Inout_ PRAT *pa, _In_ PRAT b, int32_t precision);
extern void fracrat( _Inout_ PRAT *pa , uint32_t radix, 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 factrat( _Inout_ PRAT *pa, uint32_t radix, int32_t precision);
extern void remrat(_Inout_ PRAT *pa, _In_ PRAT b);
extern void modrat(_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 gcdrat( _Inout_ PRAT *pa, int32_t precision);
extern void intrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision); extern void intrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved. // Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. // 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 // Subtract the fractional part of the rational
PRAT pret = nullptr; PRAT pret = nullptr;
DUPRAT(pret,*px); DUPRAT(pret,*px);
modrat( &pret, rat_one ); remrat( &pret, rat_one );
subrat( px, pret, precision); subrat( px, pret, precision);
destroyrat( pret ); destroyrat( pret );
@ -348,8 +348,7 @@ bool rat_ge( PRAT a, PRAT b, int32_t precision)
b->pp->sign *= -1; b->pp->sign *= -1;
addrat( &rattmp, b, precision); addrat( &rattmp, b, precision);
b->pp->sign *= -1; b->pp->sign *= -1;
bool bret = ( zernum( rattmp->pp ) || bool bret = ( zernum( rattmp->pp ) || SIGN(rattmp) == 1 );
rattmp->pp->sign * rattmp->pq->sign == 1 );
destroyrat( rattmp ); destroyrat( rattmp );
return( bret ); return( bret );
} }
@ -374,8 +373,7 @@ bool rat_gt( PRAT a, PRAT b, int32_t precision)
b->pp->sign *= -1; b->pp->sign *= -1;
addrat( &rattmp, b, precision); addrat( &rattmp, b, precision);
b->pp->sign *= -1; b->pp->sign *= -1;
bool bret = ( !zernum( rattmp->pp ) && bool bret = ( !zernum( rattmp->pp ) && SIGN(rattmp) == 1 );
rattmp->pp->sign * rattmp->pq->sign == 1 );
destroyrat( rattmp ); destroyrat( rattmp );
return( bret ); return( bret );
} }
@ -400,8 +398,7 @@ bool rat_le( PRAT a, PRAT b, int32_t precision)
b->pp->sign *= -1; b->pp->sign *= -1;
addrat( &rattmp, b, precision); addrat( &rattmp, b, precision);
b->pp->sign *= -1; b->pp->sign *= -1;
bool bret = ( zernum( rattmp->pp ) || bool bret = ( zernum( rattmp->pp ) || SIGN(rattmp) == -1 );
rattmp->pp->sign * rattmp->pq->sign == -1 );
destroyrat( rattmp ); destroyrat( rattmp );
return( bret ); return( bret );
} }
@ -426,8 +423,7 @@ bool rat_lt( PRAT a, PRAT b, int32_t precision)
b->pp->sign *= -1; b->pp->sign *= -1;
addrat( &rattmp, b, precision); addrat( &rattmp, b, precision);
b->pp->sign *= -1; b->pp->sign *= -1;
bool bret = ( !zernum( rattmp->pp ) && bool bret = ( !zernum( rattmp->pp ) && SIGN(rattmp) == -1 );
rattmp->pp->sign * rattmp->pq->sign == -1 );
destroyrat( rattmp ); destroyrat( rattmp );
return( bret ); return( bret );
} }

View File

@ -13,7 +13,7 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework;
static constexpr size_t MAX_HISTORY_SIZE = 20; static constexpr size_t MAX_HISTORY_SIZE = 20;
namespace CalculatorUnitTests namespace CalculatorEngineTests
{ {
TEST_CLASS(CalcEngineTests) TEST_CLASS(CalcEngineTests)
{ {

View File

@ -8,7 +8,7 @@ using namespace std;
using namespace CalculationManager; using namespace CalculationManager;
using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace CalculatorUnitTests namespace CalculatorEngineTests
{ {
TEST_CLASS(CalcInputTest) TEST_CLASS(CalcInputTest)
{ {

View File

@ -248,6 +248,7 @@
<ClCompile Include="Module.cpp" /> <ClCompile Include="Module.cpp" />
<ClCompile Include="MultiWindowUnitTests.cpp" /> <ClCompile Include="MultiWindowUnitTests.cpp" />
<ClCompile Include="NavCategoryUnitTests.cpp" /> <ClCompile Include="NavCategoryUnitTests.cpp" />
<ClCompile Include="RationalTest.cpp" />
<ClCompile Include="StandardViewModelUnitTests.cpp" /> <ClCompile Include="StandardViewModelUnitTests.cpp" />
<ClCompile Include="UnitConverterTest.cpp" /> <ClCompile Include="UnitConverterTest.cpp" />
<ClCompile Include="UnitConverterViewModelUnitTests.cpp" /> <ClCompile Include="UnitConverterViewModelUnitTests.cpp" />

View File

@ -29,6 +29,7 @@
<ClCompile Include="Mocks\CurrencyHttpClient.cpp"> <ClCompile Include="Mocks\CurrencyHttpClient.cpp">
<Filter>Mocks</Filter> <Filter>Mocks</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="RationalTest.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="AsyncHelper.h" /> <ClInclude Include="AsyncHelper.h" />

View File

@ -0,0 +1,225 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
#include <CppUnitTest.h>
#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");
}
};
}