mirror of
https://github.com/Microsoft/calculator.git
synced 2025-08-22 22:23:29 -07:00
Move UnitConverter static methods related to number to NumberFormattingUtils
This commit is contained in:
parent
cbd0669aa6
commit
f9d5094841
7 changed files with 227 additions and 82 deletions
|
@ -309,6 +309,7 @@
|
|||
<ClInclude Include="Ratpack\CalcErr.h" />
|
||||
<ClInclude Include="Ratpack\ratconst.h" />
|
||||
<ClInclude Include="Ratpack\ratpak.h" />
|
||||
<ClInclude Include="NumberFormattingUtils.h" />
|
||||
<ClInclude Include="UnitConverter.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -349,6 +350,7 @@
|
|||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="NumberFormattingUtils.cpp" />
|
||||
<ClCompile Include="UnitConverter.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
<ClCompile Include="CEngine\RationalMath.cpp">
|
||||
<Filter>CEngine</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="NumberFormattingUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Command.h" />
|
||||
|
@ -160,5 +161,6 @@
|
|||
<ClInclude Include="Header Files\RationalMath.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NumberFormattingUtils.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
85
src/CalcManager/NumberFormattingUtils.cpp
Normal file
85
src/CalcManager/NumberFormattingUtils.cpp
Normal 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();
|
||||
}
|
||||
}
|
15
src/CalcManager/NumberFormattingUtils.h
Normal file
15
src/CalcManager/NumberFormattingUtils.h
Normal 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);
|
||||
}
|
|
@ -6,10 +6,12 @@
|
|||
#include <algorithm> // 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<tuple<wstring, Unit>> 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<tuple<wstring, Unit>> 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();
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
if (m_currencyDataLoader != nullptr && m_vmCurrencyCallback != nullptr)
|
||||
|
|
|
@ -290,10 +290,6 @@ namespace UnitConversionManager
|
|||
std::shared_ptr<IConverterDataLoader> GetDataLoaderForCategory(const Category& category);
|
||||
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:
|
||||
std::shared_ptr<IConverterDataLoader> m_dataLoader;
|
||||
std::shared_ptr<IConverterDataLoader> m_currencyDataLoader;
|
||||
|
|
|
@ -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()
|
||||
// {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue