318 lines
8.8 KiB
C++
318 lines
8.8 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
#include "pch.h"
|
|
#include "Header Files/CalcEngine.h"
|
|
|
|
using namespace std;
|
|
using namespace CalcEngine;
|
|
|
|
constexpr int C_NUM_MAX_DIGITS = MAX_STRLEN;
|
|
constexpr int C_EXP_MAX_DIGITS = 4;
|
|
|
|
void CalcNumSec::Clear()
|
|
{
|
|
value.clear();
|
|
m_isNegative = false;
|
|
}
|
|
|
|
void CalcInput::Clear()
|
|
{
|
|
m_base.Clear();
|
|
m_exponent.Clear();
|
|
m_hasExponent = false;
|
|
m_hasDecimal = false;
|
|
m_decPtIndex = 0;
|
|
}
|
|
|
|
bool CalcInput::TryToggleSign(bool isIntegerMode, wstring_view maxNumStr)
|
|
{
|
|
// Zero is always positive
|
|
if (m_base.IsEmpty())
|
|
{
|
|
m_base.IsNegative(false);
|
|
m_exponent.IsNegative(false);
|
|
}
|
|
else if (m_hasExponent)
|
|
{
|
|
m_exponent.IsNegative(!m_exponent.IsNegative());
|
|
}
|
|
else
|
|
{
|
|
// When in integer only mode, it isn't always allowed to toggle, as toggling can cause the num to be out of
|
|
// bounds. For eg. in byte -128 is valid, but when it toggled it becomes 128, which is more than 127.
|
|
if (isIntegerMode && m_base.IsNegative())
|
|
{
|
|
// Decide if this additional digit will fit for the given bit width
|
|
if (m_base.value.size() >= maxNumStr.size() && m_base.value.back() > maxNumStr.back())
|
|
{
|
|
// Last digit is more than the allowed positive number. Fail
|
|
return false;
|
|
}
|
|
}
|
|
m_base.IsNegative(!m_base.IsNegative());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CalcInput::TryAddDigit(unsigned int value, uint32_t radix, bool isIntegerMode, wstring_view maxNumStr, long wordBitWidth, int maxDigits)
|
|
{
|
|
// Convert from an integer into a character
|
|
// This includes both normal digits and alpha 'digits' for radixes > 10
|
|
auto chDigit = static_cast<wchar_t>((value < 10) ? (L'0' + value) : (L'A' + value - 10));
|
|
|
|
CalcNumSec* pNumSec;
|
|
size_t maxCount;
|
|
if (m_hasExponent)
|
|
{
|
|
pNumSec = &m_exponent;
|
|
maxCount = C_EXP_MAX_DIGITS;
|
|
}
|
|
else
|
|
{
|
|
pNumSec = &m_base;
|
|
maxCount = maxDigits;
|
|
// Don't include the decimal point in the count. In that way you can enter the maximum allowed precision.
|
|
// Precision doesn't include decimal point.
|
|
if (HasDecimalPt())
|
|
{
|
|
maxCount++;
|
|
}
|
|
// First leading 0 is not counted in input restriction as the output can be of that form
|
|
// See NumberToString algorithm. REVIEW: We don't have such input restriction mimicking based on output of NumberToString for exponent
|
|
// NumberToString can give 10 digit exponent, but we still restrict the exponent here to be only 4 digits.
|
|
if (!pNumSec->IsEmpty() && pNumSec->value.front() == L'0')
|
|
{
|
|
maxCount++;
|
|
}
|
|
}
|
|
|
|
// Ignore leading zeros
|
|
if (pNumSec->IsEmpty() && (value == 0))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (pNumSec->value.size() < maxCount)
|
|
{
|
|
pNumSec->value += chDigit;
|
|
return true;
|
|
}
|
|
|
|
// if we are in integer mode, within the base, and we're on the last digit then
|
|
// there are special cases where we can actually add one more digit.
|
|
if (isIntegerMode && pNumSec->value.size() == maxCount && !m_hasExponent)
|
|
{
|
|
bool allowExtraDigit = false;
|
|
|
|
if (radix == 8)
|
|
{
|
|
switch (wordBitWidth % 3)
|
|
{
|
|
case 1:
|
|
// in 16 or 64bit word size, if the first digit is a 1 we can enter 6 (16bit) or 22 (64bit) digits
|
|
allowExtraDigit = (pNumSec->value.front() == L'1');
|
|
break;
|
|
|
|
case 2:
|
|
// in 8 or 32bit word size, if the first digit is a 3 or less we can enter 3 (8bit) or 11 (32bit) digits
|
|
allowExtraDigit = (pNumSec->value.front() <= L'3');
|
|
break;
|
|
}
|
|
}
|
|
else if (radix == 10)
|
|
{
|
|
// If value length is at least the max, we know we can't add another digit.
|
|
if(pNumSec->value.size() < maxNumStr.size())
|
|
{
|
|
// Compare value to substring of maxNumStr of value.size() length.
|
|
// If cmpResult > 0:
|
|
// eg. max is "127", and the current number is "20". first digit itself says we are out.
|
|
// Additional digit is not possible
|
|
|
|
// If cmpResult < 0:
|
|
// Success case. eg. max is "127", and current number is say "11". The second digit '1' being <
|
|
// corresponding digit '2', means all digits are possible to append, like 119 will still be < 127
|
|
|
|
// If cmpResult == 0:
|
|
// Undecided still. The case when max is "127", and current number is "12". Look for the new number being 7 or less to allow
|
|
auto cmpResult = pNumSec->value.compare(0, wstring::npos, maxNumStr, 0, pNumSec->value.size());
|
|
if (cmpResult < 0)
|
|
{
|
|
allowExtraDigit = true;
|
|
}
|
|
else if (cmpResult == 0)
|
|
{
|
|
auto lastChar = maxNumStr[pNumSec->value.size()];
|
|
if (chDigit <= lastChar)
|
|
{
|
|
allowExtraDigit = true;
|
|
}
|
|
else if (pNumSec->IsNegative() && chDigit <= lastChar + 1)
|
|
{
|
|
// Negative value case, eg. max is "127", and current number is "-12". Then 8 is also valid, as the range
|
|
// is always from -(max+1)...max in signed mode
|
|
allowExtraDigit = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (allowExtraDigit)
|
|
{
|
|
pNumSec->value += chDigit;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CalcInput::TryAddDecimalPt()
|
|
{
|
|
// Already have a decimal pt or we're in the exponent
|
|
if (m_hasDecimal || m_hasExponent)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_base.IsEmpty())
|
|
{
|
|
m_base.value += L"0"; // Add a leading zero
|
|
}
|
|
|
|
m_decPtIndex = m_base.value.size();
|
|
m_base.value += m_decSymbol;
|
|
m_hasDecimal = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CalcInput::HasDecimalPt()
|
|
{
|
|
return m_hasDecimal;
|
|
}
|
|
|
|
bool CalcInput::TryBeginExponent()
|
|
{
|
|
// For compatibility, add a trailing dec point to base num if it doesn't have one
|
|
TryAddDecimalPt();
|
|
|
|
if (m_hasExponent) // Already entering exponent
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_hasExponent = true; // Entering exponent
|
|
return true;
|
|
}
|
|
|
|
void CalcInput::Backspace()
|
|
{
|
|
if (m_hasExponent)
|
|
{
|
|
if (!m_exponent.IsEmpty())
|
|
{
|
|
m_exponent.value.pop_back();
|
|
|
|
if (m_exponent.IsEmpty())
|
|
{
|
|
m_exponent.Clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_hasExponent = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!m_base.IsEmpty())
|
|
{
|
|
m_base.value.pop_back();
|
|
}
|
|
|
|
if (m_base.value.size() <= m_decPtIndex)
|
|
{
|
|
// Backed up over decimal point
|
|
m_hasDecimal = false;
|
|
m_decPtIndex = 0;
|
|
}
|
|
|
|
if (m_base.IsEmpty())
|
|
{
|
|
m_base.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CalcInput::SetDecimalSymbol(wchar_t decSymbol)
|
|
{
|
|
if (m_decSymbol != decSymbol)
|
|
{
|
|
m_decSymbol = decSymbol;
|
|
|
|
if (m_hasDecimal)
|
|
{
|
|
// Change to new decimal pt
|
|
m_base.value[m_decPtIndex] = m_decSymbol;
|
|
}
|
|
}
|
|
}
|
|
|
|
wstring CalcInput::ToString(uint32_t radix, bool isIntegerMode)
|
|
{
|
|
// In theory both the base and exponent could be C_NUM_MAX_DIGITS long.
|
|
wstringstream resStream;
|
|
|
|
if ((m_base.value.size() > MAX_STRLEN) || (m_hasExponent && m_exponent.value.size() > MAX_STRLEN))
|
|
{
|
|
return wstring();
|
|
}
|
|
|
|
if (m_base.IsNegative())
|
|
{
|
|
resStream << L'-';
|
|
}
|
|
|
|
resStream << (m_base.IsEmpty() ? L"0" : m_base.value);
|
|
|
|
if (m_hasExponent)
|
|
{
|
|
// Add a decimal point if it is not already there
|
|
if (!m_hasDecimal)
|
|
{
|
|
resStream << m_decSymbol;
|
|
}
|
|
|
|
resStream << ((radix == 10) ? L'e' : L'^');
|
|
resStream << (m_exponent.IsNegative() ? L'-' : L'+');
|
|
resStream << (m_exponent.IsEmpty() ? L"0" : m_exponent.value);
|
|
}
|
|
|
|
auto result = resStream.str();
|
|
|
|
// Base and Exp can each be up to C_NUM_MAX_DIGITS in length, plus 4 characters for sign, dec, exp, and expSign.
|
|
if (result.size() > C_NUM_MAX_DIGITS * 2 + 4)
|
|
{
|
|
return wstring();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Rational CalcInput::ToRational(uint32_t radix, int32_t precision)
|
|
{
|
|
PRAT rat = StringToRat(m_base.IsNegative(), m_base.value, m_exponent.IsNegative(), m_exponent.value, radix, precision);
|
|
if (rat == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Rational result{ rat };
|
|
destroyrat(rat);
|
|
|
|
return result;
|
|
}
|