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;
}
/// <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)
{
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;
}
/// <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)
{
lhs %= rhs;

View File

@@ -387,3 +387,33 @@ Rational RationalMath::ATanh(Rational const& rat)
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_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;
}