diff --git a/src/CalcManager/CalcManager.vcxproj b/src/CalcManager/CalcManager.vcxproj index 4d03a5c9..df999574 100644 --- a/src/CalcManager/CalcManager.vcxproj +++ b/src/CalcManager/CalcManager.vcxproj @@ -309,6 +309,7 @@ + @@ -349,6 +350,7 @@ Create Create + diff --git a/src/CalcManager/CalcManager.vcxproj.filters b/src/CalcManager/CalcManager.vcxproj.filters index 2ca11666..53098917 100644 --- a/src/CalcManager/CalcManager.vcxproj.filters +++ b/src/CalcManager/CalcManager.vcxproj.filters @@ -89,6 +89,7 @@ CEngine + @@ -160,5 +161,6 @@ Header Files + - + \ No newline at end of file diff --git a/src/CalcManager/NumberFormattingUtils.cpp b/src/CalcManager/NumberFormattingUtils.cpp new file mode 100644 index 00000000..720335ad --- /dev/null +++ b/src/CalcManager/NumberFormattingUtils.cpp @@ -0,0 +1,85 @@ +#include "pch.h" +#include "NumberFormattingUtils.h" +#include +#include +using namespace std; + +namespace CalculationManager::NumberFormattingUtils +{ + /// + /// Trims out any trailing zeros or decimals in the given input string + /// + /// number to trim + void TrimTrailingZeros(_Inout_ wstring& number) + { + if (number.find(L'.') == wstring::npos) + { + return; + } + + wstring::iterator iter; + for (iter = number.end() - 1;; iter--) + { + if (*iter != L'0') + { + number.erase(iter + 1, number.end()); + break; + } + } + if (*(number.end() - 1) == L'.') + { + number.erase(number.end() - 1, number.end()); + } + } + + /// + /// Get number of digits (whole number part + decinmal part) + /// the number + unsigned int GetNumberDigits(wstring value) + { + TrimTrailingZeros(value); + unsigned int numberSignificantDigits = static_cast(value.size()); + if (value.find(L'.') != wstring::npos) + { + --numberSignificantDigits; + } + if (value.find(L'-') != wstring::npos) + { + --numberSignificantDigits; + } + return numberSignificantDigits; + } + + /// + /// Get number of digits (whole number part only) + /// the number + unsigned int GetNumberDigitsWholeNumberPart(double value) + { + return value == 0 ? 1 : (1 + (int)log10(abs(value))); + } + + /// + /// Rounds the given double to the given number of significant digits + /// + /// input double + /// int number of significant digits to round to + wstring RoundSignificantDigits(double num, int numSignificant) + { + wstringstream out(wstringstream::out); + out << fixed; + out.precision(numSignificant); + out << num; + return out.str(); + } + + /// + /// Convert a Number to Scientific Notation + /// + /// number to convert + wstring ToScientificNumber(double number) + { + wstringstream out(wstringstream::out); + out << scientific << number; + return out.str(); + } +} diff --git a/src/CalcManager/NumberFormattingUtils.h b/src/CalcManager/NumberFormattingUtils.h new file mode 100644 index 00000000..896ecd76 --- /dev/null +++ b/src/CalcManager/NumberFormattingUtils.h @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include + +namespace CalculationManager::NumberFormattingUtils +{ + void TrimTrailingZeros(_Inout_ std::wstring& input); + unsigned int GetNumberDigits(std::wstring value); + unsigned int GetNumberDigitsWholeNumberPart(double value); + std::wstring RoundSignificantDigits(double value, int numberSignificantDigits); + std::wstring ToScientificNumber(double number); +} diff --git a/src/CalcManager/UnitConverter.cpp b/src/CalcManager/UnitConverter.cpp index 46b84ea3..3784d65e 100644 --- a/src/CalcManager/UnitConverter.cpp +++ b/src/CalcManager/UnitConverter.cpp @@ -6,10 +6,12 @@ #include // for std::sort #include "Command.h" #include "UnitConverter.h" +#include "NumberFormattingUtils.h" using namespace concurrency; using namespace std; using namespace UnitConversionManager; +using namespace CalculationManager::NumberFormattingUtils; static constexpr uint32_t EXPECTEDSERIALIZEDCATEGORYTOKENCOUNT = 3; static constexpr uint32_t EXPECTEDSERIALIZEDUNITTOKENCOUNT = 6; @@ -633,19 +635,19 @@ vector> UnitConverter::CalculateSuggested() wstring roundedString; if (abs(entry.value) < 100) { - roundedString = RoundSignificant(entry.value, 2); + roundedString = RoundSignificantDigits(entry.value, 2); } else if (abs(entry.value) < 1000) { - roundedString = RoundSignificant(entry.value, 1); + roundedString = RoundSignificantDigits(entry.value, 1); } else { - roundedString = RoundSignificant(entry.value, 0); + roundedString = RoundSignificantDigits(entry.value, 0); } if (stod(roundedString) != 0.0 || m_currentCategory.supportsNegative) { - TrimString(roundedString); + TrimTrailingZeros(roundedString); returnVector.push_back(make_tuple(roundedString, entry.type)); } } @@ -671,21 +673,21 @@ vector> UnitConverter::CalculateSuggested() wstring roundedString; if (abs(entry.value) < 100) { - roundedString = RoundSignificant(entry.value, 2); + roundedString = RoundSignificantDigits(entry.value, 2); } else if (abs(entry.value) < 1000) { - roundedString = RoundSignificant(entry.value, 1); + roundedString = RoundSignificantDigits(entry.value, 1); } else { - roundedString = RoundSignificant(entry.value, 0); + roundedString = RoundSignificantDigits(entry.value, 0); } // How to work out which is the best whimsical value to add to the vector? if (stod(roundedString) != 0.0) { - TrimString(roundedString); + TrimTrailingZeros(roundedString); whimsicalReturnVector.push_back(make_tuple(roundedString, entry.type)); } } @@ -842,7 +844,7 @@ void UnitConverter::Calculate() { m_returnDisplay = m_currentDisplay; m_returnHasDecimal = m_currentHasDecimal; - TrimString(m_returnDisplay); + TrimTrailingZeros(m_returnDisplay); UpdateViewModel(); return; } @@ -852,7 +854,7 @@ void UnitConverter::Calculate() { m_returnDisplay = m_currentDisplay; m_returnHasDecimal = m_currentHasDecimal; - TrimString(m_returnDisplay); + TrimTrailingZeros(m_returnDisplay); } else { @@ -863,21 +865,19 @@ void UnitConverter::Calculate() if (isCurrencyConverter) { // We don't need to trim the value when it's a currency. - m_returnDisplay = RoundSignificant(returnValue, MAXIMUMDIGITSALLOWED); - TrimString(m_returnDisplay); + m_returnDisplay = RoundSignificantDigits(returnValue, MAXIMUMDIGITSALLOWED); + TrimTrailingZeros(m_returnDisplay); } else { - int numPreDecimal = returnValue == 0 ? 0 : (1 + (int)log10(abs(returnValue))); + int numPreDecimal = GetNumberDigitsWholeNumberPart(returnValue); if (numPreDecimal > MAXIMUMDIGITSALLOWED || (returnValue != 0 && abs(returnValue) < MINIMUMDECIMALALLOWED)) { - wstringstream out(wstringstream::out); - out << scientific << returnValue; - m_returnDisplay = out.str(); + m_returnDisplay = ToScientificNumber(returnValue); } else { - int currentNumberSignificantDigits = GetNumberSignificantDigits(m_currentDisplay); + int currentNumberSignificantDigits = GetNumberDigits(m_currentDisplay); int precision = 0; if (abs(returnValue) < OPTIMALDECIMALALLOWED) { @@ -888,8 +888,8 @@ void UnitConverter::Calculate() precision = max(0, max(OPTIMALDIGITSALLOWED, min(MAXIMUMDIGITSALLOWED, currentNumberSignificantDigits)) - numPreDecimal); } - m_returnDisplay = RoundSignificant(returnValue, precision); - TrimString(m_returnDisplay); + m_returnDisplay = RoundSignificantDigits(returnValue, precision); + TrimTrailingZeros(m_returnDisplay); } m_returnHasDecimal = (m_returnDisplay.find(L'.') != wstring::npos); } @@ -897,64 +897,6 @@ void UnitConverter::Calculate() UpdateViewModel(); } -/// -/// Trims out any trailing zeros or decimals in the given input string -/// -/// wstring to trim -void UnitConverter::TrimString(_Inout_ wstring& returnString) -{ - if (returnString.find(L'.') == returnString.npos) - { - return; - } - - wstring::iterator iter; - for (iter = returnString.end() - 1;; iter--) - { - if (*iter != L'0') - { - returnString.erase(iter + 1, returnString.end()); - break; - } - } - if (*(returnString.end() - 1) == L'.') - { - returnString.erase(returnString.end() - 1, returnString.end()); - } -} - -/// -/// Get number of significant digits (integer part + fractional part) of a number -/// the number -unsigned int UnitConverter::GetNumberSignificantDigits(std::wstring value) -{ - TrimString(value); - unsigned int numberSignificantDigits = static_cast(value.size()); - if (value.find(L'.') != wstring::npos) - { - --numberSignificantDigits; - } - if (value.find(L'-') != wstring::npos) - { - --numberSignificantDigits; - } - return numberSignificantDigits; -} - -/// -/// Rounds the given double to the given number of significant digits -/// -/// input double -/// int number of significant digits to round to -wstring UnitConverter::RoundSignificant(double num, int numSignificant) -{ - wstringstream out(wstringstream::out); - out << fixed; - out.precision(numSignificant); - out << num; - return out.str(); -} - void UnitConverter::UpdateCurrencySymbols() { if (m_currencyDataLoader != nullptr && m_vmCurrencyCallback != nullptr) diff --git a/src/CalcManager/UnitConverter.h b/src/CalcManager/UnitConverter.h index 58712386..e8c26d38 100644 --- a/src/CalcManager/UnitConverter.h +++ b/src/CalcManager/UnitConverter.h @@ -290,10 +290,6 @@ namespace UnitConversionManager std::shared_ptr GetDataLoaderForCategory(const Category& category); std::shared_ptr GetCurrencyConverterDataLoader(); - static void TrimString(_Inout_ std::wstring& input); - static unsigned int GetNumberSignificantDigits(std::wstring value); - static std::wstring RoundSignificant(double num, int numSignificant); - private: std::shared_ptr m_dataLoader; std::shared_ptr m_currencyDataLoader; diff --git a/src/CalculatorUnitTests/CalculatorManagerTest.cpp b/src/CalculatorUnitTests/CalculatorManagerTest.cpp index a2ed8127..5f46258c 100644 --- a/src/CalculatorUnitTests/CalculatorManagerTest.cpp +++ b/src/CalculatorUnitTests/CalculatorManagerTest.cpp @@ -7,9 +7,11 @@ #include "CalcManager/CalculatorHistory.h" #include "CalcViewModel/Common/EngineResourceProvider.h" +#include "CalcManager/NumberFormattingUtils.h" using namespace CalculatorApp; using namespace CalculationManager; +using namespace CalculationManager::NumberFormattingUtils; using namespace Platform; using namespace std; using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -185,6 +187,11 @@ namespace CalculatorManagerTest TEST_METHOD(CalculatorManagerTestMaxDigitsReached_LeadingDecimal); TEST_METHOD(CalculatorManagerTestMaxDigitsReached_TrailingDecimal); + TEST_METHOD(CalculatorManagerNumberFormattingUtils_TrimTrailingZeros); + TEST_METHOD(CalculatorManagerNumberFormattingUtils_GetNumberDigits); + TEST_METHOD(CalculatorManagerNumberFormattingUtils_GetNumberDigitsWholeNumberPart); + TEST_METHOD(CalculatorManagerNumberFormattingUtils_RoundSignificantDigits); + TEST_METHOD(CalculatorManagerNumberFormattingUtils_ToScientificNumber); // TODO re-enable when cause of failure is determined. Bug 20226670 // TEST_METHOD(CalculatorManagerTestBinaryOperatorReceived); // TEST_METHOD(CalculatorManagerTestBinaryOperatorReceived_Multiple); @@ -807,6 +814,102 @@ namespace CalculatorManagerTest TestMaxDigitsReachedScenario(L"123,456,789,101,112.13"); } + void CalculatorManagerTest::CalculatorManagerNumberFormattingUtils_TrimTrailingZeros() + { + wstring number = L"2.1032100000000"; + TrimTrailingZeros(number); + VERIFY_ARE_EQUAL(number, L"2.10321"); + number = L"-122.123200"; + TrimTrailingZeros(number); + VERIFY_ARE_EQUAL(number, L"-122.1232"); + number = L"0.0001200"; + TrimTrailingZeros(number); + VERIFY_ARE_EQUAL(number, L"0.00012"); + number = L"12.000"; + TrimTrailingZeros(number); + VERIFY_ARE_EQUAL(number, L"12"); + number = L"-12.00000"; + TrimTrailingZeros(number); + VERIFY_ARE_EQUAL(number, L"-12"); + number = L"0.000"; + TrimTrailingZeros(number); + VERIFY_ARE_EQUAL(number, L"0"); + number = L"322423"; + TrimTrailingZeros(number); + VERIFY_ARE_EQUAL(number, L"322423"); + } + + void CalculatorManagerTest::CalculatorManagerNumberFormattingUtils_GetNumberDigits() + { + wstring number = L"2.10321"; + unsigned int digitsCount = GetNumberDigits(number); + VERIFY_ARE_EQUAL(digitsCount, 6); + number = L"-122.1232"; + digitsCount = GetNumberDigits(number); + VERIFY_ARE_EQUAL(digitsCount, 7); + number = L"-3432"; + digitsCount = GetNumberDigits(number); + VERIFY_ARE_EQUAL(digitsCount, 4); + number = L"0"; + digitsCount = GetNumberDigits(number); + VERIFY_ARE_EQUAL(digitsCount, 1); + number = L"0.0001223"; + digitsCount = GetNumberDigits(number); + VERIFY_ARE_EQUAL(digitsCount, 8); + } + + void CalculatorManagerTest::CalculatorManagerNumberFormattingUtils_GetNumberDigitsWholeNumberPart() + { + unsigned int digitsCount = GetNumberDigitsWholeNumberPart(2.10321); + VERIFY_ARE_EQUAL(digitsCount, 1); + digitsCount = GetNumberDigitsWholeNumberPart(-122.1232); + VERIFY_ARE_EQUAL(digitsCount, 3); + digitsCount = GetNumberDigitsWholeNumberPart(-3432); + VERIFY_ARE_EQUAL(digitsCount, 4); + digitsCount = GetNumberDigitsWholeNumberPart(0); + VERIFY_ARE_EQUAL(digitsCount, 1); + digitsCount = GetNumberDigitsWholeNumberPart(324328412837382); + VERIFY_ARE_EQUAL(digitsCount, 15); + digitsCount = GetNumberDigitsWholeNumberPart(324328412837382.232213214324234); + VERIFY_ARE_EQUAL(digitsCount, 15); + } + + void CalculatorManagerTest::CalculatorManagerNumberFormattingUtils_RoundSignificantDigits() + { + wstring result = RoundSignificantDigits(12.342343242, 3); + VERIFY_ARE_EQUAL(result, L"12.342"); + result = RoundSignificantDigits(12.3429999, 3); + VERIFY_ARE_EQUAL(result, L"12.343"); + result = RoundSignificantDigits(12.342500001, 3); + VERIFY_ARE_EQUAL(result, L"12.343"); + result = RoundSignificantDigits(-2312.1244243346454345, 5); + VERIFY_ARE_EQUAL(result, L"-2312.12442"); + result = RoundSignificantDigits(0.3423432423, 5); + VERIFY_ARE_EQUAL(result, L"0.34234"); + result = RoundSignificantDigits(0.3423, 7); + VERIFY_ARE_EQUAL(result, L"0.3423000"); + } + + void CalculatorManagerTest::CalculatorManagerNumberFormattingUtils_ToScientificNumber() + { + wstring result = ToScientificNumber(3423); + VERIFY_ARE_EQUAL(result, L"3.423000e+03"); + result = ToScientificNumber(-21); + VERIFY_ARE_EQUAL(result, L"-2.100000e+01"); + result = ToScientificNumber(0.0232); + VERIFY_ARE_EQUAL(result, L"2.320000e-02"); + result = ToScientificNumber(-0.00921); + VERIFY_ARE_EQUAL(result, L"-9.210000e-03"); + result = ToScientificNumber(2343243345677); + VERIFY_ARE_EQUAL(result, L"2.343243e+12"); + result = ToScientificNumber(-3432474247332942); + VERIFY_ARE_EQUAL(result, L"-3.432474e+15"); + result = ToScientificNumber(0.000000003432432); + VERIFY_ARE_EQUAL(result, L"3.432432e-09"); + result = ToScientificNumber(-0.000000003432432); + VERIFY_ARE_EQUAL(result, L"-3.432432e-09"); + } + // TODO re-enable when cause of failure is determined. Bug 20226670 // void CalculatorManagerTest::CalculatorManagerTestBinaryOperatorReceived() // {