Move UnitConverter static methods related to number to NumberFormattingUtils

This commit is contained in:
Rudy Huyn 2019-05-17 00:57:51 -07:00
commit f9d5094841
7 changed files with 227 additions and 82 deletions

View file

@ -309,6 +309,7 @@
<ClInclude Include="Ratpack\CalcErr.h" /> <ClInclude Include="Ratpack\CalcErr.h" />
<ClInclude Include="Ratpack\ratconst.h" /> <ClInclude Include="Ratpack\ratconst.h" />
<ClInclude Include="Ratpack\ratpak.h" /> <ClInclude Include="Ratpack\ratpak.h" />
<ClInclude Include="NumberFormattingUtils.h" />
<ClInclude Include="UnitConverter.h" /> <ClInclude Include="UnitConverter.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -349,6 +350,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile> </ClCompile>
<ClCompile Include="NumberFormattingUtils.cpp" />
<ClCompile Include="UnitConverter.cpp" /> <ClCompile Include="UnitConverter.cpp" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View file

@ -89,6 +89,7 @@
<ClCompile Include="CEngine\RationalMath.cpp"> <ClCompile Include="CEngine\RationalMath.cpp">
<Filter>CEngine</Filter> <Filter>CEngine</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="NumberFormattingUtils.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Command.h" /> <ClInclude Include="Command.h" />
@ -160,5 +161,6 @@
<ClInclude Include="Header Files\RationalMath.h"> <ClInclude Include="Header Files\RationalMath.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="NumberFormattingUtils.h" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -0,0 +1,85 @@
#include "pch.h"
#include "NumberFormattingUtils.h"
#include <iostream>
#include <math.h>
using namespace std;
namespace CalculationManager::NumberFormattingUtils
{
/// <summary>
/// Trims out any trailing zeros or decimals in the given input string
/// </summary>
/// <param name="number">number to trim</param>
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());
}
}
/// <summary>
/// Get number of digits (whole number part + decinmal part)</summary>
/// <param name="value">the number</param>
unsigned int GetNumberDigits(wstring value)
{
TrimTrailingZeros(value);
unsigned int numberSignificantDigits = static_cast<unsigned int>(value.size());
if (value.find(L'.') != wstring::npos)
{
--numberSignificantDigits;
}
if (value.find(L'-') != wstring::npos)
{
--numberSignificantDigits;
}
return numberSignificantDigits;
}
/// <summary>
/// Get number of digits (whole number part only)</summary>
/// <param name="value">the number</param>
unsigned int GetNumberDigitsWholeNumberPart(double value)
{
return value == 0 ? 1 : (1 + (int)log10(abs(value)));
}
/// <summary>
/// Rounds the given double to the given number of significant digits
/// </summary>
/// <param name="num">input double</param>
/// <param name="numSignificant">int number of significant digits to round to</param>
wstring RoundSignificantDigits(double num, int numSignificant)
{
wstringstream out(wstringstream::out);
out << fixed;
out.precision(numSignificant);
out << num;
return out.str();
}
/// <summary>
/// Convert a Number to Scientific Notation
/// </summary>
/// <param name="number">number to convert</param>
wstring ToScientificNumber(double number)
{
wstringstream out(wstringstream::out);
out << scientific << number;
return out.str();
}
}

View file

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include <string>
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);
}

View file

