1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 06:17:34 +00:00

Calculator: Change internal representation to support perfect division

The purpose of this patch is to support addition, subtraction,
multiplication and division without using conversion to double. To this
end, we use the BigFraction class of LibCrypto. With this solution, we
can store values without any losses and forward rounding as the last
step before displaying.
This commit is contained in:
Lucas CHOLLET 2022-01-12 11:05:03 +01:00 committed by Sam Atkins
parent 4ab8ad2ed2
commit 53eb35caba
10 changed files with 110 additions and 361 deletions

View file

@ -10,7 +10,6 @@ set(SOURCES
Calculator.cpp
CalculatorWidget.cpp
Keypad.cpp
KeypadValue.cpp
CalculatorGML.h
)

View file

@ -6,13 +6,13 @@
*/
#include "Calculator.h"
#include "KeypadValue.h"
#include <AK/Assertions.h>
#include <AK/Math.h>
#include <LibCrypto/BigFraction/BigFraction.h>
KeypadValue Calculator::begin_operation(Operation operation, KeypadValue argument)
Crypto::BigFraction Calculator::begin_operation(Operation operation, Crypto::BigFraction argument)
{
KeypadValue res = 0;
Crypto::BigFraction res {};
switch (operation) {
case Operation::None:
@ -27,7 +27,7 @@ KeypadValue Calculator::begin_operation(Operation operation, KeypadValue argumen
return argument;
case Operation::Sqrt:
if (argument < 0) {
if (argument < Crypto::BigFraction {}) {
m_has_error = true;
return argument;
}
@ -35,7 +35,7 @@ KeypadValue Calculator::begin_operation(Operation operation, KeypadValue argumen
clear_operation();
break;
case Operation::Inverse:
if (argument == 0) {
if (argument == Crypto::BigFraction {}) {
m_has_error = true;
return argument;
}
@ -43,14 +43,14 @@ KeypadValue Calculator::begin_operation(Operation operation, KeypadValue argumen
clear_operation();
break;
case Operation::Percent:
res = argument * KeypadValue { 1, 2 }; // also known as `KeypadValue{0.01}`
res = argument * Crypto::BigFraction { 1, 100 };
break;
case Operation::ToggleSign:
res = -argument;
break;
case Operation::MemClear:
m_mem = 0;
m_mem.set_to_0();
res = argument;
break;
case Operation::MemRecall:
@ -66,15 +66,12 @@ KeypadValue Calculator::begin_operation(Operation operation, KeypadValue argumen
break;
}
if (should_be_rounded(res))
round(res);
return res;
}
KeypadValue Calculator::finish_operation(KeypadValue argument)
Crypto::BigFraction Calculator::finish_operation(Crypto::BigFraction argument)
{
KeypadValue res = 0;
Crypto::BigFraction res {};
switch (m_operation_in_progress) {
case Operation::None:
@ -90,7 +87,7 @@ KeypadValue Calculator::finish_operation(KeypadValue argument)
res = m_saved_argument * argument;
break;
case Operation::Divide:
if (argument == 0) {
if (argument == Crypto::BigFraction {}) {
m_has_error = true;
return argument;
}
@ -108,9 +105,6 @@ KeypadValue Calculator::finish_operation(KeypadValue argument)
VERIFY_NOT_REACHED();
}
if (should_be_rounded(res))
round(res);
clear_operation();
return res;
}
@ -118,32 +112,6 @@ KeypadValue Calculator::finish_operation(KeypadValue argument)
void Calculator::clear_operation()
{
m_operation_in_progress = Operation::None;
m_saved_argument = 0;
m_saved_argument.set_to_0();
clear_error();
}
bool Calculator::should_be_rounded(KeypadValue value)
{
// We check if pow(10, value.m_decimal_places) overflow.
// If it does, the value can't be displayed (and provoke a division by zero), see Keypad::set_value()
// For u64, the threshold is 19
return value.m_decimal_places > rounding_threshold;
}
void Calculator::round(KeypadValue& value)
{
while (value.m_decimal_places > rounding_threshold) {
bool const need_increment = value.m_value % 10 > 4;
value.m_value /= 10;
if (need_increment)
value.m_value++;
value.m_decimal_places--;
if (value.m_value == 0) {
value = 0;
return;
}
}
}

View file

@ -7,7 +7,7 @@
#pragma once
#include "KeypadValue.h"
#include <LibCrypto/BigFraction/BigFraction.h>
// This type implements the regular calculator
// behavior, such as performing arithmetic
@ -39,8 +39,8 @@ public:
MemAdd
};
KeypadValue begin_operation(Operation, KeypadValue);
KeypadValue finish_operation(KeypadValue);
Crypto::BigFraction begin_operation(Operation, Crypto::BigFraction);
Crypto::BigFraction finish_operation(Crypto::BigFraction);
bool has_error() const { return m_has_error; }
@ -48,24 +48,8 @@ public:
void clear_error() { m_has_error = false; }
private:
static bool should_be_rounded(KeypadValue);
static void round(KeypadValue&);
static constexpr auto rounding_threshold = []() consteval
{
using used_type = u64;
auto count = 1;
used_type res = 10;
while (!__builtin_mul_overflow(res, (used_type)10, &res)) {
count++;
}
return count;
}
();
Operation m_operation_in_progress { Operation::None };
KeypadValue m_saved_argument { (i64)0 };
KeypadValue m_mem { (i64)0 };
Crypto::BigFraction m_saved_argument {};
Crypto::BigFraction m_mem {};
bool m_has_error { false };
};

View file

@ -8,9 +8,9 @@
*/
#include "CalculatorWidget.h"
#include "KeypadValue.h"
#include <Applications/Calculator/CalculatorGML.h>
#include <LibCore/Event.h>
#include <LibCrypto/BigFraction/BigFraction.h>
#include <LibGUI/Button.h>
#include <LibGUI/Label.h>
#include <LibGUI/TextBox.h>
@ -50,14 +50,14 @@ CalculatorWidget::CalculatorWidget()
m_clear_button = *find_descendant_of_type_named<GUI::Button>("clear_button");
m_clear_button->on_click = [this](auto) {
m_keypad.set_value(0.0);
m_keypad.set_to_0();
m_calculator.clear_operation();
update_display();
};
m_clear_error_button = *find_descendant_of_type_named<GUI::Button>("clear_error_button");
m_clear_error_button->on_click = [this](auto) {
m_keypad.set_value(0.0);
m_keypad.set_to_0();
update_display();
};
@ -99,18 +99,18 @@ CalculatorWidget::CalculatorWidget()
m_equals_button = *find_descendant_of_type_named<GUI::Button>("equal_button");
m_equals_button->on_click = [this](auto) {
KeypadValue argument = m_keypad.value();
KeypadValue res = m_calculator.finish_operation(argument);
m_keypad.set_value(res);
Crypto::BigFraction argument = m_keypad.value();
Crypto::BigFraction res = m_calculator.finish_operation(move(argument));
m_keypad.set_value(move(res));
update_display();
};
}
void CalculatorWidget::perform_operation(Calculator::Operation operation)
{
KeypadValue argument = m_keypad.value();
KeypadValue res = m_calculator.begin_operation(operation, argument);
m_keypad.set_value(res);
Crypto::BigFraction argument = m_keypad.value();
Crypto::BigFraction res = m_calculator.begin_operation(operation, move(argument));
m_keypad.set_value(move(res));
update_display();
}
@ -134,9 +134,9 @@ String CalculatorWidget::get_entry()
return m_entry->text();
}
void CalculatorWidget::set_entry(KeypadValue value)
void CalculatorWidget::set_entry(Crypto::BigFraction value)
{
m_keypad.set_value(value);
m_keypad.set_value(move(value));
update_display();
}
@ -167,7 +167,7 @@ void CalculatorWidget::keydown_event(GUI::KeyEvent& event)
m_keypad.type_decimal_point();
mimic_pressed_button(m_decimal_point_button);
} else if (event.key() == KeyCode::Key_Escape || event.key() == KeyCode::Key_Delete) {
m_keypad.set_value(0.0);
m_keypad.set_to_0();
m_calculator.clear_operation();
mimic_pressed_button(m_clear_button);
} else if (event.key() == KeyCode::Key_Backspace) {

View file

@ -10,8 +10,8 @@
#include "Calculator.h"
#include "Keypad.h"
#include "KeypadValue.h"
#include <AK/Vector.h>
#include <LibCrypto/BigFraction/BigFraction.h>
#include <LibGUI/Widget.h>
class CalculatorWidget final : public GUI::Widget {
@ -19,7 +19,7 @@ class CalculatorWidget final : public GUI::Widget {
public:
virtual ~CalculatorWidget() override = default;
String get_entry();
void set_entry(KeypadValue);
void set_entry(Crypto::BigFraction);
private:
CalculatorWidget();

View file

@ -7,38 +7,31 @@
*/
#include "Keypad.h"
#include "KeypadValue.h"
#include <AK/IntegralMath.h>
#include <AK/StringBuilder.h>
#include <LibCrypto/BigFraction/BigFraction.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibCrypto/NumberTheory/ModularFunctions.h>
void Keypad::type_digit(int digit)
{
u64 previous_value = 0;
switch (m_state) {
case State::External:
m_state = State::TypingInteger;
m_negative = false;
m_int_value = digit;
m_frac_value = 0;
m_frac_length = 0;
m_frac_value.set_to_0();
m_frac_length.set_to_0();
break;
case State::TypingInteger:
VERIFY(m_frac_value.value() == 0);
VERIFY(m_frac_value == 0);
VERIFY(m_frac_length == 0);
previous_value = m_int_value.value();
m_int_value *= 10;
m_int_value += digit;
if (m_int_value.has_overflow())
m_int_value = previous_value;
m_int_value.set_to(m_int_value.multiplied_by(10));
m_int_value.set_to(m_int_value.plus(digit));
break;
case State::TypingDecimal:
previous_value = m_frac_value.value();
m_frac_value *= 10;
m_frac_value += digit;
if (m_frac_value.has_overflow())
m_frac_value = previous_value;
else
m_frac_length++;
m_frac_value.set_to(m_frac_value.multiplied_by(10));
m_frac_value.set_to(m_frac_value.plus(digit));
m_frac_length.set_to(m_frac_length.plus(1));
break;
}
}
@ -47,14 +40,13 @@ void Keypad::type_decimal_point()
{
switch (m_state) {
case State::External:
m_negative = false;
m_int_value = 0;
m_frac_value = 0;
m_frac_length = 0;
m_int_value.set_to_0();
m_frac_value.set_to_0();
m_frac_length.set_to_0();
m_state = State::TypingDecimal;
break;
case State::TypingInteger:
VERIFY(m_frac_value.value() == 0);
VERIFY(m_frac_value == 0);
VERIFY(m_frac_length == 0);
m_state = State::TypingDecimal;
break;
@ -68,70 +60,78 @@ void Keypad::type_backspace()
{
switch (m_state) {
case State::External:
m_negative = false;
m_int_value = 0;
m_frac_value = 0;
m_frac_length = 0;
m_int_value.set_to_0();
m_frac_value.set_to_0();
m_frac_length.set_to_0();
break;
case State::TypingDecimal:
if (m_frac_length > 0) {
m_frac_value /= 10;
m_frac_length--;
m_frac_value.set_to(m_frac_value.divided_by(10).quotient);
m_frac_length.set_to(m_frac_length.minus(1));
break;
}
VERIFY(m_frac_value.value() == 0);
VERIFY(m_frac_value == 0);
m_state = State::TypingInteger;
[[fallthrough]];
case State::TypingInteger:
VERIFY(m_frac_value.value() == 0);
VERIFY(m_frac_value == 0);
VERIFY(m_frac_length == 0);
m_int_value /= 10;
if (m_int_value.value() == 0)
m_negative = false;
m_int_value.set_to(m_int_value.divided_by(10).quotient);
break;
}
}
KeypadValue Keypad::value() const
Crypto::BigFraction Keypad::value() const
{
KeypadValue frac_part = { (i64)m_frac_value.value(), m_frac_length };
KeypadValue int_part = { (i64)m_int_value.value() };
KeypadValue res = int_part + frac_part;
if (m_negative)
res = -res;
return res;
if (m_state != State::External) {
Crypto::SignedBigInteger sum { m_int_value.multiplied_by(Crypto::NumberTheory::Power("10"_bigint, m_frac_length)).plus(m_frac_value) };
Crypto::BigFraction res { move(sum), Crypto::NumberTheory::Power("10"_bigint, m_frac_length) };
m_internal_value = move(res);
}
return m_internal_value;
}
void Keypad::set_value(KeypadValue value)
void Keypad::set_value(Crypto::BigFraction value)
{
m_state = State::External;
if (value.m_value < 0) {
m_negative = true;
value = -value;
} else
m_negative = false;
m_internal_value = move(value);
}
m_int_value = value.m_value / AK::pow<u64>(10, value.m_decimal_places);
m_frac_value = value.m_value % AK::pow<u64>(10, value.m_decimal_places);
m_frac_length = value.m_decimal_places;
void Keypad::set_to_0()
{
m_int_value.set_to_0();
m_frac_value.set_to_0();
m_frac_length.set_to_0();
m_internal_value.set_to_0();
m_state = State::External;
}
String Keypad::to_string() const
{
// TODO: Implement custom rounding length in the calculator.
constexpr auto maximum_precision = 6;
if (m_state == State::External)
return m_internal_value.to_string(maximum_precision);
StringBuilder builder;
if (m_negative)
builder.append('-');
builder.appendff("{}", m_int_value.value());
// NOTE: This is so the decimal point appears on screen as soon as you type it.
if (m_frac_length > 0 || m_state == State::TypingDecimal)
String const integer_value = m_int_value.to_base(10);
String const frac_value = m_frac_value.to_base(10);
unsigned const number_pre_zeros = m_frac_length.to_u64() - (frac_value.length() - 1) - (frac_value == "0" ? 0 : 1);
builder.append(integer_value);
// NOTE: We test for the state so the decimal point appears on screen as soon as you type it.
if (m_state == State::TypingDecimal) {
builder.append('.');
if (m_frac_length > 0) {
// FIXME: This disables the compiletime format string check since we can't parse '}}' here correctly.
// remove the 'StringView { }' when that's fixed.
builder.appendff("{:0{}}"sv, m_frac_value.value(), m_frac_length);
builder.append_repeated('0', number_pre_zeros);
if (frac_value != "0")
builder.append(frac_value);
}
return builder.to_string();

View file

@ -7,8 +7,9 @@
#pragma once
#include "KeypadValue.h"
#include <AK/String.h>
#include <LibCrypto/BigFraction/BigFraction.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
// This type implements number typing and
// displaying mechanics. It does not perform
@ -24,23 +25,27 @@ public:
void type_decimal_point();
void type_backspace();
KeypadValue value() const;
void set_value(KeypadValue);
Crypto::BigFraction value() const;
void set_value(Crypto::BigFraction);
void set_to_0();
String to_string() const;
private:
// Internal representation of the current decimal value.
bool m_negative { false };
Checked<u64> m_int_value { 0 };
Checked<u64> m_frac_value { 0 };
u8 m_frac_length { 0 };
// Those variables are only used when the user is entering a value.
// Otherwise, the BigFraction m_internal_value is used.
Crypto::UnsignedBigInteger m_int_value { 0 };
Crypto::UnsignedBigInteger m_frac_value { 0 };
Crypto::UnsignedBigInteger m_frac_length { 0 };
// E.g. for -35.004200,
// m_negative = true
// m_int_value = 35
// m_frac_value = 4200
// m_frac_length = 6
mutable Crypto::BigFraction m_internal_value {};
enum class State {
External,
TypingInteger,

View file

@ -1,162 +0,0 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "KeypadValue.h"
#include <AK/IntegralMath.h>
#include <AK/Math.h>
#include <AK/String.h>
KeypadValue::KeypadValue(i64 value, u8 decimal_places)
: m_value(value)
, m_decimal_places(value == 0 ? 0 : decimal_places)
{
}
KeypadValue::KeypadValue(i64 value)
: m_value(value)
{
}
KeypadValue::KeypadValue(StringView sv)
{
String str = sv.to_string(); // TODO: Once we have a StringView equivalent for this C API, we won't need to create a copy for this anymore.
size_t first_index = 0;
char* dot_ptr;
i64 int_part = strtoll(&str[first_index], &dot_ptr, 10);
size_t dot_index = dot_ptr - str.characters();
if ((dot_index < str.length()) && (str[dot_index] == '.')) {
size_t second_index = dot_index + 1;
char* end_ptr;
i64 frac_part = strtoll(&str[second_index], &end_ptr, 10);
size_t end_index = end_ptr - str.characters();
u8 frac_length = end_index - second_index;
*this = KeypadValue { int_part } + KeypadValue { frac_part, frac_length };
} else {
*this = KeypadValue { int_part };
}
};
KeypadValue KeypadValue::operator+(KeypadValue const& rhs)
{
return operator_helper<KeypadValue>(*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<KeypadValue>(*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 };
}
KeypadValue KeypadValue::sqrt(void) const
{
return KeypadValue { AK::sqrt((double)(*this)) };
}
KeypadValue KeypadValue::invert(void) const
{
return KeypadValue { 1.0 / (double)(*this) };
}
KeypadValue KeypadValue::operator/(KeypadValue const& rhs)
{
return KeypadValue { (double)(*this) / (double)rhs };
}
bool KeypadValue::operator<(KeypadValue const& rhs)
{
return operator_helper<bool>(*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<bool>(*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<typename T, typename F>
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 = AK::pow<i64>(10, 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;
double epsilon = 1e-6;
while (d >= epsilon || current_pow >= 0) {
m_value *= 10;
i8 digit = (u64)(d * AK::pow(0.1, (double)current_pow)) % 10;
m_value += digit;
d -= digit * AK::pow(10.0, (double)current_pow);
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() const
{
double res = (double)m_value / AK::pow(10.0, (double)m_decimal_places);
return res;
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Math.h>
#include <AK/String.h>
#include <AK/Types.h>
class KeypadValue {
friend class Keypad;
friend class Calculator;
public:
KeypadValue(i64, u8);
KeypadValue(i64);
explicit KeypadValue(StringView);
KeypadValue operator+(KeypadValue const&);
KeypadValue operator-(KeypadValue const&);
KeypadValue operator*(KeypadValue const&);
KeypadValue operator-(void) const;
bool operator<(KeypadValue const&);
bool operator==(KeypadValue const&);
KeypadValue sqrt() const;
KeypadValue invert() const;
KeypadValue operator/(KeypadValue const&);
private:
explicit KeypadValue(double);
explicit operator double() const;
template<typename T, typename F>
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 };
};

View file

@ -6,6 +6,7 @@
#include "CalculatorWidget.h"
#include <LibCore/System.h>
#include <LibCrypto/NumberTheory/ModularFunctions.h>
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/Clipboard.h>
@ -51,20 +52,22 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
auto clipboard = GUI::Clipboard::the().fetch_data_and_type();
if (clipboard.mime_type == "text/plain") {
if (!clipboard.data.is_empty()) {
widget->set_entry(KeypadValue(StringView(clipboard.data)));
widget->set_entry(Crypto::BigFraction(StringView(clipboard.data)));
}
}
}));
auto& constants_menu = window->add_menu("&Constants");
auto const power = Crypto::NumberTheory::Power("10"_bigint, "10"_bigint);
constants_menu.add_action(GUI::Action::create("&Pi", TRY(Gfx::Bitmap::try_load_from_file("/res/icons/calculator/pi.png"sv)), [&](auto&) {
widget->set_entry(KeypadValue { 31415926535, 10 });
widget->set_entry(Crypto::BigFraction { Crypto::SignedBigInteger(31415926535), power });
}));
constants_menu.add_action(GUI::Action::create("&Euler's Number", TRY(Gfx::Bitmap::try_load_from_file("/res/icons/calculator/eulers_number.png"sv)), [&](auto&) {
widget->set_entry(KeypadValue { 27182818284, 10 });
widget->set_entry(Crypto::BigFraction { Crypto::SignedBigInteger(27182818284), power });
}));
constants_menu.add_action(GUI::Action::create("&Phi", TRY(Gfx::Bitmap::try_load_from_file("/res/icons/calculator/phi.png"sv)), [&](auto&) {
widget->set_entry(KeypadValue { 16180339887, 10 });
widget->set_entry(Crypto::BigFraction { Crypto::SignedBigInteger(16180339887), power });
}));
auto& help_menu = window->add_menu("&Help");