-1

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.

elokode
  • 11
  • 3
  • dividing by 0 has no sense. So which result do you expect ? `[Nan, Nan]` ? – Jarod42 Jan 07 '19 at 20:48
  • I specified in my question "when it is known that B does not contain 0" – elokode Jan 07 '19 at 20:50
  • 1
    Your algorithm as given is already viable when it is known that B doesn't contain 0. So what is your question, really? – TonyK Jan 07 '19 at 20:50
  • You mean `B = [-10, 10 ] - {0}` ? (So `B = [-10, -1] U [1, 10]`) ? – Jarod42 Jan 07 '19 at 20:56
  • @TonyK it is not viable since for example 1 is part of B but 150000 is not within the resulting interval [-15000, 15000] – elokode Jan 07 '19 at 20:56
  • @Jarod42 yes, exactly – elokode Jan 07 '19 at 20:57
  • 1
    From wikipedia, same page: "Because several such divisions may occur in an interval arithmetic calculation, it is sometimes useful to do the calculation with so-called multi-intervals of the form [Union of interval]"... – Jarod42 Jan 07 '19 at 21:07
  • @Jarod42 I'm unclear how that would translate into an algorithm though (my div function), can you help on this? – elokode Jan 07 '19 at 21:10
  • 1
    OK, I see what you are trying to do. But it's not Interval Arithmetic. Are you really sure you want to do this? – TonyK Jan 07 '19 at 21:16
  • `A/B- = [-150000, 10000] A/B+ = [-10000, 150000]`, So either keep 2 sets, or merge them to `[-150000, 150000]`. – Jarod42 Jan 07 '19 at 21:19
  • @TonyK it is possible I misunderstand interval arithmetic (if so I'm willing to close that question as invalid, accept, or whichever action is appropriate). Is my reasoning on the value 1 incorrect and if so could you please give me some pointers on why it is so? – elokode Jan 07 '19 at 21:21
  • BTW, you probably want to create classes `Interval`/`IntervalSet`. – Jarod42 Jan 07 '19 at 21:22
  • In Interval Arithmetic, it is simply invalid to divide by an interval that contains 0. (And if you just remove 0 from your interval, it's not an interval any more.) – TonyK Jan 07 '19 at 21:24
  • @TonyK is there such a thing as multi-interval arithmetic, where B is as Jarod exemplified B = [-10, -1] U [1, 10]) ? In my (possibly naive) understanding of interval arithmetic, I would think that any integer from the interval (or union of intervals) representing A/B multiplied by any integer in B should give us back A. – elokode Jan 07 '19 at 21:26
  • [OT]: `std::min(std::min(v1, v2), std::min(v3, v4))` can simply be `std::min({ v1, v2, v3, v4})` and even using `std::minmax`. – Jarod42 Jan 07 '19 at 21:27
  • I guess I should specify that the solution to my question does not have to be a single interval, but a union of intervals as required. – elokode Jan 07 '19 at 21:34
  • I've never heard of multi-interval arithmetic, and I don't see how it could ever be useful. Your hope of retrieving A from A/B and B is not going to work even in single-interval arithmetic -- in fact, it doesn't even work in regular non-interval integer arithmetic: 12/7 * 7 is 7, not 12. – TonyK Jan 07 '19 at 21:34
  • @TonyK the idea is to have A represent a set of possible signed integers, and likewise for B. The intent then is described by the question: "what are all the possible values for A/B ?" , i.e. the union of all possible values (or at least the closest approximation) for a/b given a and b selected within A and B respectively (with b != 0). – elokode Jan 07 '19 at 21:38
  • @Jarod42 the solution may create new classes Interval / IntervalSet, as required to express the solution in terms of intervals. – elokode Jan 07 '19 at 21:44

1 Answers1

1

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.:

struct Interval
{
    int left;
    int right;
};

Then I would start with addition, subtraction and multiplication. E.g.

auto operator+(Interval lhs, Interval rhs) -> Interval;

Which are quite simple.

When we get to division, as seen on your wiki link, things get more complicated.

  1. The result is no longer an interval, but a multi-interval i.e. a conjunction of intervals.
  2. These intervals have and -∞ heads.

The first problem is solved with a new class:

class MultiInterval
{
    std::vector<Interval> intervals_;
    //...
};

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.

class ExtendedInt
{
    // black magic
};

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:

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;

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 enum

enum class ExtendedIntValues { Normal, Inf, NegInf, NaN };

class ExtendedInt
{
     int value_;
     ExtendedIntValues ext_value_;
};

if ext_value_ == Normal then the value of the instance is value_, else it is ext_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:

enum class ExtendedIntValues { Inf, NegInf, NaN }; // notice no Normal enum

class ExtendedInt
{
     std::optional<int> value_;
     ExtendedIntValues ext_value_;
};

All these solution sacrifice space with std::variant sacrificing usability.

Another solution that just sacrifices 3 normal int values is to have just one int and have some values represent the special cases:

class ExtendedInt
{
     int value_;
};
constexpr ExtededInt NegInf = ...;
constexpr ExtededInt Inf = ...;
constexpr ExtededInt NaN = ... ;

Internally:

  • std::numeric_limits<int>::min() beeing NegInf
  • std::numeric_limits<int>::max() - 1 is Inf
  • std::numeric_limits<int>::max() is NaN

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 or double. This would sacrifice precision.


Once you have ExtendedInt figured out then just follow the wiki algorithm:

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;
}

Minimum interface for the above to work:

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;

Finally please note this from wikipedia:

Because several such divisions may occur in an interval arithmetic calculation, it is sometimes useful to do the calculation with so-called multi-intervals of the form

So you would ultimately have to work only with MultiInterval where an interval is a MultiInterval with just one interval.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • @bolov I don't think infinity and Nan are applicable to my problem. A and B are bounded and closed intervals (specified by 2 - possibly signed - integers), and B never contains 0. Given this clarification, can you give an implementation for operator/ ? – elokode Jan 07 '19 at 21:48
  • `-∞ + ∞` simply gives the range `[-∞, ∞]`. but division by zero really gives Nan. but unsure how to get -∞ or ∞ with integer ;-) – Jarod42 Jan 07 '19 at 21:49
  • @Jarod42 1. you are right, `x, y` terrible. Made into `left, right`. 2. I meant you would have to support arithmetic operations on `ExtendedInt`. `-∞ + 5 == -∞` and `-∞ + ∞ = NaN`. That is before you even talk about intervals. – bolov Jan 07 '19 at 21:52
  • @elokode once you have `-∞` and `∞` as values and you support arithmetic operations on them then you most definitely need `NaN`. Imagine `ExtendedInt a = -∞; ExtendedInt b = ∞; ExtendedInt s = a + b` . `s` must be `NaN`. – bolov Jan 07 '19 at 21:57
  • @bolov your idea of ExtendedInt is interesting. In the context of my division problem (I only need division) though, long long is sufficient to hold the results (and temporaries). It is acceptable for the solution to give the result in terms of long long intervals. How would you code operator/ then? – elokode Jan 07 '19 at 22:13
  • @elokode see the updated answer. It's not easy what you want to achieve. – bolov Jan 07 '19 at 22:25
  • I'd agree on that :) thx, will look again tomorrow. Ps. typo in your first condition, should be if (! contains(rhs, 0)). – elokode Jan 07 '19 at 22:41