Add unit tests + fix issue when a mod b == 0 with b negative

This commit is contained in:
Rudy Huyn 2019-03-31 16:53:15 -07:00
commit 4908efcb96
8 changed files with 195 additions and 84 deletions

View file

@ -182,6 +182,12 @@ namespace CalcEngine
return *this; return *this;
} }
/// <summary>
/// Calculate the remainder after division, the sign of the result will match the sign of the current object.
/// </summary>
/// <remarks>
/// This function has the same behavior than the standard C/C++ operator '%', to calculate the modulus after division instead, use <see cref="Rational::operator%"/> instead.
/// </remarks>
Rational& Rational::operator%=(Rational const& rhs) Rational& Rational::operator%=(Rational const& rhs)
{ {
PRAT lhsRat = this->ToPRAT(); PRAT lhsRat = this->ToPRAT();
@ -342,6 +348,12 @@ namespace CalcEngine
return lhs; return lhs;
} }
/// <summary>
/// Calculate the remainder after division, the sign of the result will match the sign of a.
/// </summary>
/// <remarks>
/// This function has the same behavior than 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

@ -388,11 +388,16 @@ Rational RationalMath::ATanh(Rational const& rat)
return result; return result;
} }
/// <summary>
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.
/// </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 = base.ToPRAT(); PRAT prat = a.ToPRAT();
PRAT pn = n.ToPRAT(); PRAT pn = b.ToPRAT();
try try
{ {

View file

@ -45,7 +45,7 @@ namespace CalculationManager
class IResourceProvider; class IResourceProvider;
} }
namespace CalculatorUnitTests namespace CalculatorEngineTests
{ {
class CalcEngineTests; class CalcEngineTests;
} }
@ -159,5 +159,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,7 +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& base, Rational const& n); 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

