// 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((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; }