用于2个有符号整数间隔之间的除法的C ++算法

时间:2019-01-07 20:31:34

标签: c++ math interval-arithmetic

考虑间隔A = [x1,y1]和B = [x2,y2],两个间隔代表有符号整数。

Interval arithmetic page on Wikipedia给出一个公式来解决B不包含0的情况,其C ++实现可能如下:

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

输出:

A = [-10000, 150000]
B = [-10, 10]
A/B = [-15000, 15000]

按预期,由于B包含0,因此结果是不正确的。例如,由于1是B的一部分,因此150000应该在A / B内,但是不在A / B之内。

从B排除0时,可行的算法是什么?解决方案是否应该在-1和1之间使用多个间隔(即排除0)并以某种方式加入它们?

编辑:该解决方案可以用long long类型间隔的并集(向量)来表示。

1 个答案:

答案 0 :(得分:1)

您不是在编写C ++,而是在那时用一些小的C ++包装C。所以这就是我要做的:

首先,我将从一个类开始一段间隔,即:

struct Interval
{
    int left;
    int right;
};

然后,我将从加法,减法和乘法开始。例如

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

这很简单。

当我们进行划分时,如您在Wiki链接上所看到的,事情变得更加复杂。

  1. 结果不再是一个间隔,而是一个多间隔,即间隔的合计。
  2. 这些间隔有-∞头。

第一个问题是通过新类解决的:

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

对于第二个问题……那么我们将无法再使用int作为数据。因此,您需要一个新的整数类,其中可以包含值∞, -∞, NaNNaN是副产品,例如-∞ + ∞ = 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()蜜蜂NegInf
  • std::numeric_limits<int>::max() - 1Inf
  • std::numeric_limits<int>::max()NaN

或类似的东西,但是必须使其完全透明。算术运算必须格外小心。

另一种解决方案是意识到已经有一种(或两种)本机支持这些值并与floatdouble一起使用。这会牺牲精度。


找到ExtendedInt之后,请按照Wiki算法进行操作:

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,间隔只有一个间隔)即可。