@ -18,54 +18,54 @@
using namespace std; 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; int32_t intb;
intrat(pa, radix, precision); intrat(pa, radix, precision);
if ( !zernum( (*pa)->pp ) ) if (!zernum((*pa)->pp))
{ {
// If input is zero we're done. // 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 // Don't attempt lsh of anything big
throw( CALC_E_DOMAIN ); throw(CALC_E_DOMAIN);
} }
intb = rattoi32(b, radix, precision); intb = rattoi32(b, radix, precision);
DUPRAT(pwr,rat_two); DUPRAT(pwr, rat_two);
ratpowi32(&pwr, intb, precision); ratpowi32(&pwr, intb, precision);
mulrat(pa, pwr, precision); mulrat(pa, pwr, precision);
destroyrat(pwr); 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; int32_t intb;
intrat(pa, radix, precision); intrat(pa, radix, precision);
if ( !zernum( (*pa)->pp ) ) if (!zernum((*pa)->pp))
{ {
// If input is zero we're done. // 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. // Don't attempt rsh of anything big and negative.
throw( CALC_E_DOMAIN ); throw(CALC_E_DOMAIN);
} }
intb = rattoi32(b, radix, precision); intb = rattoi32(b, radix, precision);
DUPRAT(pwr,rat_two); DUPRAT(pwr, rat_two);
ratpowi32(&pwr, intb, precision); ratpowi32(&pwr, intb, precision);
divrat(pa, pwr, precision); divrat(pa, pwr, precision);
destroyrat(pwr); destroyrat(pwr);
} }
} }
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);
void boolnum( PNUMBER *pa, PNUMBER b, int func ); void boolnum(PNUMBER *pa, PNUMBER b, int func);
enum { enum {
@ -74,22 +74,22 @@ enum {
FUNC_XOR FUNC_XOR
} BOOL_FUNCS; } 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; PRAT tmp = nullptr;
intrat( pa, radix, precision); intrat(pa, radix, precision);
DUPRAT(tmp,b); DUPRAT(tmp, b);
intrat( &tmp, radix, precision); intrat(&tmp, radix, precision);
boolnum( &((*pa)->pp), tmp->pp, func ); boolnum(&((*pa)->pp), tmp->pp, func);
destroyrat(tmp); 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 c = nullptr;
PNUMBER a= nullptr; PNUMBER a = nullptr;
MANTTYPE *pcha; MANTTYPE *pcha;
MANTTYPE *pchb; MANTTYPE *pchb;
MANTTYPE *pchc; MANTTYPE *pchc;
@ -143,25 +143,25 @@ void boolnum( PNUMBER *pa, PNUMBER b, int func )
MANTTYPE da; MANTTYPE da;
MANTTYPE db; MANTTYPE db;
a=*pa; a = *pa;
cdigits = max( a->cdigit+a->exp, b->cdigit+b->exp ) - cdigits = max(a->cdigit + a->exp, b->cdigit + b->exp) -
min( a->exp, b->exp ); min(a->exp, b->exp);
createnum( c, cdigits ); createnum(c, cdigits);
c->exp = min( a->exp, b->exp ); c->exp = min(a->exp, b->exp);
mexp = c->exp; mexp = c->exp;
c->cdigit = cdigits; c->cdigit = cdigits;
pcha = a->mant; pcha = a->mant;
pchb = b->mant; pchb = b->mant;
pchc = c->mant; pchc = c->mant;
for ( ;cdigits > 0; cdigits--, mexp++ ) for (; cdigits > 0; cdigits--, mexp++)
{ {
da = ( ( ( mexp >= a->exp ) && ( cdigits + a->exp - c->exp > da = (((mexp >= a->exp) && (cdigits + a->exp - c->exp >
(c->cdigit - a->cdigit) ) ) ? (c->cdigit - a->cdigit))) ?
*pcha++ : 0 ); *pcha++ : 0);
db = ( ( ( mexp >= b->exp ) && ( cdigits + b->exp - c->exp > db = (((mexp >= b->exp) && (cdigits + b->exp - c->exp >
(c->cdigit - b->cdigit) ) ) ? (c->cdigit - b->cdigit))) ?
*pchb++ : 0 ); *pchb++ : 0);
switch ( func ) switch (func)
{ {
case FUNC_AND: case FUNC_AND:
*pchc++ = da & db; *pchc++ = da & db;
@ -175,12 +175,12 @@ void boolnum( PNUMBER *pa, PNUMBER b, int func )
} }
} }
c->sign = a->sign; c->sign = a->sign;
while ( c->cdigit > 1 && *(--pchc) == 0 ) while (c->cdigit > 1 && *(--pchc) == 0)
{ {
c->cdigit--; c->cdigit--;
} }
destroynum( *pa ); destroynum(*pa);
*pa=c; *pa = c;
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -191,8 +191,9 @@ void boolnum( PNUMBER *pa, PNUMBER b, int func )
// //
// RETURN: None, changes pointer. // RETURN: None, changes pointer.
// //
// DESCRIPTION: Calculate the remainder of *pa / b, equivalent of 'pa % b' in C; // DESCRIPTION: Calculate the remainder of *pa / b,
// NOTE: produces a result that is either zero or has the same sign as the dividend. // 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. // RETURN: None, changes pointer.
// //
// DESCRIPTION: Calculate the remainder of *pa / b, equivalent of 'pa modulo b' in arithmetic // DESCRIPTION: Calculate the remainder of *pa / b, with the sign of the result
// NOTE: produces a result that is either zero or has the same sign as the divisor. // 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); remnum(&((*pa)->pp), tmp->pp, BASEX);
mulnumx(&((*pa)->pq), tmp->pq); mulnumx(&((*pa)->pq), tmp->pq);
if (needAdjust) if (needAdjust && !zerrat(*pa))
{ {
addrat(pa, b, BASEX); addrat(pa, b, BASEX);
} }

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

@ -10,7 +10,7 @@ using namespace CalcEngine;
using namespace CalcEngine::RationalMath; using namespace CalcEngine::RationalMath;
using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace CalculatorManagerTest namespace CalculatorEngineTests
{ {
TEST_CLASS(RationalTest) TEST_CLASS(RationalTest)
{ {
@ -44,10 +44,44 @@ namespace CalculatorManagerTest
VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-8113"); VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-8113");
res = Mod(Rational(-643), Rational(-8756)); res = Mod(Rational(-643), Rational(-8756));
VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-643"); 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 //Test with Zero
res = Mod(Rational(343654332), Rational(0)); res = Mod(Rational(343654332), Rational(0));
VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"343654332"); 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) TEST_METHOD(RemainderTest)
@ -76,10 +110,39 @@ namespace CalculatorManagerTest
res = Rational(-643) % Rational(-8756); res = Rational(-643) % Rational(-8756);
VERIFY_ARE_EQUAL(res.ToString(10, FMT_FLOAT, 128), L"-643"); 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 //Test with Zero
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 })
{
try try
{ {
res = Rational(343654332) % Rational(0); 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(); Assert::Fail();
} }
catch (uint32_t t) catch (uint32_t t)
@ -94,5 +157,33 @@ namespace CalculatorManagerTest
Assert::Fail(); 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");
}
}; };
} }