Это класс, представляющий экспоненту. Его основная цель — иметь дело с логарифмическими величинами.
template<std::floating_point Representation>
class Exponent
{
public:
using representation = Representation;
struct Literal{};
constexpr explicit Exponent(representation val, Literal):m_val{val}
{}
constexpr explicit Exponent(representation val):m_val{std::log2(val)}
{}
constexpr representation value() const
{
return m_val;
}
constexpr operator representation() const
{
return std::exp2(m_val);
}
constexpr Exponent& operator+=(Exponent other)
{
m_val += other.m_val;
return *this;
}
constexpr Exponent& operator-=(Exponent other)
{
m_val -= other.m_val;
return *this;
}
constexpr Exponent& operator/=(Exponent other)
{
m_val /= other.m_val;
return *this;
}
constexpr Exponent& operator*=(Exponent other)
{
m_val *= other.m_val;
return *this;
}
constexpr auto operator<=>(Exponent const&) const = default;
private:
representation m_val;
};
template<std::floating_point T>
constexpr Exponent<T> operator+(Exponent<T> a, Exponent<T> b)
{
return a+=b;
}
//...
Я не уверен в том, как правильно устранить неоднозначность между операциями преобразования данных (логарифм против экспоненты) и чистыми преобразованиями значений (простое присваивание).
Другой вопрос, должен ли API имитировать реальные значения. С помощью вышеуказанного API:
constexpr double multiply(double a, double b)
{return Exponent{a} + Exponent{b};}
static_assert(multiply(2.0, 3.0) == 6.0);
Однако, если вы определяете оператор * для показателей степени как сложение, вы вместо этого просто напишите:
constexpr double multiply(Exponent a, Exponent b)
{return a*b;}
static_assert(multiply(2.0, 3.0) == 6.0);