Consider intervals A = [x1, y1] and B = [x2, y2], two intervals representing signed integers.
Interval arithmetic page on Wikipedia gives a formula to address the case where B does not contain 0, whose C++ implementation may be as follows:
void print(const char* const what, int x, int y)
{
std::cout << what << " = [" << x << ", " << y << "]\n";
}
void div(const char* const what, int x1, int y1, int x2, int y2)
{
int v1 = x1/x2;
int v2 = x1/y2;
int v3 = y1/x2;
int v4 = y1/y2;
int x = std::min(std::min(v1, v2), std::min(v3, v4));
int y = std::max(std::max(v1, v2), std::max(v3, v4));
print(what, x, y);
}
int main()
{
int x1 = -10000, y1 = 150000;
int x2 = -10, y2 = 10;
print("A", x1, y1);
print("B", x2, y2);
div("A/B", x1, y1, x2, y2);
}
Output:
A = [-10000, 150000]
B = [-10, 10]
A/B = [-15000, 15000]
As expected, the result is incorrect given that B contains 0. For example, since 1 is part of B, 150000 should be within A/B but it is not.
What is a viable algorithm when 0 is excluded from B? Should the solution use multiple intervals around -1 and 1 (i.e. exclude 0) and join them somehow?
Edit: the solution may be expressed in terms of a union (vector) of intervals of long long type.
You are not writing C++, you are writing C wrapped in some little C++ then and there. So here's what I would do:
First I would start with a class for an interval, i.e.:
Then I would start with addition, subtraction and multiplication. E.g.
Which are quite simple.
When we get to division, as seen on your wiki link, things get more complicated.
∞
and-∞
heads.The first problem is solved with a new class:
For the second problem ... well we can no longer use
int
as data. So you need a new class for integer that can contain the values∞, -∞, NaN
.NaN
is required as a byproduct, e.g.-∞ + ∞ = NaN
.for which you would have to define 3 constants that are the new 3 values. You would then need to define all of the basic arithmetic operations on them.
Finally we get to redo everything to something like this:
As you can see things get complicated pretty fast.
As for how you would implement ExtendedInt one solution is to have two data members, one
int
and one enumif
ext_value_ == Normal
then the value of the instance isvalue_
, else it isext_value_
.Another solution is to realize that functionally that is an union. So you could use
std::variant<int, ExtendedIntValues>
instead.Yet another solution is to use
std::optional
:All these solution sacrifice space with
std::variant
sacrificing usability.Another solution that just sacrifices 3 normal
int
values is to have just oneint
and have some values represent the special cases:Internally:
std::numeric_limits<int>::min()
beeingNegInf
std::numeric_limits<int>::max() - 1
isInf
std::numeric_limits<int>::max()
isNaN
Or something like that, but that would have to be made completely transparent. Extra special care would have to be made to arithmetic operations.
Another solution is to realize there is already a type (or two) that natively support these values and go with
float
ordouble
. This would sacrifice precision.Once you have
ExtendedInt
figured out then just follow the wiki algorithm:Minimum interface for the above to work:
Finally please note this from wikipedia:
So you would ultimately have to work only with
MultiInterval
where an interval is aMultiInterval
with just one interval.