考虑间隔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类型间隔的并集(向量)来表示。
答案 0 :(得分:1)
您不是在编写C ++,而是在那时用一些小的C ++包装C。所以这就是我要做的:
首先,我将从一个类开始一段间隔,即:
struct Interval
{
int left;
int right;
};
然后,我将从加法,减法和乘法开始。例如
auto operator+(Interval lhs, Interval rhs) -> Interval;
这很简单。
当我们进行划分时,如您在Wiki链接上所看到的,事情变得更加复杂。
∞
和-∞
头。第一个问题是通过新类解决的:
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()
蜜蜂NegInf
std::numeric_limits<int>::max() - 1
是Inf
std::numeric_limits<int>::max()
是NaN
或类似的东西,但是必须使其完全透明。算术运算必须格外小心。
另一种解决方案是意识到已经有一种(或两种)本机支持这些值并与float
或double
一起使用。这会牺牲精度。
找到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
,间隔只有一个间隔)即可。