calculator/src/CalcManager/CEngine/CalcInput.cpp
Howard Wolosky c13b8a099e Hello GitHub
2019-01-28 16:24:37 -08:00

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 isnt 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 radices > 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 doesnt 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 dont 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 Rational{};
}
Rational result{ rat };
destroyrat(rat);
return result;
}