Hello GitHub
This commit is contained in:
317
src/CalcManager/CEngine/CalcInput.cpp
Normal file
317
src/CalcManager/CEngine/CalcInput.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
// 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;
|
||||
}
|
58
src/CalcManager/CEngine/CalcUtils.cpp
Normal file
58
src/CalcManager/CEngine/CalcUtils.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Header Files/CalcEngine.h"
|
||||
|
||||
bool IsOpInRange(WPARAM op, uint32_t x, uint32_t y)
|
||||
{
|
||||
return ((op >= x) && (op <= y));
|
||||
}
|
||||
|
||||
bool IsBinOpCode(WPARAM opCode)
|
||||
{
|
||||
return IsOpInRange(opCode, IDC_AND, IDC_PWR);
|
||||
}
|
||||
|
||||
// WARNING: IDC_SIGN is a special unary op but still this doesnt catch this. Caller has to be aware
|
||||
// of it and catch it themself or not needing this
|
||||
bool IsUnaryOpCode(WPARAM opCode)
|
||||
{
|
||||
return IsOpInRange(opCode, IDC_UNARYFIRST, IDC_UNARYLAST);
|
||||
}
|
||||
|
||||
bool IsDigitOpCode(WPARAM opCode)
|
||||
{
|
||||
return IsOpInRange(opCode, IDC_0, IDC_F);
|
||||
}
|
||||
|
||||
// Some commands are not affecting the state machine state of the calc flow. But these are more of
|
||||
// some gui mode kind of settings (eg Inv button, or Deg,Rad , Back etc.). This list is getting bigger & bigger
|
||||
// so we abstract this as a separate routine. Note: There is another side to this. Some commands are not
|
||||
// gui mode setting to begin with, but once it is discovered it is invalid and we want to behave as though it
|
||||
// was never inout, we need to revert the state changes made as a result of this test
|
||||
bool IsGuiSettingOpCode(WPARAM opCode)
|
||||
{
|
||||
if (IsOpInRange(opCode, IDM_HEX, IDM_BIN) ||
|
||||
IsOpInRange(opCode, IDM_QWORD, IDM_BYTE) ||
|
||||
IsOpInRange(opCode, IDM_DEG, IDM_GRAD))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (opCode)
|
||||
{
|
||||
case IDC_INV:
|
||||
case IDC_FE:
|
||||
case IDC_MCLEAR:
|
||||
case IDC_BACK:
|
||||
case IDC_EXP:
|
||||
case IDC_STORE:
|
||||
case IDC_MPLUS:
|
||||
case IDC_MMINUS:
|
||||
return true;
|
||||
}
|
||||
|
||||
// most of the commands
|
||||
return false;
|
||||
}
|
490
src/CalcManager/CEngine/History.cpp
Normal file
490
src/CalcManager/CEngine/History.cpp
Normal file
@@ -0,0 +1,490 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#pragma once
|
||||
#include "Header Files/CalcEngine.h"
|
||||
#include "Command.h"
|
||||
#include "CalculatorVector.h"
|
||||
#include "ExpressionCommand.h"
|
||||
#include "CalcException.h"
|
||||
|
||||
constexpr int ASCII_0 = 48;
|
||||
|
||||
using namespace std;
|
||||
|
||||
void CHistoryCollector::ReinitHistory()
|
||||
{
|
||||
m_lastOpStartIndex = -1;
|
||||
m_lastBinOpStartIndex = -1;
|
||||
m_curOperandIndex = 0;
|
||||
m_bLastOpndBrace = false;
|
||||
if (m_spTokens != nullptr)
|
||||
{
|
||||
m_spTokens->Clear();
|
||||
}
|
||||
if (m_spCommands != nullptr)
|
||||
{
|
||||
m_spCommands->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor
|
||||
// Can throw Out of memory error
|
||||
CHistoryCollector::CHistoryCollector(ICalcDisplay *pCalcDisplay, std::shared_ptr<IHistoryDisplay> pHistoryDisplay, wchar_t decimalSymbol) :
|
||||
m_pHistoryDisplay(pHistoryDisplay),
|
||||
m_pCalcDisplay(pCalcDisplay),
|
||||
m_decimalSymbol(decimalSymbol),
|
||||
m_iCurLineHistStart(-1)
|
||||
{
|
||||
ReinitHistory();
|
||||
}
|
||||
|
||||
CHistoryCollector::~CHistoryCollector()
|
||||
{
|
||||
m_pHistoryDisplay = nullptr;
|
||||
m_pCalcDisplay = nullptr;
|
||||
|
||||
if (m_spTokens != nullptr)
|
||||
{
|
||||
m_spTokens->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void CHistoryCollector::AddOpndToHistory(wstring_view numStr, PRAT hNoNum, bool fRepetition)
|
||||
{
|
||||
std::shared_ptr<CalculatorVector<int>> commands = std::make_shared<CalculatorVector<int>>();
|
||||
// Check for negate
|
||||
bool fNegative = (numStr[0] == L'-');
|
||||
bool fSciFmt = false;
|
||||
bool fDecimal = false;
|
||||
|
||||
for (size_t i = (fNegative ? 1 : 0); i < numStr.length(); i++)
|
||||
{
|
||||
if (numStr[i] == m_decimalSymbol)
|
||||
{
|
||||
IFT(commands->Append(IDC_PNT));
|
||||
if (!fSciFmt)
|
||||
{
|
||||
fDecimal = true;
|
||||
}
|
||||
}
|
||||
else if (numStr[i] == L'e')
|
||||
{
|
||||
IFT(commands->Append(IDC_EXP));
|
||||
fSciFmt = true;
|
||||
}
|
||||
else if (numStr[i] == L'-')
|
||||
{
|
||||
IFT(commands->Append(IDC_SIGN));
|
||||
}
|
||||
else if (numStr[i] == L'+')
|
||||
{
|
||||
// Ignore.
|
||||
}
|
||||
// Number
|
||||
else
|
||||
{
|
||||
int num = static_cast<int>(numStr[i]) - ASCII_0;
|
||||
num += IDC_0;
|
||||
IFT(commands->Append(num));
|
||||
}
|
||||
}
|
||||
|
||||
auto operandCommand = std::make_shared<COpndCommand>(commands, fNegative, fDecimal, fSciFmt);
|
||||
operandCommand->Initialize(hNoNum);
|
||||
int iCommandEnd = AddCommand(operandCommand);
|
||||
m_lastOpStartIndex = IchAddSzToEquationSz(numStr, iCommandEnd);
|
||||
|
||||
if (fRepetition)
|
||||
{
|
||||
SetExpressionDisplay();
|
||||
}
|
||||
m_bLastOpndBrace = false;
|
||||
m_lastBinOpStartIndex = -1;
|
||||
}
|
||||
|
||||
void CHistoryCollector::RemoveLastOpndFromHistory()
|
||||
{
|
||||
TruncateEquationSzFromIch(m_lastOpStartIndex);
|
||||
SetExpressionDisplay();
|
||||
m_lastOpStartIndex = -1;
|
||||
// This will not restore the m_lastBinOpStartIndex, as it isnt possible to remove that also later
|
||||
}
|
||||
|
||||
void CHistoryCollector::AddBinOpToHistory(int nOpCode, bool fNoRepetition)
|
||||
{
|
||||
int iCommandEnd = AddCommand(std::make_shared<CBinaryCommand>(nOpCode));
|
||||
m_lastBinOpStartIndex = IchAddSzToEquationSz(L" ", -1);
|
||||
|
||||
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(nOpCode), iCommandEnd);
|
||||
IchAddSzToEquationSz(L" ", -1);
|
||||
|
||||
if (fNoRepetition)
|
||||
{
|
||||
SetExpressionDisplay();
|
||||
}
|
||||
m_lastOpStartIndex = -1;
|
||||
}
|
||||
|
||||
// This is expected to be called when a binary op in the last say 1+2+ is changing to another one say 1+2* (+ changed to *)
|
||||
// It needs to know by this change a Precedence inversion happenned. i.e. previous op was lower or equal to its previous op, but the new
|
||||
// one isn't. (Eg. 1*2* to 1*2^). It can add explicit brackets to ensure the precedence is inverted. (Eg. (1*2) ^)
|
||||
void CHistoryCollector::ChangeLastBinOp(int nOpCode, bool fPrecInvToHigher)
|
||||
{
|
||||
TruncateEquationSzFromIch(m_lastBinOpStartIndex);
|
||||
if (fPrecInvToHigher)
|
||||
{
|
||||
EnclosePrecInvertionBrackets();
|
||||
}
|
||||
AddBinOpToHistory(nOpCode);
|
||||
}
|
||||
|
||||
void CHistoryCollector::PushLastOpndStart(int ichOpndStart)
|
||||
{
|
||||
int ich = (ichOpndStart == -1) ? m_lastOpStartIndex : ichOpndStart;
|
||||
|
||||
if (m_curOperandIndex < static_cast<int>(m_operandIndices.size()))
|
||||
{
|
||||
m_operandIndices[m_curOperandIndex++] = ich;
|
||||
}
|
||||
}
|
||||
|
||||
void CHistoryCollector::PopLastOpndStart()
|
||||
{
|
||||
if (m_curOperandIndex > 0)
|
||||
{
|
||||
m_lastOpStartIndex = m_operandIndices[--m_curOperandIndex];
|
||||
}
|
||||
}
|
||||
|
||||
void CHistoryCollector::AddOpenBraceToHistory()
|
||||
{
|
||||
int iCommandEnd = AddCommand(std::make_shared<CParentheses>(IDC_OPENP));
|
||||
int ichOpndStart = IchAddSzToEquationSz(CCalcEngine::OpCodeToString(IDC_OPENP), -1);
|
||||
PushLastOpndStart(ichOpndStart);
|
||||
|
||||
SetExpressionDisplay();
|
||||
m_lastBinOpStartIndex = -1;
|
||||
}
|
||||
|
||||
void CHistoryCollector::AddCloseBraceToHistory()
|
||||
{
|
||||
int iCommandEnd = AddCommand(std::make_shared<CParentheses>(IDC_CLOSEP));
|
||||
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(IDC_CLOSEP), -1);
|
||||
SetExpressionDisplay();
|
||||
PopLastOpndStart();
|
||||
|
||||
m_lastBinOpStartIndex = -1;
|
||||
m_bLastOpndBrace = true;
|
||||
}
|
||||
|
||||
void CHistoryCollector::EnclosePrecInvertionBrackets()
|
||||
{
|
||||
// Top of the Opnd starts index or 0 is nothing is in top
|
||||
int ichStart = (m_curOperandIndex > 0) ? m_operandIndices[m_curOperandIndex - 1] : 0;
|
||||
|
||||
InsertSzInEquationSz(CCalcEngine::OpCodeToString(IDC_OPENP), -1, ichStart);
|
||||
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(IDC_CLOSEP), -1);
|
||||
}
|
||||
|
||||
bool CHistoryCollector::FOpndAddedToHistory()
|
||||
{
|
||||
return (-1 != m_lastOpStartIndex);
|
||||
}
|
||||
|
||||
// AddUnaryOpToHistory
|
||||
//
|
||||
// This is does the postfix to prefix transalation of the input and adds the text to the history. Eg. doing 2 + 4 (sqrt),
|
||||
// this routine will ensure the last sqrt call unary operator, actually goes back in history and wraps 4 in sqrt(4)
|
||||
//
|
||||
void CHistoryCollector::AddUnaryOpToHistory(int nOpCode, bool fInv, ANGLE_TYPE angletype)
|
||||
{
|
||||
int iCommandEnd;
|
||||
// When successfully applying a unary op, there should be an opnd already
|
||||
// A very special case of % which is a funny post op unary op.
|
||||
if (IDC_PERCENT == nOpCode)
|
||||
{
|
||||
iCommandEnd = AddCommand(std::make_shared<CUnaryCommand>(nOpCode));
|
||||
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(nOpCode), iCommandEnd);
|
||||
}
|
||||
else // all the other unary ops
|
||||
{
|
||||
std::shared_ptr<IOperatorCommand> spExpressionCommand;
|
||||
if (IDC_SIGN == nOpCode)
|
||||
{
|
||||
spExpressionCommand = std::make_shared<CUnaryCommand>(nOpCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
CalculationManager::Command angleOpCode;
|
||||
if (angletype == ANGLE_DEG)
|
||||
{
|
||||
angleOpCode = CalculationManager::Command::CommandDEG;
|
||||
}
|
||||
if (angletype == ANGLE_RAD)
|
||||
{
|
||||
angleOpCode = CalculationManager::Command::CommandRAD;
|
||||
}
|
||||
if (angletype == ANGLE_GRAD)
|
||||
{
|
||||
angleOpCode = CalculationManager::Command::CommandGRAD;
|
||||
}
|
||||
|
||||
int command = nOpCode;
|
||||
switch (nOpCode)
|
||||
{
|
||||
case IDC_SIN:
|
||||
command = fInv ? static_cast<int>(CalculationManager::Command::CommandASIN) : IDC_SIN;
|
||||
spExpressionCommand = std::make_shared<CUnaryCommand>(static_cast<int>(angleOpCode), command);
|
||||
break;
|
||||
case IDC_COS:
|
||||
command = fInv ? static_cast<int>(CalculationManager::Command::CommandACOS) : IDC_COS;
|
||||
spExpressionCommand = std::make_shared<CUnaryCommand>(static_cast<int>(angleOpCode), command);
|
||||
break;
|
||||
case IDC_TAN:
|
||||
command = fInv ? static_cast<int>(CalculationManager::Command::CommandATAN) : IDC_TAN;
|
||||
spExpressionCommand = std::make_shared<CUnaryCommand>(static_cast<int>(angleOpCode), command);
|
||||
break;
|
||||
case IDC_SINH:
|
||||
command = fInv ? static_cast<int>(CalculationManager::Command::CommandASINH) : IDC_SINH;
|
||||
spExpressionCommand = std::make_shared<CUnaryCommand>(command);
|
||||
break;
|
||||
case IDC_COSH:
|
||||
command = fInv ? static_cast<int>(CalculationManager::Command::CommandACOSH) : IDC_COSH;
|
||||
spExpressionCommand = std::make_shared<CUnaryCommand>(command);
|
||||
break;
|
||||
case IDC_TANH:
|
||||
command = fInv ? static_cast<int>(CalculationManager::Command::CommandATANH) : IDC_TANH;
|
||||
spExpressionCommand = std::make_shared<CUnaryCommand>(command);
|
||||
break;
|
||||
case IDC_LN:
|
||||
command = fInv ? static_cast<int>(CalculationManager::Command::CommandPOWE) : IDC_LN;
|
||||
spExpressionCommand = std::make_shared<CUnaryCommand>(command);
|
||||
break;
|
||||
default:
|
||||
spExpressionCommand = std::make_shared<CUnaryCommand>(nOpCode);
|
||||
}
|
||||
}
|
||||
|
||||
iCommandEnd = AddCommand(spExpressionCommand);
|
||||
|
||||
wstring operandStr{ CCalcEngine::OpCodeToUnaryString(nOpCode, fInv, angletype) };
|
||||
if (!m_bLastOpndBrace) // The opnd is already covered in braces. No need for additional braces around it
|
||||
{
|
||||
operandStr.append(CCalcEngine::OpCodeToString(IDC_OPENP));
|
||||
}
|
||||
InsertSzInEquationSz(operandStr, iCommandEnd, m_lastOpStartIndex);
|
||||
|
||||
if (!m_bLastOpndBrace)
|
||||
{
|
||||
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(IDC_CLOSEP), -1);
|
||||
}
|
||||
}
|
||||
|
||||
SetExpressionDisplay();
|
||||
m_bLastOpndBrace = false;
|
||||
// m_lastOpStartIndex remains the same as last opnd is just replaced by unaryop(lastopnd)
|
||||
m_lastBinOpStartIndex = -1;
|
||||
}
|
||||
|
||||
// Called after = with the result of the equation
|
||||
// Responsible for clearing the top line of current running history display, as well as adding yet another element to
|
||||
// history of equations
|
||||
void CHistoryCollector::CompleteHistoryLine(wstring_view numStr)
|
||||
{
|
||||
if (nullptr != m_pCalcDisplay)
|
||||
{
|
||||
m_pCalcDisplay->SetExpressionDisplay(std::make_shared<CalculatorVector<std::pair<std::wstring, int>>>(), std::make_shared<CalculatorVector<std::shared_ptr<IExpressionCommand>>>());
|
||||
}
|
||||
|
||||
if (nullptr != m_pHistoryDisplay)
|
||||
{
|
||||
unsigned int addedItemIndex = m_pHistoryDisplay->AddToHistory(m_spTokens, m_spCommands, numStr);
|
||||
m_pCalcDisplay->OnHistoryItemAdded(addedItemIndex);
|
||||
}
|
||||
|
||||
m_spTokens = nullptr;
|
||||
m_spCommands = nullptr;
|
||||
m_iCurLineHistStart = -1; // It will get recomputed at the first Opnd
|
||||
ReinitHistory();
|
||||
}
|
||||
|
||||
void CHistoryCollector::ClearHistoryLine(wstring_view errStr)
|
||||
{
|
||||
if (errStr.empty()) // in case of error let the display stay as it is
|
||||
{
|
||||
if (nullptr != m_pCalcDisplay)
|
||||
{
|
||||
m_pCalcDisplay->SetExpressionDisplay(std::make_shared<CalculatorVector<std::pair<std::wstring, int>>>(), std::make_shared<CalculatorVector<std::shared_ptr<IExpressionCommand>>>());
|
||||
}
|
||||
m_iCurLineHistStart = -1; // It will get recomputed at the first Opnd
|
||||
ReinitHistory();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Adds the given string psz to the globally maintained current equation string at the end.
|
||||
// Also returns the 0 based index in the string just added. Can throw out of memory error
|
||||
int CHistoryCollector::IchAddSzToEquationSz(wstring_view str, int icommandIndex)
|
||||
{
|
||||
if (m_spTokens == nullptr)
|
||||
{
|
||||
m_spTokens = std::make_shared<CalculatorVector<std::pair<std::wstring, int>>>();
|
||||
}
|
||||
|
||||
if (FAILED(m_spTokens->Append(std::make_pair(wstring(str), icommandIndex))))
|
||||
{
|
||||
throw(CALC_E_OUTOFMEMORY);
|
||||
}
|
||||
|
||||
unsigned int nTokens;
|
||||
m_spTokens->GetSize(&nTokens);
|
||||
return nTokens - 1;
|
||||
}
|
||||
|
||||
// Inserts a given string into the global m_pszEquation at the given index ich taking care of reallocations etc.
|
||||
void CHistoryCollector::InsertSzInEquationSz(wstring_view str, int icommandIndex, int ich)
|
||||
{
|
||||
if (FAILED(m_spTokens->InsertAt(ich, std::make_pair(wstring(str), icommandIndex))))
|
||||
{
|
||||
throw(CALC_E_OUTOFMEMORY);
|
||||
}
|
||||
}
|
||||
|
||||
// Chops off the current equation string from the given index
|
||||
void CHistoryCollector::TruncateEquationSzFromIch(int ich)
|
||||
{
|
||||
// Truncate commands
|
||||
int minIdx = -1;
|
||||
unsigned int nTokens = 0;
|
||||
std::pair<std::wstring, int> currentPair;
|
||||
m_spTokens->GetSize(&nTokens);
|
||||
|
||||
for (unsigned int i = ich; i < nTokens; i++)
|
||||
{
|
||||
IFT(m_spTokens->GetAt(i, ¤tPair));
|
||||
int curTokenId = currentPair.second;
|
||||
if (curTokenId != -1)
|
||||
{
|
||||
if ((minIdx != -1) || (curTokenId < minIdx))
|
||||
{
|
||||
minIdx = curTokenId;
|
||||
IFT(m_spCommands->Truncate(minIdx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IFT(m_spTokens->Truncate(ich));
|
||||
}
|
||||
|
||||
// Adds the m_pszEquation into the running history text
|
||||
void CHistoryCollector::SetExpressionDisplay()
|
||||
{
|
||||
if (nullptr != m_pCalcDisplay)
|
||||
{
|
||||
m_pCalcDisplay->SetExpressionDisplay(m_spTokens, m_spCommands);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int CHistoryCollector::AddCommand(_In_ const std::shared_ptr<IExpressionCommand> & spCommand)
|
||||
{
|
||||
if (m_spCommands == nullptr)
|
||||
{
|
||||
m_spCommands = std::make_shared <CalculatorVector<std::shared_ptr<IExpressionCommand>>>();
|
||||
}
|
||||
|
||||
if (FAILED(m_spCommands->Append(spCommand)))
|
||||
{
|
||||
throw(CALC_E_OUTOFMEMORY);
|
||||
}
|
||||
|
||||
unsigned int nCommmands = 0;
|
||||
m_spCommands->GetSize(&nCommmands);
|
||||
return nCommmands - 1;
|
||||
}
|
||||
|
||||
//To Update the operands in the Expression according to the current Radix
|
||||
void CHistoryCollector::UpdateHistoryExpression(uint32_t radix, int32_t precision)
|
||||
{
|
||||
if (m_spTokens != nullptr)
|
||||
{
|
||||
unsigned int size;
|
||||
IFT(m_spTokens->GetSize(&size));
|
||||
|
||||
for (unsigned int i = 0; i < size; ++i)
|
||||
{
|
||||
std::pair<std::wstring, int> token;
|
||||
IFT(m_spTokens->GetAt(i, &token));
|
||||
int commandPosition = token.second;
|
||||
if (commandPosition != -1)
|
||||
{
|
||||
std::shared_ptr<IExpressionCommand> expCommand;
|
||||
IFT(m_spCommands->GetAt(commandPosition, &expCommand));
|
||||
if (expCommand != nullptr && CalculationManager::CommandType::OperandCommand == expCommand->GetCommandType())
|
||||
{
|
||||
std::shared_ptr<COpndCommand> opndCommand = std::static_pointer_cast<COpndCommand>(expCommand);
|
||||
if (opndCommand != nullptr)
|
||||
{
|
||||
token.first = opndCommand->GetString(radix, precision, m_decimalSymbol);
|
||||
IFT(m_spTokens->SetAt(i, token));
|
||||
opndCommand->SetCommands(GetOperandCommandsFromString(token.first));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SetExpressionDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
void CHistoryCollector::SetDecimalSymbol(wchar_t decimalSymbol)
|
||||
{
|
||||
m_decimalSymbol = decimalSymbol;
|
||||
}
|
||||
|
||||
//Update the commands corresponding to the passed string Number
|
||||
std::shared_ptr<CalculatorVector<int>> CHistoryCollector::GetOperandCommandsFromString(wstring_view numStr)
|
||||
{
|
||||
std::shared_ptr<CalculatorVector<int>> commands = std::make_shared<CalculatorVector<int>>();
|
||||
// Check for negate
|
||||
bool fNegative = (numStr[0] == L'-');
|
||||
bool fSciFmt = false;
|
||||
bool fDecimal = false;
|
||||
|
||||
for (size_t i = (fNegative ? 1 : 0); i < numStr.length(); i++)
|
||||
{
|
||||
if (numStr[i] == m_decimalSymbol)
|
||||
{
|
||||
IFT(commands->Append(IDC_PNT));
|
||||
fDecimal = true;
|
||||
}
|
||||
else if (numStr[i] == L'e')
|
||||
{
|
||||
IFT(commands->Append(IDC_EXP));
|
||||
fSciFmt = true;
|
||||
}
|
||||
else if (numStr[i] == L'-')
|
||||
{
|
||||
IFT(commands->Append(IDC_SIGN));
|
||||
}
|
||||
else if (numStr[i] == L'+')
|
||||
{
|
||||
// Ignore.
|
||||
}
|
||||
// Number
|
||||
else
|
||||
{
|
||||
int num = static_cast<int>(numStr[i]) - ASCII_0;
|
||||
num += IDC_0;
|
||||
IFT(commands->Append(num));
|
||||
}
|
||||
}
|
||||
|
||||
// If the number is negative, append a sign command at the end.
|
||||
if (fNegative)
|
||||
{
|
||||
IFT(commands->Append(IDC_SIGN));
|
||||
}
|
||||
return commands;
|
||||
}
|
64
src/CalcManager/CEngine/Number.cpp
Normal file
64
src/CalcManager/CEngine/Number.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Header Files/Number.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace CalcEngine
|
||||
{
|
||||
Number::Number() noexcept :
|
||||
Number(1, 0, { 0 })
|
||||
{}
|
||||
|
||||
Number::Number(int32_t sign, int32_t exp, vector<uint32_t> const& mantissa) noexcept :
|
||||
m_sign{ sign },
|
||||
m_exp{ exp },
|
||||
m_mantissa{ mantissa }
|
||||
{}
|
||||
|
||||
Number::Number(PNUMBER p) noexcept :
|
||||
m_sign{ p->sign },
|
||||
m_exp{ p->exp },
|
||||
m_mantissa{}
|
||||
{
|
||||
m_mantissa.reserve(p->cdigit);
|
||||
copy(p->mant, p->mant + p->cdigit, back_inserter(m_mantissa));
|
||||
}
|
||||
|
||||
PNUMBER Number::ToPNUMBER() const
|
||||
{
|
||||
PNUMBER ret = _createnum(static_cast<ULONG>(this->Mantissa().size()) + 1);
|
||||
ret->sign = this->Sign();
|
||||
ret->exp = this->Exp();
|
||||
ret->cdigit = static_cast<long>(this->Mantissa().size());
|
||||
|
||||
MANTTYPE *ptrRet = ret->mant;
|
||||
for (auto const& digit : this->Mantissa())
|
||||
{
|
||||
*ptrRet++ = digit;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int32_t const& Number::Sign() const
|
||||
{
|
||||
return m_sign;
|
||||
}
|
||||
|
||||
int32_t const& Number::Exp() const
|
||||
{
|
||||
return m_exp;
|
||||
}
|
||||
|
||||
vector<uint32_t> const& Number::Mantissa() const
|
||||
{
|
||||
return m_mantissa;
|
||||
}
|
||||
|
||||
bool Number::IsZero() const
|
||||
{
|
||||
return all_of(m_mantissa.begin(), m_mantissa.end(), [](auto &&i) { return i == 0; });
|
||||
}
|
||||
}
|
61
src/CalcManager/CEngine/Rational.cpp
Normal file
61
src/CalcManager/CEngine/Rational.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Header Files/Rational.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace CalcEngine
|
||||
{
|
||||
Rational::Rational() noexcept :
|
||||
m_p{},
|
||||
m_q{ 1, 0, { 1 } }
|
||||
{}
|
||||
|
||||
Rational::Rational(Number const& n) noexcept
|
||||
{
|
||||
int32_t qExp = 0;
|
||||
if (n.Exp() < 0)
|
||||
{
|
||||
qExp -= n.Exp();
|
||||
}
|
||||
|
||||
m_p = Number(n.Sign(), 0, n.Mantissa());
|
||||
m_q = Number(1, qExp, { 1 });
|
||||
}
|
||||
|
||||
Rational::Rational(Number const& p, Number const& q) noexcept :
|
||||
m_p{ p },
|
||||
m_q{ q }
|
||||
{}
|
||||
|
||||
Rational::Rational(PRAT prat) noexcept :
|
||||
m_p{ Number{prat->pp} },
|
||||
m_q{ Number{prat->pq} }
|
||||
{}
|
||||
|
||||
PRAT Rational::ToPRAT() const
|
||||
{
|
||||
PRAT ret = _createrat();
|
||||
|
||||
ret->pp = this->P().ToPNUMBER();
|
||||
ret->pq = this->Q().ToPNUMBER();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Number const& Rational::P() const
|
||||
{
|
||||
return m_p;
|
||||
}
|
||||
|
||||
Number const& Rational::Q() const
|
||||
{
|
||||
return m_q;
|
||||
}
|
||||
|
||||
bool Rational::IsZero() const
|
||||
{
|
||||
return this->P().IsZero();
|
||||
}
|
||||
}
|
220
src/CalcManager/CEngine/calc.cpp
Normal file
220
src/CalcManager/CEngine/calc.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/**************************************************************************\
|
||||
*** SCICALC Scientific Calculator for Windows 3.00.12
|
||||
*** (c)1989 Microsoft Corporation. All Rights Reserved.
|
||||
***
|
||||
*** scimain.c
|
||||
***
|
||||
*** Definitions of all globals, WinMain procedure
|
||||
***
|
||||
*** Last modification
|
||||
*** Fri 22-Nov-1996
|
||||
***
|
||||
*** 22-Nov-1996
|
||||
*** Converted Calc from floating point to infinite precision.
|
||||
*** The new math engine is in ..\ratpak
|
||||
***
|
||||
***
|
||||
*** 05-Jan-1990
|
||||
*** Calc did not have a floating point exception signal handler. This
|
||||
*** would cause CALC to be forced to exit on a FP exception as that's
|
||||
*** the default.
|
||||
*** The signal handler is defined in SCIFUNC.C, in WinMain we hook the
|
||||
*** the signal.
|
||||
\**************************************************************************/
|
||||
#include "pch.h"
|
||||
#include "Header Files/CalcEngine.h"
|
||||
|
||||
#include "CalculatorResource.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace CalcEngine;
|
||||
|
||||
/**************************************************************************/
|
||||
/*** Global variable declarations and initializations ***/
|
||||
/**************************************************************************/
|
||||
|
||||
static constexpr int DEFAULT_MAX_DIGITS = 32;
|
||||
static constexpr int DEFAULT_PRECISION = 32;
|
||||
static constexpr long DEFAULT_RADIX = 10;
|
||||
|
||||
static constexpr wchar_t DEFAULT_DEC_SEPARATOR = L'.';
|
||||
static constexpr wchar_t DEFAULT_GRP_SEPARATOR = L',';
|
||||
static constexpr wstring_view DEFAULT_GRP_STR = L"3;0";
|
||||
static constexpr wstring_view DEFAULT_NUMBER_STR = L"0";
|
||||
|
||||
// Read strings for keys, errors, trig types, etc.
|
||||
// These will be copied from the resources to local memory. A larger
|
||||
// than needed block is allocated first and then reallocated once we
|
||||
// know how much is actually used.
|
||||
|
||||
array<wstring, CSTRINGSENGMAX> CCalcEngine::s_engineStrings;
|
||||
|
||||
void CCalcEngine::LoadEngineStrings(CalculationManager::IResourceProvider& resourceProvider)
|
||||
{
|
||||
for (size_t i = 0; i < s_engineStrings.size(); i++)
|
||||
{
|
||||
s_engineStrings[i] = resourceProvider.GetCEngineString(g_sids[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// InitialOneTimeOnlyNumberSetup
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
void CCalcEngine::InitialOneTimeOnlySetup(CalculationManager::IResourceProvider& resourceProvider)
|
||||
{
|
||||
LoadEngineStrings(resourceProvider);
|
||||
|
||||
// we must now setup all the ratpak constants and our arrayed pointers
|
||||
// to these constants.
|
||||
ChangeBaseConstants(DEFAULT_RADIX, DEFAULT_MAX_DIGITS, DEFAULT_PRECISION);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// CCalcEngine::CCalcEngine
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
CCalcEngine::CCalcEngine(bool fPrecedence, bool fIntegerMode, CalculationManager::IResourceProvider* const pResourceProvider, __in_opt ICalcDisplay *pCalcDisplay, __in_opt shared_ptr<IHistoryDisplay> pHistoryDisplay) :
|
||||
m_HistoryCollector(pCalcDisplay, pHistoryDisplay, DEFAULT_DEC_SEPARATOR),
|
||||
m_resourceProvider(pResourceProvider),
|
||||
m_bSetCalcState(false),
|
||||
m_fPrecedence(fPrecedence),
|
||||
m_fIntegerMode(fIntegerMode),
|
||||
m_pCalcDisplay(pCalcDisplay),
|
||||
m_input(DEFAULT_DEC_SEPARATOR),
|
||||
m_nOpCode(0),
|
||||
m_nPrevOpCode(0),
|
||||
m_openParenCount(0),
|
||||
m_nPrecNum(0),
|
||||
m_nTempCom(0),
|
||||
m_nLastCom(0),
|
||||
m_parenVals{},
|
||||
m_precedenceVals{},
|
||||
m_bChangeOp(false),
|
||||
m_bRecord(false),
|
||||
m_bError(false),
|
||||
m_bInv(false),
|
||||
m_nFE(FMT_FLOAT),
|
||||
m_bNoPrevEqu(true),
|
||||
m_numwidth(QWORD_WIDTH),
|
||||
m_angletype(ANGLE_DEG),
|
||||
m_radix(DEFAULT_RADIX),
|
||||
m_precision(DEFAULT_PRECISION),
|
||||
m_cIntDigitsSav(DEFAULT_MAX_DIGITS),
|
||||
m_decGrouping(),
|
||||
m_groupSeparator(DEFAULT_GRP_SEPARATOR),
|
||||
m_numberString(DEFAULT_NUMBER_STR),
|
||||
m_nOp(),
|
||||
m_nPrecOp(),
|
||||
m_memoryValue{make_unique<Rational>()},
|
||||
m_holdVal{},
|
||||
m_currentVal{},
|
||||
m_lastVal{}
|
||||
{
|
||||
InitChopNumbers();
|
||||
|
||||
m_dwWordBitWidth = DwWordBitWidthFromeNumWidth(m_numwidth);
|
||||
|
||||
PRAT maxTrig = longtorat(10L);
|
||||
PRAT hundred = longtorat(100L);
|
||||
powrat(&maxTrig, hundred, m_radix, m_precision);
|
||||
m_maxTrigonometricNum = Rational{ maxTrig };
|
||||
destroyrat(maxTrig);
|
||||
destroyrat(hundred);
|
||||
|
||||
SetRadixTypeAndNumWidth(DEC_RADIX, m_numwidth);
|
||||
SettingsChanged();
|
||||
DisplayNum();
|
||||
}
|
||||
|
||||
void CCalcEngine::InitChopNumbers()
|
||||
{
|
||||
// these rat numbers are set only once and then never change regardless of
|
||||
// base or precision changes
|
||||
assert(m_chopNumbers.size() >= 4);
|
||||
m_chopNumbers[0] = Rational{ rat_qword };
|
||||
m_chopNumbers[1] = Rational{ rat_dword };
|
||||
m_chopNumbers[2] = Rational{ rat_word };
|
||||
m_chopNumbers[3] = Rational{ rat_byte };
|
||||
|
||||
// initialize the max dec number you can support for each of the supported bit length
|
||||
// this is basically max num in that width / 2 in integer
|
||||
assert(m_chopNumbers.size() == m_maxDecimalValueStrings.size());
|
||||
for (size_t i = 0; i < m_chopNumbers.size(); i++)
|
||||
{
|
||||
PRAT hno = m_chopNumbers[i].ToPRAT();
|
||||
|
||||
divrat(&hno, rat_two, m_precision);
|
||||
intrat(&hno, m_radix, m_precision);
|
||||
|
||||
m_maxDecimalValueStrings[i] = NumObjToString(hno, 10, FMT_FLOAT, m_precision);
|
||||
|
||||
NumObjDestroy(&hno);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the number in memory for UI to keep it persisted and set it again to a different instance
|
||||
// of CCalcEngine. Otherwise it will get destructed with the CalcEngine
|
||||
unique_ptr<Rational> CCalcEngine::PersistedMemObject()
|
||||
{
|
||||
return move(m_memoryValue);
|
||||
}
|
||||
|
||||
void CCalcEngine::PersistedMemObject(Rational const& memObject)
|
||||
{
|
||||
m_memoryValue = make_unique<Rational>(memObject);
|
||||
}
|
||||
|
||||
void CCalcEngine::SettingsChanged()
|
||||
{
|
||||
wchar_t lastDec = m_decimalSeparator;
|
||||
wstring decStr = m_resourceProvider->GetCEngineString(L"sDecimal");
|
||||
m_decimalSeparator = decStr.empty() ? DEFAULT_DEC_SEPARATOR : decStr.at(0);
|
||||
// Until it can be removed, continue to set ratpak decimal here
|
||||
SetDecimalSeparator(m_decimalSeparator);
|
||||
|
||||
wchar_t lastSep = m_groupSeparator;
|
||||
wstring sepStr = m_resourceProvider->GetCEngineString(L"sThousand");
|
||||
m_groupSeparator = sepStr.empty() ? DEFAULT_GRP_SEPARATOR : sepStr.at(0);
|
||||
|
||||
auto lastDecGrouping = m_decGrouping;
|
||||
wstring grpStr = m_resourceProvider->GetCEngineString(L"sGrouping");
|
||||
m_decGrouping = DigitGroupingStringToGroupingVector(grpStr.empty() ? DEFAULT_GRP_STR : grpStr);
|
||||
|
||||
bool numChanged = false;
|
||||
|
||||
// if the grouping pattern or thousands symbol changed we need to refresh the display
|
||||
if (m_decGrouping != lastDecGrouping || m_groupSeparator != lastSep)
|
||||
{
|
||||
numChanged = true;
|
||||
}
|
||||
|
||||
// if the decimal symbol has changed we always do the following things
|
||||
if (m_decimalSeparator != lastDec)
|
||||
{
|
||||
// Re-initialize member variables' decimal point.
|
||||
m_input.SetDecimalSymbol(m_decimalSeparator);
|
||||
m_HistoryCollector.SetDecimalSymbol(m_decimalSeparator);
|
||||
|
||||
// put the new decimal symbol into the table used to draw the decimal key
|
||||
s_engineStrings[IDS_DECIMAL] = m_decimalSeparator;
|
||||
|
||||
// we need to redraw to update the decimal point button
|
||||
numChanged = true;
|
||||
}
|
||||
|
||||
if (numChanged)
|
||||
{
|
||||
DisplayNum();
|
||||
}
|
||||
}
|
||||
|
||||
wchar_t CCalcEngine::DecimalSeparator() const
|
||||
{
|
||||
return m_decimalSeparator;
|
||||
}
|
1107
src/CalcManager/CEngine/scicomm.cpp
Normal file
1107
src/CalcManager/CEngine/scicomm.cpp
Normal file
File diff suppressed because it is too large
Load Diff
400
src/CalcManager/CEngine/scidisp.cpp
Normal file
400
src/CalcManager/CEngine/scidisp.cpp
Normal file
@@ -0,0 +1,400 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/****************************Module*Header***********************************\
|
||||
* Module Name: SCIDISP.C
|
||||
*
|
||||
* Module Descripton:
|
||||
*
|
||||
* Warnings:
|
||||
*
|
||||
* Created:
|
||||
*
|
||||
* Author:
|
||||
\****************************************************************************/
|
||||
#include "pch.h"
|
||||
#include "Header Files/CalcEngine.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace CalcEngine;
|
||||
|
||||
constexpr int MAX_EXPONENT = 4;
|
||||
constexpr uint32_t MAX_GROUPING_SIZE = 16;
|
||||
constexpr wstring_view c_decPreSepStr = L"[+-]?(\\d*)[";
|
||||
constexpr wstring_view c_decPostSepStr = L"]?(\\d*)(?:e[+-]?(\\d*))?$";
|
||||
|
||||
|
||||
/****************************************************************************\
|
||||
* void DisplayNum(void)
|
||||
*
|
||||
* Convert m_currentVal to a string in the current radix.
|
||||
*
|
||||
* Updates the following variables:
|
||||
* m_currentVal, m_numberString
|
||||
\****************************************************************************/
|
||||
//
|
||||
// State of calc last time DisplayNum was called
|
||||
//
|
||||
typedef struct {
|
||||
PRAT hnoNum;
|
||||
int32_t precision;
|
||||
uint32_t radix;
|
||||
INT nFE;
|
||||
NUM_WIDTH numwidth;
|
||||
bool fIntMath;
|
||||
bool bRecord;
|
||||
bool bUseSep;
|
||||
} LASTDISP;
|
||||
|
||||
LASTDISP gldPrevious = { nullptr, -1, 0, -1, (NUM_WIDTH)-1, false, false, false };
|
||||
|
||||
// Truncates if too big, makes it a non negative - the number in rat. Doesn't do anything if not in INT mode
|
||||
CalcEngine::Rational CCalcEngine::TruncateNumForIntMath(CalcEngine::Rational const& rat)
|
||||
{
|
||||
if (!m_fIntegerMode)
|
||||
{
|
||||
return rat;
|
||||
}
|
||||
|
||||
PRAT tempRat = rat.ToPRAT();
|
||||
|
||||
// Truncate to an integer. Do not round here.
|
||||
intrat(&tempRat, m_radix, m_precision);
|
||||
|
||||
PRAT chopRat = m_chopNumbers[m_numwidth].ToPRAT();
|
||||
// Can be converting a dec -ve number to Hex/Oct/Bin rep. Use 2's completement form
|
||||
// Check the range.
|
||||
if (NumObjIsLess(tempRat, rat_zero, m_precision))
|
||||
{
|
||||
// if negative make positive by doing a twos complement
|
||||
NumObjNegate(&tempRat);
|
||||
subrat(&tempRat, rat_one, m_precision);
|
||||
NumObjNot(&tempRat, true, chopRat, m_radix, m_precision);
|
||||
}
|
||||
|
||||
andrat(&tempRat, chopRat, m_radix, m_precision);
|
||||
destroyrat(chopRat);
|
||||
|
||||
Rational result{ tempRat };
|
||||
destroyrat(tempRat);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CCalcEngine::DisplayNum(void)
|
||||
{
|
||||
//
|
||||
// Only change the display if
|
||||
// we are in record mode -OR-
|
||||
// this is the first time DisplayNum has been called, -OR-
|
||||
// something important has changed since the last time DisplayNum was
|
||||
// called.
|
||||
//
|
||||
PRAT curRat = m_currentVal.ToPRAT();
|
||||
bool hasValChanged = !gldPrevious.hnoNum || !NumObjIsEq(gldPrevious.hnoNum, curRat, m_precision);
|
||||
destroyrat(curRat);
|
||||
if ( m_bRecord || gldPrevious.hnoNum == nullptr ||
|
||||
hasValChanged ||
|
||||
gldPrevious.precision != m_precision ||
|
||||
gldPrevious.radix != m_radix ||
|
||||
gldPrevious.nFE != (int)m_nFE ||
|
||||
gldPrevious.bUseSep != true ||
|
||||
gldPrevious.numwidth != m_numwidth ||
|
||||
gldPrevious.fIntMath != m_fIntegerMode ||
|
||||
gldPrevious.bRecord != m_bRecord )
|
||||
{
|
||||
gldPrevious.precision = m_precision;
|
||||
gldPrevious.radix = m_radix;
|
||||
gldPrevious.nFE = (int)m_nFE;
|
||||
gldPrevious.numwidth = m_numwidth;
|
||||
|
||||
gldPrevious.fIntMath = m_fIntegerMode;
|
||||
gldPrevious.bRecord = m_bRecord;
|
||||
gldPrevious.bUseSep = true;
|
||||
|
||||
if (m_bRecord)
|
||||
{
|
||||
// Display the string and return.
|
||||
m_numberString = m_input.ToString(m_radix, m_fIntegerMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're in Programmer mode, perform integer truncation so e.g. 5 / 2 * 2 results in 4, not 5.
|
||||
if (m_fIntegerMode)
|
||||
{
|
||||
m_currentVal = TruncateNumForIntMath(m_currentVal);
|
||||
}
|
||||
m_numberString = GetStringForDisplay(m_currentVal, m_radix);
|
||||
}
|
||||
|
||||
// Displayed number can go thru transformation. So copy it after transformation
|
||||
destroyrat(gldPrevious.hnoNum);
|
||||
gldPrevious.hnoNum = m_currentVal.ToPRAT();
|
||||
|
||||
if((m_radix == 10) && IsNumberInvalid(m_numberString, MAX_EXPONENT, m_precision, m_radix))
|
||||
{
|
||||
DisplayError(CALC_E_OVERFLOW);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display the string and return.
|
||||
SetPrimaryDisplay(GroupDigitsPerRadix(m_numberString, m_radix));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int CCalcEngine::IsNumberInvalid(const wstring& numberString, int iMaxExp, int iMaxMantissa, uint32_t radix) const
|
||||
{
|
||||
int iError = 0;
|
||||
|
||||
if (radix == 10)
|
||||
{
|
||||
// start with an optional + or -
|
||||
// followed by zero or more digits
|
||||
// followed by an optional decimal point
|
||||
// followed by zero or more digits
|
||||
// followed by an optional exponent
|
||||
// in case there's an exponent:
|
||||
// its optionally followed by a + or -
|
||||
// which is followed by zero or more digits
|
||||
wregex rx(wstring{ c_decPreSepStr } + m_decimalSeparator + wstring{ c_decPostSepStr });
|
||||
wsmatch matches;
|
||||
if (regex_match(numberString, matches, rx))
|
||||
{
|
||||
// Check that exponent isn't too long
|
||||
if (matches.length(3) > iMaxExp)
|
||||
{
|
||||
iError = IDS_ERR_INPUT_OVERFLOW;
|
||||
}
|
||||
else
|
||||
{
|
||||
wstring exp = matches.str(1);
|
||||
auto intItr = exp.begin();
|
||||
auto intEnd = exp.end();
|
||||
while (intItr != intEnd && *intItr == L'0')
|
||||
{
|
||||
intItr++;
|
||||
}
|
||||
|
||||
auto iMantissa = distance(intItr, intEnd) + matches.length(2);
|
||||
if (iMantissa > iMaxMantissa)
|
||||
{
|
||||
iError = IDS_ERR_INPUT_OVERFLOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
iError = IDS_ERR_UNK_CH;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const wchar_t& c : numberString)
|
||||
{
|
||||
if (radix == 16)
|
||||
{
|
||||
if (!(iswdigit(c) || (c >= L'A' && c <= L'F')))
|
||||
{
|
||||
iError = IDS_ERR_UNK_CH;
|
||||
}
|
||||
}
|
||||
else if (c < L'0' || c >= L'0' + radix)
|
||||
{
|
||||
iError = IDS_ERR_UNK_CH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return iError;
|
||||
}
|
||||
|
||||
/****************************************************************************\
|
||||
*
|
||||
* DigitGroupingStringToGroupingVector
|
||||
*
|
||||
* Description:
|
||||
* This will take the digit grouping string found in the regional applet and
|
||||
* represent this string as a vector.
|
||||
*
|
||||
* groupingString
|
||||
* 0;0 - no grouping
|
||||
* 3;0 - group every 3 digits
|
||||
* 3 - group 1st 3, then no grouping after
|
||||
* 3;0;0 - group 1st 3, then no grouping after
|
||||
* 3;2;0 - group 1st 3 and then every 2 digits
|
||||
* 4;0 - group every 4 digits
|
||||
* 5;3;2;0 - group 5, then 3, then every 2
|
||||
* 5;3;2 - group 5, then 3, then 2, then no grouping after
|
||||
*
|
||||
* Returns: the groupings as a vector
|
||||
*
|
||||
\****************************************************************************/
|
||||
vector<uint32_t> CCalcEngine::DigitGroupingStringToGroupingVector(wstring_view groupingString)
|
||||
{
|
||||
vector<uint32_t> grouping{};
|
||||
uint32_t currentGroup = 0;
|
||||
wchar_t* next = nullptr;
|
||||
for (const wchar_t* itr = groupingString.data(); *itr != L'\0'; ++itr)
|
||||
{
|
||||
// Try to parse a grouping number from the string
|
||||
currentGroup = wcstoul(itr, &next, 10);
|
||||
|
||||
// If we successfully parsed a group, add it to the grouping.
|
||||
if (currentGroup < MAX_GROUPING_SIZE)
|
||||
{
|
||||
grouping.emplace_back(currentGroup);
|
||||
}
|
||||
|
||||
// If we found a grouping and aren't at the end of the string yet,
|
||||
// jump to the next position in the string (the ';').
|
||||
// The loop will then increment us to the next character, which should be a number.
|
||||
if (next && (static_cast<size_t>(next - groupingString.data()) < groupingString.length()))
|
||||
{
|
||||
itr = next;
|
||||
}
|
||||
}
|
||||
|
||||
return grouping;
|
||||
}
|
||||
|
||||
wstring CCalcEngine::GroupDigitsPerRadix(wstring_view numberString, uint32_t radix)
|
||||
{
|
||||
if (numberString.empty())
|
||||
{
|
||||
return wstring{};
|
||||
}
|
||||
|
||||
switch (radix)
|
||||
{
|
||||
case 10:
|
||||
return GroupDigits(wstring{ m_groupSeparator }, m_decGrouping, numberString, (L'-' == numberString[0]));
|
||||
case 8:
|
||||
return GroupDigits(L" ", { 3, 0 }, numberString);
|
||||
case 2:
|
||||
case 16:
|
||||
return GroupDigits(L" ", { 4, 0 }, numberString);
|
||||
default:
|
||||
return wstring{ numberString };
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************\
|
||||
*
|
||||
* GroupDigits
|
||||
*
|
||||
* Description:
|
||||
* This routine will take a grouping vector and the display string and
|
||||
* add the separator according to the pattern indicated by the separator.
|
||||
*
|
||||
* Grouping
|
||||
* 0,0 - no grouping
|
||||
* 3,0 - group every 3 digits
|
||||
* 3 - group 1st 3, then no grouping after
|
||||
* 3,0,0 - group 1st 3, then no grouping after
|
||||
* 3,2,0 - group 1st 3 and then every 2 digits
|
||||
* 4,0 - group every 4 digits
|
||||
* 5,3,2,0 - group 5, then 3, then every 2
|
||||
* 5,3,2 - group 5, then 3, then 2, then no grouping after
|
||||
*
|
||||
\***************************************************************************/
|
||||
wstring CCalcEngine::GroupDigits(wstring_view delimiter, vector<uint32_t> const& grouping, wstring_view displayString, bool isNumNegative)
|
||||
{
|
||||
// if there's nothing to do, bail
|
||||
if (delimiter.empty() || grouping.empty())
|
||||
{
|
||||
return wstring{ displayString };
|
||||
}
|
||||
|
||||
// Find the position of exponential 'e' in the string
|
||||
size_t exp = displayString.find(L'e');
|
||||
bool hasExponent = (exp != wstring_view::npos);
|
||||
|
||||
// Find the position of decimal point in the string
|
||||
size_t dec = displayString.find(m_decimalSeparator);
|
||||
bool hasDecimal = (dec != wstring_view::npos);
|
||||
|
||||
// Create an iterator that points to the end of the portion of the number subject to grouping (i.e. left of the decimal)
|
||||
auto ritr = displayString.rend();
|
||||
if (hasDecimal)
|
||||
{
|
||||
ritr -= dec;
|
||||
}
|
||||
else if (hasExponent)
|
||||
{
|
||||
ritr -= exp;
|
||||
}
|
||||
else
|
||||
{
|
||||
ritr = displayString.rbegin();
|
||||
}
|
||||
|
||||
wstringstream groupedStream{};
|
||||
uint32_t groupingSize = 0;
|
||||
|
||||
auto groupItr = grouping.begin();
|
||||
auto currGrouping = *groupItr;
|
||||
// Mark the 'end' of the string as either rend() or rend()-1 if there is a negative sign
|
||||
// We exclude the sign here because we don't want to end up with e.g. "-,123,456"
|
||||
// Then, iterate from back to front, adding group delimiters as needed.
|
||||
auto reverse_end = displayString.rend() - (isNumNegative ? 1 : 0);
|
||||
while (ritr != reverse_end)
|
||||
{
|
||||
groupedStream << *ritr++;
|
||||
groupingSize++;
|
||||
|
||||
// If a group is complete, add a separator
|
||||
// Do not add a separator if:
|
||||
// - grouping size is 0
|
||||
// - we are at the end of the digit string
|
||||
if (currGrouping != 0 && (groupingSize % currGrouping) == 0 && ritr != reverse_end)
|
||||
{
|
||||
groupedStream << wstring{ delimiter };
|
||||
groupingSize = 0; // reset for a new group
|
||||
|
||||
// Shift the grouping to next values if they exist
|
||||
if (groupItr != grouping.end())
|
||||
{
|
||||
++groupItr;
|
||||
|
||||
// Loop through grouping vector until we find a non-zero value.
|
||||
// "0" values may appear in a form of either e.g. "3;0" or "3;0;0".
|
||||
// A 0 in the last position means repeat the previous grouping.
|
||||
// A 0 in another position is a group. So, "3;0;0" means "group 3, then group 0 repeatedly"
|
||||
// This could be expressed as just "3" but GetLocaleInfo is returning 3;0;0 in some cases instead.
|
||||
for (currGrouping = 0; groupItr != grouping.end(); ++groupItr)
|
||||
{
|
||||
// If it's a non-zero value, that's our new group
|
||||
if (*groupItr != 0)
|
||||
{
|
||||
currGrouping = *groupItr;
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, save the previous grouping in case we need to repeat it
|
||||
currGrouping = *(groupItr - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now copy the negative sign if it is there
|
||||
if (isNumNegative)
|
||||
{
|
||||
groupedStream << displayString[0];
|
||||
}
|
||||
|
||||
auto groupedString = groupedStream.str();
|
||||
wstring result(groupedString.rbegin(), groupedString.rend());
|
||||
// Add the right (fractional or exponential) part of the number to the final string.
|
||||
if (hasDecimal)
|
||||
{
|
||||
result += displayString.substr(dec);
|
||||
}
|
||||
else if (hasExponent)
|
||||
{
|
||||
result += displayString.substr(exp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
306
src/CalcManager/CEngine/scifunc.cpp
Normal file
306
src/CalcManager/CEngine/scifunc.cpp
Normal file
@@ -0,0 +1,306 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/**************************************************************************/
|
||||
/*** SCICALC Scientific Calculator for Windows 3.00.12 ***/
|
||||
/*** (c)1989 Microsoft Corporation. All Rights Reserved. ***/
|
||||
/*** ***/
|
||||
/*** scifunc.c ***/
|
||||
/*** ***/
|
||||
/*** Functions contained: ***/
|
||||
/*** SciCalcFunctions--do sin, cos, tan, com, log, ln, rec, fac, etc.***/
|
||||
/*** DisplayError--Error display driver. ***/
|
||||
/*** ***/
|
||||
/*** Functions called: ***/
|
||||
/*** SciCalcFunctions call DisplayError. ***/
|
||||
/*** ***/
|
||||
/*** ***/
|
||||
/**************************************************************************/
|
||||
#include "pch.h"
|
||||
#include "Header Files/CalcEngine.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace CalcEngine;
|
||||
|
||||
/* Routines for more complex mathematical functions/error checking. */
|
||||
CalcEngine::Rational CCalcEngine::SciCalcFunctions(CalcEngine::Rational const& rat, DWORD op)
|
||||
{
|
||||
PRAT tempRat = rat.ToPRAT();
|
||||
try
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case IDC_CHOP:
|
||||
m_bInv ? fracrat(&tempRat , m_radix, m_precision) : intrat(&tempRat, m_radix, m_precision);
|
||||
break;
|
||||
|
||||
/* Return complement. */
|
||||
case IDC_COM:
|
||||
{
|
||||
PRAT chopRat = m_chopNumbers[m_numwidth].ToPRAT();
|
||||
NumObjNot(&tempRat, m_fIntegerMode, chopRat, m_radix, m_precision);
|
||||
destroyrat(chopRat);
|
||||
break;
|
||||
}
|
||||
|
||||
// Rotate Left with hi bit wrapped over to lo bit
|
||||
case IDC_ROL:
|
||||
if (m_fIntegerMode)
|
||||
{
|
||||
intrat(&tempRat, m_radix, m_precision);
|
||||
|
||||
PRAT curRat = m_currentVal.ToPRAT();
|
||||
ULONGLONG w64Bits = NumObjGetUlValue(curRat, m_radix, m_precision);
|
||||
ULONGLONG msb = (w64Bits >> (m_dwWordBitWidth - 1)) & 1;
|
||||
|
||||
w64Bits <<= 1; // LShift by 1
|
||||
w64Bits |= msb; // Set the prev Msb as the current Lsb
|
||||
NumObjSetUlonglongValue(&curRat, w64Bits, m_radix, m_precision);
|
||||
m_currentVal = Rational{ curRat };
|
||||
destroyrat(curRat);
|
||||
}
|
||||
break;
|
||||
|
||||
// Rotate right with lo bit wrapped over to hi bit
|
||||
case IDC_ROR:
|
||||
if (m_fIntegerMode)
|
||||
{
|
||||
intrat(&tempRat, m_radix, m_precision);
|
||||
PRAT curRat = m_currentVal.ToPRAT();
|
||||
ULONGLONG w64Bits = NumObjGetUlValue(curRat, m_radix, m_precision);
|
||||
|
||||
ULONGLONG lsb = ((w64Bits & 0x01) == 1) ? 1 : 0;
|
||||
w64Bits >>= 1; //RShift by 1
|
||||
w64Bits |= (lsb << (m_dwWordBitWidth - 1));
|
||||
NumObjSetUlonglongValue(&curRat, w64Bits, m_radix, m_precision);
|
||||
m_currentVal = Rational{ curRat };
|
||||
destroyrat(curRat);
|
||||
}
|
||||
break;
|
||||
|
||||
case IDC_PERCENT:
|
||||
{
|
||||
PRAT hno = nullptr;
|
||||
PRAT hno100 = nullptr;
|
||||
|
||||
try
|
||||
{
|
||||
// If the operator is multiply/divide, we evaluate this as "X [op] (Y%)"
|
||||
// Otherwise, we evaluate it as "X [op] (X * Y%)"
|
||||
if (m_nOpCode == IDC_MUL || m_nOpCode == IDC_DIV)
|
||||
{
|
||||
NumObjSetIntValue(&hno100, 100);
|
||||
|
||||
divrat(&tempRat, hno100, m_precision);
|
||||
destroyrat(hno100);
|
||||
}
|
||||
else
|
||||
{
|
||||
hno = m_lastVal.ToPRAT();
|
||||
NumObjSetIntValue(&hno100, 100);
|
||||
|
||||
divrat(&hno, hno100, m_precision);
|
||||
destroyrat(hno100);
|
||||
|
||||
mulrat(&tempRat, hno, m_precision);
|
||||
destroyrat(hno);
|
||||
}
|
||||
}
|
||||
catch ( DWORD nErrCode )
|
||||
{
|
||||
destroyrat(hno);
|
||||
destroyrat(hno100);
|
||||
destroyrat(tempRat);
|
||||
throw nErrCode;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IDC_SIN: /* Sine; normal and arc */
|
||||
if (!m_fIntegerMode)
|
||||
{
|
||||
m_bInv ? asinanglerat(&tempRat, m_angletype, m_radix, m_precision) : NumObjSin(&tempRat, m_angletype, m_radix, m_precision);
|
||||
}
|
||||
break;
|
||||
|
||||
case IDC_SINH: /* Sine- hyperbolic and archyperbolic */
|
||||
if (!m_fIntegerMode)
|
||||
{
|
||||
m_bInv ? asinhrat(&tempRat, m_radix, m_precision) : sinhrat(&tempRat, m_radix, m_precision);
|
||||
}
|
||||
break;
|
||||
|
||||
case IDC_COS: /* Cosine, follows convention of sine function. */
|
||||
if (!m_fIntegerMode)
|
||||
{
|
||||
m_bInv ? acosanglerat(&tempRat, m_angletype, m_radix, m_precision) : NumObjCos(&tempRat, m_angletype, m_radix, m_precision);
|
||||
}
|
||||
break;
|
||||
|
||||
case IDC_COSH: /* Cosine hyperbolic, follows convention of sine h function. */
|
||||
if (!m_fIntegerMode)
|
||||
{
|
||||
m_bInv ? acoshrat(&tempRat, m_radix, m_precision) : coshrat(&tempRat, m_radix, m_precision);
|
||||
}
|
||||
break;
|
||||
|
||||
case IDC_TAN: /* Same as sine and cosine. */
|
||||
if (!m_fIntegerMode)
|
||||
{
|
||||
m_bInv ? atananglerat(&tempRat, m_angletype, m_radix, m_precision) : NumObjTan(&tempRat, m_angletype, m_radix, m_precision);
|
||||
}
|
||||
break;
|
||||
|
||||
case IDC_TANH: /* Same as sine h and cosine h. */
|
||||
if (!m_fIntegerMode)
|
||||
{
|
||||
m_bInv ? atanhrat(&tempRat, m_precision) : tanhrat(&tempRat, m_radix, m_precision);
|
||||
}
|
||||
break;
|
||||
|
||||
case IDC_REC: /* Reciprocal. */
|
||||
NumObjInvert(&tempRat, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_SQR: /* Square */
|
||||
powrat(&tempRat, rat_two, m_radix, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_SQRT: /* Sqrt only in Std mode */
|
||||
rootrat(&tempRat, rat_two, m_radix, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_CUBEROOT:
|
||||
case IDC_CUB: /* Cubing and cube root functions. */
|
||||
{
|
||||
PRAT hno = nullptr;
|
||||
|
||||
try
|
||||
{
|
||||
NumObjAssign(&hno, rat_one);
|
||||
addrat(&hno, rat_two, m_precision);
|
||||
|
||||
if (IDC_CUBEROOT == op)
|
||||
{
|
||||
rootrat(&tempRat, hno, m_radix, m_precision);
|
||||
}
|
||||
else
|
||||
{
|
||||
powrat(&tempRat, hno, m_radix, m_precision);
|
||||
}
|
||||
|
||||
destroyrat(hno);
|
||||
}
|
||||
catch (DWORD nErrCode)
|
||||
{
|
||||
destroyrat(hno);
|
||||
destroyrat(tempRat);
|
||||
throw nErrCode;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IDC_LOG: /* Functions for common log. */
|
||||
log10rat(&tempRat, m_precision);
|
||||
break;
|
||||
case IDC_POW10:
|
||||
NumObjAntiLog10(&tempRat, m_radix, m_precision);
|
||||
break;
|
||||
case IDC_LN: /* Functions for natural log. */
|
||||
if (m_bInv)
|
||||
{
|
||||
exprat(&tempRat, m_radix, m_precision); // e^x.
|
||||
}
|
||||
else
|
||||
{
|
||||
lograt(&tempRat, m_precision);
|
||||
}
|
||||
break;
|
||||
|
||||
case IDC_FAC: /* Calculate factorial. Inverse is ineffective. */
|
||||
factrat(&tempRat, m_radix, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_DEGREES:
|
||||
ProcessCommand(IDC_INV);
|
||||
// This case falls through to IDC_DMS case because in the old Win32 Calc,
|
||||
// the degrees functionality was achieved as 'Inv' of 'dms' operation,
|
||||
// so setting the IDC_INV command first and then performing 'dms' operation as global variables m_bInv, m_bRecord
|
||||
// are set properly through ProcessCommand(IDC_INV)
|
||||
case IDC_DMS:
|
||||
{
|
||||
if (!m_fIntegerMode)
|
||||
{
|
||||
PRAT hnoMin = nullptr;
|
||||
PRAT hnoSec = nullptr;
|
||||
PRAT hnoShft = nullptr;
|
||||
|
||||
try
|
||||
{
|
||||
NumObjSetIntValue(&hnoShft, m_bInv ? 100 : 60);
|
||||
|
||||
NumObjAssign(&hnoMin, tempRat);
|
||||
intrat(&tempRat, m_radix, m_precision);
|
||||
|
||||
subrat(&hnoMin, tempRat, m_precision);
|
||||
mulrat(&hnoMin, hnoShft, m_precision);
|
||||
NumObjAssign(&hnoSec, hnoMin );
|
||||
intrat(&hnoMin, m_radix, m_precision);
|
||||
|
||||
subrat(&hnoSec, hnoMin, m_precision);
|
||||
mulrat(&hnoSec, hnoShft, m_precision);
|
||||
|
||||
//
|
||||
// tempRat == degrees, hnoMin == minutes, hnoSec == seconds
|
||||
//
|
||||
|
||||
NumObjSetIntValue(&hnoShft, m_bInv ? 60 : 100);
|
||||
divrat(&hnoSec, hnoShft, m_precision);
|
||||
addrat(&hnoMin, hnoSec, m_precision);
|
||||
|
||||
divrat(&hnoMin, hnoShft, m_precision);
|
||||
addrat(&tempRat, hnoMin, m_precision);
|
||||
|
||||
destroyrat(hnoShft);
|
||||
destroyrat(hnoMin);
|
||||
destroyrat(hnoSec);
|
||||
}
|
||||
catch (DWORD nErrCode)
|
||||
{
|
||||
destroyrat(hnoShft);
|
||||
destroyrat(hnoMin);
|
||||
destroyrat(hnoSec);
|
||||
destroyrat(tempRat);
|
||||
throw nErrCode;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} // end switch( op )
|
||||
|
||||
Rational result{ tempRat };
|
||||
destroyrat(tempRat);
|
||||
return result;
|
||||
}
|
||||
catch(DWORD nErrCode)
|
||||
{
|
||||
DisplayError(nErrCode);
|
||||
destroyrat(tempRat);
|
||||
return rat;
|
||||
}
|
||||
}
|
||||
|
||||
/* Routine to display error messages and set m_bError flag. Errors are */
|
||||
/* called with DisplayError (n), where n is a DWORD between 0 and 5. */
|
||||
|
||||
void CCalcEngine::DisplayError(DWORD nError)
|
||||
{
|
||||
wstring errorString{ GetString(IDS_ERRORS_FIRST + SCODE_CODE(nError)) };
|
||||
|
||||
SetPrimaryDisplay(errorString, true /*isError*/);
|
||||
|
||||
m_bError = true; /* Set error flag. Only cleared with CLEAR or CENTR. */
|
||||
|
||||
m_HistoryCollector.ClearHistoryLine(errorString);
|
||||
}
|
||||
|
206
src/CalcManager/CEngine/scimath.cpp
Normal file
206
src/CalcManager/CEngine/scimath.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Header Files/CalcEngine.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
/**************************************************************************\
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* # # ##### *
|
||||
* # # # # # *
|
||||
* # # # # # # # *
|
||||
* # ### ### # # *
|
||||
* # # ### # # # ### # # ### ##### # ### ### ### *
|
||||
* # ## # # # ## # # # # # # ## # # # *
|
||||
* # # # # # # # # # ##### # # ##### # *
|
||||
* # # # # # # # # # # # # # # ## *
|
||||
* # # # # # # # # # ### # # ### ### ## *
|
||||
* *
|
||||
* *
|
||||
* Infinte Precision Production Version *
|
||||
* *
|
||||
\**************************************************************************/
|
||||
//
|
||||
// RETAIL version of NUMOBJ math that uses Infinite Precision
|
||||
//
|
||||
// History
|
||||
//
|
||||
// 16-Nov-1996 Wrote it
|
||||
// whenever-97 Rewrote it using improved ratpak model
|
||||
//
|
||||
|
||||
/*****************************************************************\
|
||||
*
|
||||
* Generic Math Package support routines and variables
|
||||
*
|
||||
* History:
|
||||
* 01-Dec-1996 Wrote them
|
||||
* whenever-97 Rewrote them
|
||||
*
|
||||
\*****************************************************************/
|
||||
|
||||
/*****************************************************************\
|
||||
*
|
||||
* Unary functions
|
||||
*
|
||||
* History:
|
||||
* 01-Dec-1996 Wrote them
|
||||
* whenever-97 Rewrote them
|
||||
*
|
||||
\*****************************************************************/
|
||||
|
||||
void NumObjInvert(PRAT * phno, int32_t precision)
|
||||
{
|
||||
PRAT hno = nullptr;
|
||||
|
||||
NumObjAssign( &hno, rat_one);
|
||||
divrat( &hno, *phno, precision);
|
||||
NumObjAssign( phno, hno );
|
||||
NumObjDestroy( &hno );
|
||||
}
|
||||
|
||||
void NumObjNegate(PRAT *phno)
|
||||
{
|
||||
(*phno)->pp->sign = -(*phno)->pp->sign;
|
||||
}
|
||||
|
||||
void NumObjAbs(PRAT *phno)
|
||||
{
|
||||
(*phno)->pp->sign = 1;
|
||||
(*phno)->pq->sign = 1;
|
||||
}
|
||||
|
||||
void NumObjAntiLog10(PRAT *phno, uint32_t radix, int32_t precision)
|
||||
{
|
||||
PRAT hno = nullptr;
|
||||
|
||||
NumObjSetIntValue( &hno, 10 );
|
||||
powrat( &hno, *phno, radix, precision);
|
||||
NumObjAssign( phno, hno );
|
||||
NumObjDestroy( &hno );
|
||||
}
|
||||
|
||||
void NumObjNot(PRAT *phno, bool fIntegerMode, PRAT chopNum, uint32_t radix, int32_t precision)
|
||||
{
|
||||
if (radix == 10 && !fIntegerMode)
|
||||
{
|
||||
intrat( phno, radix, precision);
|
||||
addrat( phno, rat_one, precision);
|
||||
NumObjNegate( phno );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
xorrat( phno, chopNum, radix, precision);
|
||||
}
|
||||
}
|
||||
|
||||
void NumObjSin(PRAT *phno, ANGLE_TYPE angletype, uint32_t radix, int32_t precision )
|
||||
{
|
||||
sinanglerat(phno, angletype, radix, precision);
|
||||
}
|
||||
|
||||
void NumObjCos(PRAT *phno, ANGLE_TYPE angletype, uint32_t radix, int32_t precision)
|
||||
{
|
||||
cosanglerat(phno, angletype, radix, precision);
|
||||
}
|
||||
|
||||
void NumObjTan(PRAT *phno, ANGLE_TYPE angletype, uint32_t radix, int32_t precision)
|
||||
{
|
||||
tananglerat(phno, angletype, radix, precision);
|
||||
}
|
||||
|
||||
/******************************************************************\
|
||||
*
|
||||
* Number format conversion routines
|
||||
*
|
||||
* History:
|
||||
* 06-Dec-1996 wrote them
|
||||
\******************************************************************/
|
||||
void NumObjSetIntValue(PRAT *phnol, LONG i )
|
||||
{
|
||||
PRAT pr = nullptr;
|
||||
|
||||
pr = longtorat( i );
|
||||
NumObjAssign( phnol, pr );
|
||||
destroyrat(pr);
|
||||
}
|
||||
|
||||
void NumObjSetIUlongValue(PRAT *phnol, ULONG ul )
|
||||
{
|
||||
PRAT pr = nullptr;
|
||||
|
||||
pr = Ulongtorat( ul );
|
||||
NumObjAssign( phnol, pr );
|
||||
destroyrat(pr);
|
||||
}
|
||||
|
||||
// A Set with 64 bit number
|
||||
void NumObjSetUlonglongValue(PRAT *phnol, ULONGLONG ul, uint32_t radix, int32_t precision)
|
||||
{
|
||||
ULONG hi, lo;
|
||||
PRAT hno = nullptr;
|
||||
|
||||
hi = HIDWORD(ul);
|
||||
lo = LODWORD(ul);
|
||||
NumObjSetIUlongValue(phnol, hi);
|
||||
NumObjSetIntValue(&hno, 32);
|
||||
lshrat(phnol, hno, radix, precision);
|
||||
NumObjSetIUlongValue(&hno, lo);
|
||||
orrat(phnol, hno, radix, precision);
|
||||
NumObjDestroy(&hno);
|
||||
}
|
||||
|
||||
ULONGLONG NumObjGetUlValue( PRAT hnol, uint32_t radix, int32_t precision)
|
||||
{
|
||||
return rattoUlonglong(hnol, radix, precision);
|
||||
}
|
||||
|
||||
wstring NumObjToString(PRAT hnoNum, uint32_t radix, NUMOBJ_FMT fmt, int32_t precision)
|
||||
{
|
||||
return RatToString(hnoNum, fmt, radix, precision);
|
||||
}
|
||||
|
||||
bool NumObjIsZero(PRAT hno)
|
||||
{
|
||||
return zerrat(hno);
|
||||
}
|
||||
|
||||
bool NumObjIsLess(PRAT hno1, PRAT hno2, int32_t precision)
|
||||
{
|
||||
return rat_lt(hno1, hno2, precision);
|
||||
}
|
||||
|
||||
bool NumObjIsLessEq(PRAT hno1, PRAT hno2, int32_t precision)
|
||||
{
|
||||
return rat_le(hno1, hno2, precision);
|
||||
}
|
||||
|
||||
bool NumObjIsGreaterEq(PRAT hno1, PRAT hno2, int32_t precision)
|
||||
{
|
||||
return rat_ge(hno1, hno2, precision);
|
||||
}
|
||||
|
||||
bool NumObjIsEq(PRAT hno1, PRAT hno2, int32_t precision)
|
||||
{
|
||||
return rat_equ(hno1, hno2, precision);
|
||||
}
|
||||
|
||||
void NumObjAssign(PRAT *phnol, PRAT hnor)
|
||||
{
|
||||
DUPRAT(*phnol, hnor);
|
||||
}
|
||||
|
||||
int32_t NumObjGetExp(PRAT hno)
|
||||
{
|
||||
return LOGRATRADIX(hno);
|
||||
}
|
||||
|
||||
void NumObjDestroy(PRAT *phno)
|
||||
{
|
||||
destroyrat(*phno);
|
||||
}
|
201
src/CalcManager/CEngine/scioper.cpp
Normal file
201
src/CalcManager/CEngine/scioper.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Header Files/CalcEngine.h"
|
||||
|
||||
using namespace CalcEngine;
|
||||
|
||||
// Routines to perform standard operations &|^~<<>>+-/*% and pwr.
|
||||
CalcEngine::Rational CCalcEngine::DoOperation(int operation, CalcEngine::Rational const& lhs, CalcEngine::Rational const& rhs)
|
||||
{
|
||||
PRAT rhsRat = rhs.ToPRAT();
|
||||
PRAT hno = nullptr;
|
||||
|
||||
// Remove any variance in how 0 could be represented in rat e.g. -0, 0/n, etc.
|
||||
PRAT resultRat = nullptr;
|
||||
if (lhs.IsZero())
|
||||
{
|
||||
NumObjAssign(&resultRat, rat_zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultRat = lhs.ToPRAT();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (operation)
|
||||
{
|
||||
case IDC_AND:
|
||||
andrat(&resultRat, rhsRat, m_radix, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_OR:
|
||||
orrat(&resultRat, rhsRat, m_radix, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_XOR:
|
||||
xorrat(&resultRat, rhsRat, m_radix, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_RSHF:
|
||||
{
|
||||
if (m_fIntegerMode)
|
||||
{
|
||||
NumObjSetIntValue(&hno, m_dwWordBitWidth);
|
||||
if (NumObjIsGreaterEq(resultRat, hno, m_precision)) // Lsh/Rsh >= than current word size is always 0
|
||||
{
|
||||
throw CALC_E_NORESULT;
|
||||
}
|
||||
}
|
||||
|
||||
NumObjAssign(&hno, resultRat);
|
||||
NumObjAssign(&resultRat, rhsRat);
|
||||
|
||||
uint64_t w64Bits = NumObjGetUlValue(resultRat, m_radix, m_precision);
|
||||
bool fMsb = (w64Bits >> (m_dwWordBitWidth - 1)) & 1;
|
||||
|
||||
rshrat(&resultRat, hno, m_radix, m_precision);
|
||||
|
||||
if (fMsb)
|
||||
{
|
||||
intrat(&resultRat, m_radix, m_precision);
|
||||
PRAT temp = m_chopNumbers[m_numwidth].ToPRAT();
|
||||
PRAT chopRat = m_chopNumbers[m_numwidth].ToPRAT();
|
||||
rshrat(&temp, hno, m_radix, m_precision);
|
||||
intrat(&temp, m_radix, m_precision);
|
||||
xorrat(&temp, chopRat, m_radix, m_precision);
|
||||
orrat(&resultRat, temp, m_radix, m_precision);
|
||||
destroyrat(temp);
|
||||
destroyrat(chopRat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IDC_LSHF:
|
||||
if (m_fIntegerMode)
|
||||
{
|
||||
NumObjSetIntValue(&hno, m_dwWordBitWidth);
|
||||
if (NumObjIsGreaterEq(resultRat, hno, m_precision))
|
||||
{
|
||||
throw CALC_E_NORESULT;
|
||||
}
|
||||
}
|
||||
|
||||
NumObjAssign(&hno, resultRat);
|
||||
NumObjAssign(&resultRat, rhsRat);
|
||||
|
||||
lshrat(&resultRat, hno, m_radix, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_ADD:
|
||||
addrat(&resultRat, rhsRat, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_SUB:
|
||||
// in order to do ( rhsRat - resultRat ) we actually do -(resultRat - rhsRat ) because it's quicker
|
||||
subrat(&resultRat, rhsRat, m_precision);
|
||||
NumObjNegate(&resultRat);
|
||||
break;
|
||||
|
||||
case IDC_MUL:
|
||||
mulrat(&resultRat, rhsRat, m_precision);
|
||||
break;
|
||||
|
||||
case IDC_DIV:
|
||||
case IDC_MOD:
|
||||
{
|
||||
int iNumeratorSign = 1, iDenominatorSign = 1, iFinalSign = 1;
|
||||
// REVIEW: These lengthly number assignments can be replaced with some quick pointer swaps.
|
||||
// the swaps cannot change the value of pratX unless we also modify the code that calls
|
||||
// the DoOperation function.
|
||||
NumObjAssign(&hno, resultRat);
|
||||
NumObjAssign(&resultRat, rhsRat);
|
||||
|
||||
if(m_fIntegerMode)
|
||||
{
|
||||
uint64_t w64Bits = NumObjGetUlValue(resultRat, m_radix, m_precision);
|
||||
bool fMsb = (w64Bits >> (m_dwWordBitWidth - 1)) & 1;
|
||||
|
||||
if (fMsb)
|
||||
{
|
||||
PRAT chopRat = m_chopNumbers[m_numwidth].ToPRAT();
|
||||
NumObjNot(&resultRat, true, chopRat, m_radix, m_precision);
|
||||
destroyrat(chopRat);
|
||||
addrat(&resultRat, rat_one, m_precision);
|
||||
|
||||
iNumeratorSign = -1;
|
||||
}
|
||||
|
||||
w64Bits = NumObjGetUlValue(hno, m_radix, m_precision);
|
||||
fMsb = (w64Bits >> (m_dwWordBitWidth - 1)) & 1;
|
||||
|
||||
if (fMsb)
|
||||
{
|
||||
PRAT chopRat = m_chopNumbers[m_numwidth].ToPRAT();
|
||||
NumObjNot( &hno, true, chopRat, m_radix, m_precision);
|
||||
destroyrat(chopRat);
|
||||
addrat( &hno, rat_one, m_precision);
|
||||
|
||||
iDenominatorSign = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (operation == IDC_DIV)
|
||||
{
|
||||
iFinalSign = iNumeratorSign * iDenominatorSign;
|
||||
divrat(&resultRat, hno, m_precision);
|
||||
}
|
||||
else
|
||||
{
|
||||
iFinalSign = iNumeratorSign;
|
||||
modrat(&resultRat, hno );
|
||||
}
|
||||
|
||||
if (m_fIntegerMode && iFinalSign == -1)
|
||||
{
|
||||
intrat(&resultRat, m_radix, m_precision);
|
||||
NumObjNegate(&resultRat);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case IDC_PWR: // Calculates rhsRat to the resultRat(th) power.
|
||||
{
|
||||
NumObjAssign(&hno, resultRat);
|
||||
NumObjAssign(&resultRat, rhsRat);
|
||||
|
||||
powrat(&resultRat, hno , m_radix, m_precision);
|
||||
|
||||
break;
|
||||
}
|
||||
case IDC_ROOT: // Calculates rhsRat to the resultRat(th) root.
|
||||
{
|
||||
NumObjAssign(&hno, resultRat);
|
||||
NumObjAssign(&resultRat, rhsRat);
|
||||
|
||||
rootrat(&resultRat, hno, m_radix, m_precision);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (DWORD dwErrCode)
|
||||
{
|
||||
DisplayError(dwErrCode);
|
||||
|
||||
// On error, return the original value
|
||||
destroyrat(resultRat);
|
||||
resultRat = lhs.ToPRAT();
|
||||
}
|
||||
|
||||
destroyrat(hno);
|
||||
destroyrat(rhsRat);
|
||||
|
||||
Rational result{ resultRat };
|
||||
destroyrat(resultRat);
|
||||
|
||||
return result;
|
||||
}
|
213
src/CalcManager/CEngine/sciset.cpp
Normal file
213
src/CalcManager/CEngine/sciset.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/**************************************************************************/
|
||||
/*** SCICALC Scientific Calculator for Windows 3.00.12 ***/
|
||||
/*** (c)1989 Microsoft Corporation. All Rights Reserved. ***/
|
||||
/*** ***/
|
||||
/*** sciset.c ***/
|
||||
/*** ***/
|
||||
/*** Functions contained: ***/
|
||||
/*** ***/
|
||||
/*** Functions called: ***/
|
||||
/*** none ***/
|
||||
/*** ***/
|
||||
/*** History: ***/
|
||||
/*** 12-Dec-1996 Added SetMaxIntDigits ***/
|
||||
/*** Whenever-97 Removed SetMaxIntDigits ***/
|
||||
/*** ***/
|
||||
/**************************************************************************/
|
||||
#include "pch.h"
|
||||
#include "Header Files/CalcEngine.h"
|
||||
|
||||
using namespace CalcEngine;
|
||||
|
||||
//
|
||||
// To be called when either the radix or num width changes. You can use -1 in either of these values to mean
|
||||
// dont change that.
|
||||
void CCalcEngine::SetRadixTypeAndNumWidth(RADIX_TYPE radixtype, NUM_WIDTH numwidth)
|
||||
{
|
||||
// When in integer mode, the number is represented in 2's complement form. When a bit width is changing, we can
|
||||
// change the number representation back to sign, abs num form in ratpak. Soon when display sees this, it will
|
||||
// convert to 2's complement form, but this time all high bits will be propogated. Eg. -127, in byte mode is
|
||||
// represented as 1000,0001. This puts it back as sign=-1, 01111111 . But DisplayNum will see this and convert it
|
||||
// back to 1111,1111,1000,0001 when in Word mode.
|
||||
if (m_fIntegerMode)
|
||||
{
|
||||
PRAT curRat = m_currentVal.ToPRAT();
|
||||
ULONGLONG w64Bits = NumObjGetUlValue(curRat, m_radix, m_precision);
|
||||
bool fMsb = (w64Bits >> (m_dwWordBitWidth - 1)) & 1; // make sure you use the old width
|
||||
|
||||
if (fMsb)
|
||||
{
|
||||
// If high bit is set, then get the decimal number in -ve 2'scompl form.
|
||||
PRAT chopRat = m_chopNumbers[m_numwidth].ToPRAT();
|
||||
NumObjNot(&curRat, true, chopRat, m_radix, m_precision);
|
||||
destroyrat(chopRat);
|
||||
addrat(&curRat, rat_one, m_precision);
|
||||
NumObjNegate(&curRat);
|
||||
m_currentVal = Rational{ curRat };
|
||||
}
|
||||
|
||||
destroyrat(curRat);
|
||||
}
|
||||
|
||||
if (radixtype >= HEX_RADIX && radixtype <= BIN_RADIX)
|
||||
{
|
||||
m_radix = NRadixFromRadixType(radixtype);
|
||||
// radixtype is not even saved
|
||||
}
|
||||
|
||||
if (numwidth >= QWORD_WIDTH && numwidth <= BYTE_WIDTH)
|
||||
{
|
||||
m_numwidth = numwidth;
|
||||
m_dwWordBitWidth = DwWordBitWidthFromeNumWidth(numwidth);
|
||||
}
|
||||
|
||||
// inform ratpak that a change in base or precision has occured
|
||||
BaseOrPrecisionChanged();
|
||||
|
||||
// display the correct number for the new state (ie convert displayed
|
||||
// number to correct base)
|
||||
DisplayNum();
|
||||
}
|
||||
|
||||
LONG CCalcEngine::DwWordBitWidthFromeNumWidth(NUM_WIDTH /*numwidth*/)
|
||||
{
|
||||
static constexpr int nBitMax[] = { 64, 32, 16, 8 };
|
||||
LONG wmax = nBitMax[0];
|
||||
|
||||
if (m_numwidth >= 0 && m_numwidth < ARRAYSIZE(nBitMax))
|
||||
{
|
||||
wmax = nBitMax[m_numwidth];
|
||||
}
|
||||
return wmax;
|
||||
}
|
||||
|
||||
uint32_t CCalcEngine::NRadixFromRadixType( RADIX_TYPE radixtype)
|
||||
{
|
||||
static constexpr uint32_t rgnRadish[4]={16, 10, 8, 2}; /* Number bases in the same order as radixtype */
|
||||
uint32_t radix = 10;
|
||||
|
||||
// convert special bases into symbolic values
|
||||
if (radixtype >= 0 && radixtype < ARRAYSIZE(rgnRadish))
|
||||
{
|
||||
radix = rgnRadish[radixtype];
|
||||
}
|
||||
return radix;
|
||||
}
|
||||
|
||||
// Toggles a given bit into the number representation. returns true if it changed it actually.
|
||||
bool CCalcEngine::TryToggleBit(CalcEngine::Rational& rat, DWORD wbitno)
|
||||
{
|
||||
DWORD wmax = DwWordBitWidthFromeNumWidth(m_numwidth);
|
||||
if (wbitno >= wmax)
|
||||
{
|
||||
return false; // ignore error cant happen
|
||||
}
|
||||
|
||||
PRAT hnumPow = nullptr;
|
||||
NumObjAssign(&hnumPow, rat_two);
|
||||
PRAT hnum = longtorat(wbitno);
|
||||
powrat(&hnumPow, hnum, m_radix, m_precision);
|
||||
|
||||
PRAT resultRat = rat.ToPRAT();
|
||||
intrat(&resultRat, m_radix, m_precision);
|
||||
if (NumObjIsZero(resultRat))
|
||||
{
|
||||
// This is the same work around happenning in SciCalcFunctions. Ought to move to intrat function itself.
|
||||
// Basic bug is there which doesn treat 0/ n as 0, or -0 as 0 etc.
|
||||
NumObjAssign(&resultRat, rat_zero);
|
||||
}
|
||||
|
||||
xorrat(&resultRat, hnumPow, m_radix, m_precision);
|
||||
rat = Rational{ resultRat };
|
||||
destroyrat(resultRat);
|
||||
NumObjDestroy(&hnumPow);
|
||||
NumObjDestroy(&hnum);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns the nearest power of two
|
||||
int CCalcEngine::QuickLog2(int iNum)
|
||||
{
|
||||
int iRes = 0;
|
||||
|
||||
// while first digit is a zero
|
||||
while (!(iNum & 1))
|
||||
{
|
||||
iRes++;
|
||||
iNum >>= 1;
|
||||
}
|
||||
|
||||
// if our number isn't a perfect square
|
||||
iNum = iNum >> 1;
|
||||
if (iNum)
|
||||
{
|
||||
// find the largest digit
|
||||
for (iNum = iNum >> 1; iNum; iNum = iNum >> 1)
|
||||
++iRes;
|
||||
|
||||
// and then add two
|
||||
iRes += 2;
|
||||
}
|
||||
|
||||
return iRes;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// UpdateMaxIntDigits
|
||||
//
|
||||
// determine the maximum number of digits needed for the current precision,
|
||||
// word size, and base. This number is conservative towards the small side
|
||||
// such that there may be some extra bits left over. For example, base 8 requires 3 bits per digit.
|
||||
// A word size of 32 bits allows for 10 digits with a remainder of two bits. Bases
|
||||
// that require variable numnber of bits (non-power-of-two bases) are approximated
|
||||
// by the next highest power-of-two base (again, to be conservative and gaurentee
|
||||
// there will be no over flow verse the current word size for numbers entered).
|
||||
// Base 10 is a special case and always uses the base 10 precision (m_nPrecisionSav).
|
||||
void CCalcEngine::UpdateMaxIntDigits()
|
||||
{
|
||||
if (m_radix == 10)
|
||||
{
|
||||
// if in integer mode you still have to honor the max digits you can enter based on bit width
|
||||
if (m_fIntegerMode)
|
||||
{
|
||||
m_cIntDigitsSav = static_cast<int>(m_maxDecimalValueStrings[m_numwidth].length()) - 1;
|
||||
// This is the max digits you can enter a decimal in fixed width mode aka integer mode -1. The last digit
|
||||
// has to be checked separately
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cIntDigitsSav = m_precision;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cIntDigitsSav = m_dwWordBitWidth / CCalcEngine::QuickLog2(m_radix);
|
||||
}
|
||||
}
|
||||
|
||||
void CCalcEngine::ChangeBaseConstants(uint32_t radix, int maxIntDigits, int32_t precision)
|
||||
{
|
||||
if (10 == radix)
|
||||
{
|
||||
ChangeConstants(radix, precision); // Base 10 precesion for internal computing still needs to be 32, to
|
||||
// take care of decimals preceisly. For eg. to get the HI word of a qword, we do a rsh, which depends on getting
|
||||
// 18446744073709551615 / 4294967296 = 4294967295.9999917... This is important it works this and doesnt reduce
|
||||
// the precision to number of digits allowed to enter. In otherwords precision and # of allowed digits to be
|
||||
// entered are different.
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeConstants(radix, maxIntDigits + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void CCalcEngine::BaseOrPrecisionChanged()
|
||||
{
|
||||
UpdateMaxIntDigits();
|
||||
CCalcEngine::ChangeBaseConstants(m_radix, m_cIntDigitsSav, m_precision);
|
||||
}
|
Reference in New Issue
Block a user