158 lines
4.2 KiB
C++
158 lines
4.2 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <type_traits>
|
|
|
|
#include "pow.h"
|
|
#include "rational.h"
|
|
|
|
// Все ошибки вычислений
|
|
using Error = std::string;
|
|
|
|
namespace calculator_detail {
|
|
|
|
// Сравнивает значение с 0
|
|
template<class Number>
|
|
bool IsZero(const Number& value) {
|
|
return value == Number{};
|
|
}
|
|
|
|
// Вынесение 0 в отдельную перегрузку для Rational
|
|
inline bool IsZero(const Rational& value) {
|
|
return value == Rational{};
|
|
}
|
|
|
|
// Преобразует число в текст для отображения на экране
|
|
template<class Number>
|
|
std::string NumberToString(const Number& value) {
|
|
std::ostringstream out;
|
|
out << value;
|
|
return out.str();
|
|
}
|
|
|
|
// Вывод uint8_t как число
|
|
inline std::string NumberToString(std::uint8_t value) {
|
|
std::ostringstream out;
|
|
out << +value;
|
|
return out.str();
|
|
}
|
|
|
|
}
|
|
|
|
template<class Number>
|
|
class Calculator {
|
|
public:
|
|
// Устанавливает новое текущее значение
|
|
void Set(Number value) {
|
|
current_ = value;
|
|
}
|
|
|
|
// Возвращает текущее значение
|
|
Number GetNumber() const {
|
|
return current_;
|
|
}
|
|
|
|
// Сложение
|
|
std::optional<Error> Add(Number value) {
|
|
current_ += value;
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Вычитание
|
|
std::optional<Error> Sub(Number value) {
|
|
current_ -= value;
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Умножение
|
|
std::optional<Error> Mul(Number value) {
|
|
current_ *= value;
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Деление провряет 0 только для типов без NaN
|
|
std::optional<Error> Div(Number value) {
|
|
if constexpr (std::is_integral_v<Number> || std::is_same_v<Number, Rational>) {
|
|
if (calculator_detail::IsZero(value)) {
|
|
return "Division by zero";
|
|
}
|
|
}
|
|
|
|
current_ /= value;
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Возведение в степень выбирает нужную функцию и проверяет ошибки
|
|
std::optional<Error> Pow(Number value) {
|
|
if constexpr (std::is_floating_point_v<Number>) {
|
|
current_ = ::Pow(current_, value);
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (calculator_detail::IsZero(current_) && calculator_detail::IsZero(value)) {
|
|
return "Zero power to zero";
|
|
}
|
|
|
|
if constexpr (std::is_integral_v<Number>) {
|
|
if constexpr (std::is_signed_v<Number>) {
|
|
if (value < 0) {
|
|
return "Integer negative power";
|
|
}
|
|
}
|
|
|
|
current_ = ::Pow(current_, value);
|
|
return std::nullopt;
|
|
}
|
|
|
|
if constexpr (std::is_same_v<Number, Rational>) {
|
|
if (value.GetDenominator() != 1) {
|
|
return "Fractional power is not supported";
|
|
}
|
|
if (calculator_detail::IsZero(current_) && value.GetNumerator() < 0) {
|
|
return "Division by zero";
|
|
}
|
|
|
|
current_ = ::Pow(current_, value);
|
|
return std::nullopt;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Сохраняет текущее значение в память
|
|
void Save() {
|
|
mem_ = current_;
|
|
}
|
|
|
|
// Загружает текущее значение из памяти
|
|
void Load() {
|
|
if (mem_) {
|
|
current_ = *mem_;
|
|
}
|
|
}
|
|
|
|
// Очищает память калькулятора
|
|
void ClearMem() {
|
|
mem_ = std::nullopt;
|
|
}
|
|
|
|
// Значение сохранено в памяти? Если Да выводить индикатор
|
|
bool GetHasMem() const {
|
|
return mem_.has_value();
|
|
}
|
|
|
|
// Возвращает текущее значение в том же формате, что используется в интерфейсе
|
|
std::string GetNumberRepr() const {
|
|
return calculator_detail::NumberToString(current_);
|
|
}
|
|
|
|
private:
|
|
// Текущее значение
|
|
Number current_ = {};
|
|
// Ячейка памяти
|
|
std::optional<Number> mem_ = std::nullopt;
|
|
};
|