@ -6,10 +6,12 @@
#include <algorithm> // for std::sort #include <algorithm> // for std::sort
#include "Command.h" #include "Command.h"
#include "UnitConverter.h" #include "UnitConverter.h"
#include "NumberFormattingUtils.h"
using namespace concurrency; using namespace concurrency;
using namespace std; using namespace std;
using namespace UnitConversionManager; using namespace UnitConversionManager;
using namespace CalculationManager::NumberFormattingUtils;
static constexpr uint32_t EXPECTEDSERIALIZEDCATEGORYTOKENCOUNT = 3; static constexpr uint32_t EXPECTEDSERIALIZEDCATEGORYTOKENCOUNT = 3;
static constexpr uint32_t EXPECTEDSERIALIZEDUNITTOKENCOUNT = 6; static constexpr uint32_t EXPECTEDSERIALIZEDUNITTOKENCOUNT = 6;
@ -633,19 +635,19 @@ vector<tuple<wstring, Unit>> UnitConverter::CalculateSuggested()
wstring roundedString; wstring roundedString;
if (abs(entry.value) < 100) if (abs(entry.value) < 100)
{ {
roundedString = RoundSignificant(entry.value, 2); roundedString = RoundSignificantDigits(entry.value, 2);
} }
else if (abs(entry.value) < 1000) else if (abs(entry.value) < 1000)
{ {
roundedString = RoundSignificant(entry.value, 1); roundedString = RoundSignificantDigits(entry.value, 1);
} }
else else
{ {
roundedString = RoundSignificant(entry.value, 0); roundedString = RoundSignificantDigits(entry.value, 0);
} }
if (stod(roundedString) != 0.0 || m_currentCategory.supportsNegative) if (stod(roundedString) != 0.0 || m_currentCategory.supportsNegative)
{ {
TrimString(roundedString); TrimTrailingZeros(roundedString);
returnVector.push_back(make_tuple(roundedString, entry.type)); returnVector.push_back(make_tuple(roundedString, entry.type));
} }
} }
@ -671,21 +673,21 @@ vector<tuple<wstring, Unit>> UnitConverter::CalculateSuggested()
wstring roundedString; wstring roundedString;
if (abs(entry.value) < 100) if (abs(entry.value) < 100)
{ {
roundedString = RoundSignificant(entry.value, 2); roundedString = RoundSignificantDigits(entry.value, 2);
} }
else if (abs(entry.value) < 1000) else if (abs(entry.value) < 1000)
{ {
roundedString = RoundSignificant(entry.value, 1); roundedString = RoundSignificantDigits(entry.value, 1);
} }
else 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? // How to work out which is the best whimsical value to add to the vector?
if (stod(roundedString) != 0.0) if (stod(roundedString) != 0.0)
{ {
TrimString(roundedString); TrimTrailingZeros(roundedString);
whimsicalReturnVector.push_back(make_tuple(roundedString, entry.type)); whimsicalReturnVector.push_back(make_tuple(roundedString, entry.type));
} }
} }
@ -842,7 +844,7 @@ void UnitConverter::Calculate()
{ {
m_returnDisplay = m_currentDisplay; m_returnDisplay = m_currentDisplay;
m_returnHasDecimal = m_currentHasDecimal; m_returnHasDecimal = m_currentHasDecimal;
TrimString(m_returnDisplay); TrimTrailingZeros(m_returnDisplay);
UpdateViewModel(); UpdateViewModel();
return; return;
} }
@ -852,7 +854,7 @@ void UnitConverter::Calculate()
{ {
m_returnDisplay = m_currentDisplay; m_returnDisplay = m_currentDisplay;
m_returnHasDecimal = m_currentHasDecimal; m_returnHasDecimal = m_currentHasDecimal;
TrimString(m_returnDisplay); TrimTrailingZeros(m_returnDisplay);
} }
else else
{ {
@ -863,21 +865,19 @@ void UnitConverter::Calculate()
if (isCurrencyConverter) if (isCurrencyConverter)
{ {
// We don't need to trim the value when it's a currency. // We don't need to trim the value when it's a currency.
m_returnDisplay = RoundSignificant(returnValue, MAXIMUMDIGITSALLOWED); m_returnDisplay = RoundSignificantDigits(returnValue, MAXIMUMDIGITSALLOWED);
TrimString(m_returnDisplay); TrimTrailingZeros(m_returnDisplay);
} }
else else
{ {
int numPreDecimal = returnValue == 0 ? 0 : (1 + (int)log10(abs(returnValue))); int numPreDecimal = GetNumberDigitsWholeNumberPart(returnValue);
if (numPreDecimal > MAXIMUMDIGITSALLOWED || (returnValue != 0 && abs(returnValue) < MINIMUMDECIMALALLOWED)) if (numPreDecimal > MAXIMUMDIGITSALLOWED || (returnValue != 0 && abs(returnValue) < MINIMUMDECIMALALLOWED))
{ {
wstringstream out(wstringstream::out); m_returnDisplay = ToScientificNumber(returnValue);
out << scientific << returnValue;
m_returnDisplay = out.str();
} }
else else
{ {
int currentNumberSignificantDigits = GetNumberSignificantDigits(m_currentDisplay); int currentNumberSignificantDigits = GetNumberDigits(m_currentDisplay);
int precision = 0; int precision = 0;
if (abs(returnValue) < OPTIMALDECIMALALLOWED) if (abs(returnValue) < OPTIMALDECIMALALLOWED)
{ {
@ -888,8 +888,8 @@ void UnitConverter::Calculate()
precision = max(0, max(OPTIMALDIGITSALLOWED, min(MAXIMUMDIGITSALLOWED, currentNumberSignificantDigits)) - numPreDecimal); precision = max(0, max(OPTIMALDIGITSALLOWED, min(MAXIMUMDIGITSALLOWED, currentNumberSignificantDigits)) - numPreDecimal);
} }
m_returnDisplay = RoundSignificant(returnValue, precision); m_returnDisplay = RoundSignificantDigits(returnValue, precision);
TrimString(m_returnDisplay); TrimTrailingZeros(m_returnDisplay);
} }
m_returnHasDecimal = (m_returnDisplay.find(L'.') != wstring::npos); m_returnHasDecimal = (m_returnDisplay.find(L'.') != wstring::npos);
} }
@ -897,64 +897,6 @@ void UnitConverter::Calculate()
UpdateViewModel(); UpdateViewModel();
} }
/// <summary>
/// Trims out any trailing zeros or decimals in the given input string
/// </summary>
/// <param name="input">wstring to trim</param>
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());
}
}
/// <summary>
/// Get number of significant digits (integer part + fractional part) of a number</summary>
/// <param name="value">the number</param>
unsigned int UnitConverter::GetNumberSignificantDigits(std::wstring value)
{
TrimString(value);
unsigned int numberSignificantDigits = static_cast<unsigned int>(value.size());
if (value.find(L'.') != wstring::npos)
{
--numberSignificantDigits;
}
if (value.find(L'-') != wstring::npos)
{
--numberSignificantDigits;
}
return numberSignificantDigits;
}
/// <summary>
/// Rounds the given double to the given number of significant digits
/// </summary>
/// <param name="num">input double</param>
/// <param name="numSignificant">int number of significant digits to round to</param>
wstring UnitConverter::RoundSignificant(double num, int numSignificant)
{
wstringstream out(wstringstream::out);
out << fixed;
out.precision(numSignificant);
out << num;
return out.str();
}
void UnitConverter::UpdateCurrencySymbols() void UnitConverter::UpdateCurrencySymbols()
{ {
if (m_currencyDataLoader != nullptr && m_vmCurrencyCallback != nullptr) if (m_currencyDataLoader != nullptr && m_vmCurrencyCallback != nullptr)

View file

@ -290,10 +290,6 @@ namespace UnitConversionManager
std::shared_ptr<IConverterDataLoader> GetDataLoaderForCategory(const Category& category); std::shared_ptr<IConverterDataLoader> GetDataLoaderForCategory(const Category& category);
std::shared_ptr<ICurrencyConverterDataLoader> GetCurrencyConverterDataLoader(); std::shared_ptr<ICurrencyConverterDataLoader> GetCurrencyConverterDataLoader();
static void TrimString(_Inout_ std::wstring& input);
static unsigned int GetNumberSignificantDigits(std::wstring value);
static std::wstring RoundSignificant(double num, int numSignificant);
private: private:
std::shared_ptr<IConverterDataLoader> m_dataLoader; std::shared_ptr<IConverterDataLoader> m_dataLoader;
std::shared_ptr<IConverterDataLoader> m_currencyDataLoader; std::shared_ptr<IConverterDataLoader> m_currencyDataLoader;

View file

@ -7,9 +7,11 @@
#include "CalcManager/CalculatorHistory.h" #include "CalcManager/CalculatorHistory.h"
#include "CalcViewModel/Common/EngineResourceProvider.h" #include "CalcViewModel/Common/EngineResourceProvider.h"
#include "CalcManager/NumberFormattingUtils.h"
using namespace CalculatorApp; using namespace CalculatorApp;
using namespace CalculationManager; using namespace CalculationManager;
using namespace CalculationManager::NumberFormattingUtils;
using namespace Platform; using namespace Platform;
using namespace std; using namespace std;
using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::VisualStudio::CppUnitTestFramework;
@ -185,6 +187,11 @@ namespace CalculatorManagerTest
TEST_METHOD(CalculatorManagerTestMaxDigitsReached_LeadingDecimal); TEST_METHOD(CalculatorManagerTestMaxDigitsReached_LeadingDecimal);
TEST_METHOD(CalculatorManagerTestMaxDigitsReached_TrailingDecimal); 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 // TODO re-enable when cause of failure is determined. Bug 20226670
// TEST_METHOD(CalculatorManagerTestBinaryOperatorReceived); // TEST_METHOD(CalculatorManagerTestBinaryOperatorReceived);
// TEST_METHOD(CalculatorManagerTestBinaryOperatorReceived_Multiple); // TEST_METHOD(CalculatorManagerTestBinaryOperatorReceived_Multiple);
@ -807,6 +814,102 @@ namespace CalculatorManagerTest
TestMaxDigitsReachedScenario(L"123,456,789,101,112.13"); 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 // TODO re-enable when cause of failure is determined. Bug 20226670
// void CalculatorManagerTest::CalculatorManagerTestBinaryOperatorReceived() // void CalculatorManagerTest::CalculatorManagerTestBinaryOperatorReceived()
// { // {