Вы не пишете C ++, вы пишете C, завернутый в какой-то маленький C ++. Итак, вот что я бы сделал:
Сначала я бы начал с класса для интервала, т. Е .:
struct Interval
{
int left;
int right;
};
Тогда я бы начал с сложения, вычитания и умножения. Э.Г.
auto operator+(Interval lhs, Interval rhs) -> Interval;
Что довольно просто.
Когда мы дойдем до деления, как видно из вашей вики-ссылки, все станет сложнее.
- Результатом является уже не интервал, а мультиинтервал, т.е. соединение интервалов.
- Эти интервалы имеют головки
∞
и -∞
.
Первая проблема решается с помощью нового класса:
class MultiInterval
{
std::vector<Interval> intervals_;
//...
};
Для второй проблемы ... мы больше не можем использовать int
в качестве данных. Поэтому вам нужен новый класс для целых чисел, который может содержать значения ∞, -∞, NaN
. NaN
требуется в качестве побочного продукта, например, -∞ + ∞ = NaN
.
class ExtendedInt
{
// black magic
};
, для которого вам нужно будет определить 3 константы, которые являются новыми 3 значениями. Затем вам нужно будет определить все основные арифметические операции над ними.
Наконец-то мы можем переделать все как-то так:
class ExtendedInt;
auto operator+(ExtendedInt lhs, ExtendedInt rhs) -> ExtendedInt;
auto operator-(ExtendedInt lhs, ExtendedInt rhs) -> ExtendedInt;
auto operator*(ExtendedInt lhs, ExtendedInt rhs) -> ExtendedInt;
auto operator/(ExtendedInt lhs, ExtendedInt rhs) -> ExtendedInt;
// etc...
struct Interval
{
ExtendedInt left;
ExtendedInt right;
};
class MultiInterval
{
std::vector<Interval> intervals_;
//...
};
auto operator+(Interval lhs, Interval rhs) -> Interval;
auto operator-(Interval lhs, Interval rhs) -> Interval;
auto operator*(Interval lhs, Interval rhs) -> Interval;
auto operator/(Interval lhs, Interval rhs) -> MultiInterval;
Как видите, все довольно быстро усложняется.
Что касается реализации ExtendedInt, то одно решение состоит из двух элементов данных, одного int
и одного перечисления
enum class ExtendedIntValues { Normal, Inf, NegInf, NaN };
class ExtendedInt
{
int value_;
ExtendedIntValues ext_value_;
};
если ext_value_ == Normal
, то значение экземпляра равно value_
, иначе это ext_value_
.
Другое решение состоит в том, чтобы осознать, что функционально это союз. Таким образом, вы можете использовать std::variant<int, ExtendedIntValues>
вместо.
Еще одним решением является использование std::optional
:
enum class ExtendedIntValues { Inf, NegInf, NaN }; // notice no Normal enum
class ExtendedInt
{
std::optional<int> value_;
ExtendedIntValues ext_value_;
};
Все эти решения жертвуют пространством с std::variant
, жертвуя удобством использования.
Другое решение, которое просто жертвует 3 нормальными int
значениями, состоит в том, чтобы иметь только одно int
и иметь некоторые значения, представляющие особые случаи:
class ExtendedInt
{
int value_;
};
constexpr ExtededInt NegInf = ...;
constexpr ExtededInt Inf = ...;
constexpr ExtededInt NaN = ... ;
Внутренне:
std::numeric_limits<int>::min()
beeing NegInf
std::numeric_limits<int>::max() - 1
is Inf
std::numeric_limits<int>::max()
is NaN
Или что-то в этом роде, но это должно быть сделано совершенно прозрачным. Особое внимание должно быть уделено арифметическим операциям.
Другое решение состоит в том, чтобы понять, что уже есть тип (или два), которые изначально поддерживают эти значения и идут с float
или double
. Это принесет в жертву точность.
Как только вы выяснили ExtendedInt
, просто следуйте алгоритму вики:
auto operator/(Interval lhs, Interval rhs) -> MultiInterval
{
MultiInterval f1{lhs};
MultiInterval f2{};
if (!contains(rhs, 0))
f2 = MultiInterval{{1 / rhs.right, 1 / rhs.left}};
else if (rhs.right == 0)
f2 = MultiInterval{{NegInf, 1 / rhs.left}};
else if (rhs.left == 0)
f2 = MultiInterval{{1 / rhs.right, Inf}};
else
f2 = MultiInterval{{NegInf, 1 / rhs.left}, {1 / rhs.right, Inf}};
return f1 * f2;
}
Минимальный интерфейс для работы выше:
struct ExtendedInt
{
ExtendedInt();
ExtendedInt(int);
};
ExtendedInt NegInf;
ExtendedInt Inf;
ExtendedInt NaN;
auto operator+(ExtendedInt, ExtendedInt) -> ExtendedInt;
auto operator-(ExtendedInt, ExtendedInt) -> ExtendedInt;
auto operator*(ExtendedInt, ExtendedInt) -> ExtendedInt;
auto operator/(ExtendedInt, ExtendedInt) -> ExtendedInt;
auto operator==(ExtendedInt, ExtendedInt) -> bool;
auto operator!=(ExtendedInt, ExtendedInt) -> bool;
struct Interval
{
ExtendedInt left, right;
};
auto contains(Interval, ExtendedInt) -> bool;
class MultiInterval
{
public:
MultiInterval(std::initializer_list<Interval>);
};
auto operator*(MultiInterval lhs, MultiInterval rhs) -> MultiInterval;
Наконец, обратите внимание на это из википедии:
Потому что несколько таких делений могут возникать в интервальной арифметике
расчет, иногда полезно сделать расчет с
так называемые мультиинтервалы вида
Таким образом, в конечном итоге вам придется работать только с MultiInterval
, где интервал равен MultiInterval
с одним интервалом.