diff --git a/Userland/Applications/Calculator/CMakeLists.txt b/Userland/Applications/Calculator/CMakeLists.txt index 966e163910..31d4d842be 100644 --- a/Userland/Applications/Calculator/CMakeLists.txt +++ b/Userland/Applications/Calculator/CMakeLists.txt @@ -10,6 +10,7 @@ set(SOURCES Calculator.cpp CalculatorWidget.cpp Keypad.cpp + KeypadValue.cpp CalculatorGML.h ) diff --git a/Userland/Applications/Calculator/KeypadValue.cpp b/Userland/Applications/Calculator/KeypadValue.cpp new file mode 100644 index 0000000000..fbd658bddc --- /dev/null +++ b/Userland/Applications/Calculator/KeypadValue.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "KeypadValue.h" +#include +#include + +KeypadValue::KeypadValue(i64 value, u8 decimal_places) + : m_value(value) + , m_decimal_places(decimal_places) +{ +} + +KeypadValue::KeypadValue(i64 value) + : m_value(value) +{ +} + +KeypadValue KeypadValue::operator+(KeypadValue const& rhs) +{ + return operator_helper(*this, rhs, [](KeypadValue const&, KeypadValue const& more_decimal_places, i64 less_decimal_places_equalized, i64 more_decimal_places_equalized, bool) -> KeypadValue { + return { + more_decimal_places_equalized + less_decimal_places_equalized, + more_decimal_places.m_decimal_places + }; + }); +} + +KeypadValue KeypadValue::operator-(KeypadValue const& rhs) +{ + return *this + (-rhs); +} + +KeypadValue KeypadValue::operator*(KeypadValue const& rhs) +{ + return operator_helper(*this, rhs, [](KeypadValue const& less_decimal_places, KeypadValue const& more_decimal_places, i64, i64, bool) -> KeypadValue { + return { + less_decimal_places.m_value * more_decimal_places.m_value, + (u8)(less_decimal_places.m_decimal_places + more_decimal_places.m_decimal_places) + }; + }); +} + +KeypadValue KeypadValue::operator-(void) const +{ + return { -m_value, m_decimal_places }; +} + +bool KeypadValue::operator<(KeypadValue const& rhs) +{ + return operator_helper(*this, rhs, [](KeypadValue const&, KeypadValue const&, i64 less_decimal_places_equalized, i64 more_decimal_places_equalized, bool lhs_is_less) { + if (lhs_is_less) + return (less_decimal_places_equalized < more_decimal_places_equalized); + else + return (more_decimal_places_equalized < less_decimal_places_equalized); + }); +} + +bool KeypadValue::operator==(KeypadValue const& rhs) +{ + return operator_helper(*this, rhs, [](KeypadValue const&, KeypadValue const&, i64 less_decimal_places_equalized, i64 more_decimal_places_equalized, bool) { + return less_decimal_places_equalized == more_decimal_places_equalized; + }); +} + +// This is a helper function for the operators. A lot of them need to do very similar calculations, so this function +// does the calculations for them and calls them on the result. In case they don't need the result of a particular +// calculation, they simply ignore that argument. +// The arguments to this function are the operands on the left- and right-hand sides and the callback to call on the +// values computed by this function. +// The first two KeypadValues it passes to the callback are the two original operands, but sorted by the amount of +// decimal places. +// The next two i64s it passes to the callback are these sorted KeypadValues, but normalized, which means that if +// you have for example 12.1 (represented as {121, 1}) and 54.23 (represented as {5423, 2}), you will get 1210 and +// 5423, so that you can compare these two i64s directly in order to compare the original KeypadValues. +// Unfortunately, not all operators are symmetric, so the last boolean tells the callback whether the left-hand side +// was the KeypadValue with less decimal places (true), or the one with more decimal places (false). +template +ALWAYS_INLINE T KeypadValue::operator_helper(KeypadValue const& lhs, KeypadValue const& rhs, F callback) +{ + KeypadValue const& less_decimal_places = (lhs.m_decimal_places < rhs.m_decimal_places) ? lhs : rhs; + KeypadValue const& more_decimal_places = (lhs.m_decimal_places < rhs.m_decimal_places) ? rhs : lhs; + + i64 more_decimal_places_equalized = more_decimal_places.m_value; + i64 less_decimal_places_equalized = (i64)AK::pow(10.0, (double)(more_decimal_places.m_decimal_places - less_decimal_places.m_decimal_places)) * less_decimal_places.m_value; + + bool lhs_is_less = (lhs.m_decimal_places < rhs.m_decimal_places); + + return callback(less_decimal_places, more_decimal_places, + less_decimal_places_equalized, more_decimal_places_equalized, + lhs_is_less); +} + +KeypadValue::KeypadValue(double d) +{ + bool negative = false; + if (d < 0) { + negative = true; + d = -d; + } + i8 current_pow = 0; + while (AK::pow(10.0, (double)current_pow) <= d) + current_pow += 1; + current_pow -= 1; + while (d != 0) { + m_value *= 10; + m_value += (u64)(d / AK::pow(10.0, (double)current_pow)) % 10; + if (current_pow < 0) + m_decimal_places += 1; + current_pow -= 1; + if (m_decimal_places > 6) + break; + } + m_value = negative ? (-m_value) : m_value; +} + +KeypadValue::operator double() +{ + double res = (double)m_value / AK::pow(10.0, (double)m_decimal_places); + return res; +} diff --git a/Userland/Applications/Calculator/KeypadValue.h b/Userland/Applications/Calculator/KeypadValue.h new file mode 100644 index 0000000000..6e69d04016 --- /dev/null +++ b/Userland/Applications/Calculator/KeypadValue.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +class KeypadValue { + friend class Keypad; + +public: + KeypadValue(i64, u8); + KeypadValue(i64); + + KeypadValue operator+(KeypadValue const&); + KeypadValue operator-(KeypadValue const&); + KeypadValue operator*(KeypadValue const&); + KeypadValue operator-(void) const; + bool operator<(KeypadValue const&); + bool operator>(KeypadValue const&); + bool operator==(KeypadValue const&); + + explicit KeypadValue(double); + explicit operator double(); + +private: + template + T operator_helper(KeypadValue const& lhs, KeypadValue const& rhs, F callback); + + // This class represents a pair of a value together with the amount of decimal places that value is offset by. + // For example, if we were to represent the value -123.55 in this format, m_value would be -12355 and + // m_decimal_places would be 2, because when you shift -12355 2 digits to the right, you get -123.55. + // This way, most operations don't have to be performed on doubles, but can be performed without loss of + // precision on this class. + i64 m_value { 0 }; + u8 m_decimal_places { 0 }; +};