与溢出的简单间隔/范围交叉

时间:2013-04-12 18:16:19

标签: c# c++ overflow range intervals

我正在编写一个物理内存管理器,它可以从BIOS中获取一些内存,这些内存不是关键系统数据所使用的。每个时间间隔都有0 <= start <= 2^32 - 10 <= length <= 2^32。我已经过滤了零长度间隔。

给定两个间隔S和T,我想检测它们相交的 。例如,S在T之前开始并在T之内结束(图片 a )吗?或者S在T之前开始并在T之后结束(图片 c )?

你认为解决方案很简单:

uint s_end = s_start + s_length;
uint t_end = t_start + t_length;

if (s_start < t_start)
    // S starts before T
else if (s_start < t_end)
    // S starts within T
else
    // S starts after T

if (s_end <= t_start)
    // S ends before T
else if (s_end <= t_end)
    // S ends within T
else
    // S ends after T

问题是溢出:我在技术上限于32位整数,并且间隔可以(并且经常)使用整个可用整数范围。例如,在图 b 中,由于溢出,t_end等于0。或者甚至,如图{em> f t_start = t_end = s_start = 0 t_length != 0

如何使这些间隔交叉条件与溢出一起考虑?

溢出会搞砸我的条件,但我真的不能使用64位整数(这是最简单的)。我知道必须使用我的条件进行一些巧妙的重新调整并使用加法和减法,但在制作了无数的图表并思考了几个小时后,我似乎无法绕过它。

Some examples of intervals

虽然我的问题是32位整数,但在这个图像中我使用了4位整数来简化它。问题依然存在。

4 个答案:

答案 0 :(得分:1)

好的,问题是,如果你希望你的范围跨越所有的n位,任何基于开始/结束的计算都有可能溢出。

所以诀窍是对开始/结束计算不溢出的地方进行线性变换,进行计算,然后进行线性变换。

备注

下面,我们可以安全地调用end()now 行,您可以调用排序检查(原始代码),因为在线性变换期间保留了排序,所以它是安全的。

另外,正如我在上一篇文章中提到的,有一个特殊的边界情况,即使你进行了这个变换,你也会溢出(你跨越整行) - 但是你可以编码那个特殊的边界条件。 / p>

<强>输出

5 11

<强> CODE

#include <iostream>

using type = uint8_t;

struct segment
{
    type start, length;
    type end() const { return start + length; }
};

static segment
intersect( segment s, segment t )
{
    type shift = std::min( s.start, t.start );

    // transform so we can safely call end()
    s.start -= shift;     // doesn't affect length
    t.start -= shift;     // doesn't affect length

    // we can safely call end() now ----------------------------------------------
    type u_start  = std::max( s.start, t.start );
    type u_end    = std::min( s.end(), t.end() );
    type u_length = u_end - u_start;

    segment u{ u_start, u_length };

    // transform back
    u.start += shift;

    return u;
}

int main()
{
    segment s{ 3, 13 }, t{ 5, 11 };
    segment u = intersect( s, t );

    std::cerr << uint32_t( u.start ) << " " << uint32_t( u.length ) << std::endl;

    return 0;
}

答案 1 :(得分:0)

您的示例代码不会枚举所有情况。例如,间隔也可以在同一点开始或结束。

要解决溢出问题,您可以尝试根据开始比较添加不同的数学运算,而不包括计算结束点。类似的东西:

if (s_start < t_start)
{
    // S starts before T
    uint start_offset = t_start - s_start;
    if (start_offset < s_length)
    {
        if (s_length - start_offset < t_length)
        {
            // ...
        }
        else ...
    } else ...
}

答案 2 :(得分:0)

一种解决方案是将0的结尾视为特例。将其编织到if语句中,它变为:

uint s_end = s_start + s_length;
uint t_end = t_start + t_length;

if (s_start < t_start)
    // S starts before T
else if (t_end == 0 || s_start < t_end)
    // S starts within T
else
    // S starts after T

if (s_end != 0 && s_end <= t_start)
    // S ends before T
else if (t_end == 0 || s_end == t_end
    || (s_end != 0 && s_end <= t_end))
    // S ends within T
else
    // S ends after T

这看起来是正确的。

答案 3 :(得分:0)

我不知道你对(f)这样的条件做了什么,因为那里的32位t_length将为0。 假设你以某种方式管理这个案例,当你过滤掉长度= 0,这可能意味着0和2 ^ 32时,基本的想法是:

bool s_overflows=false;
if(s_start>0)//can't have overflow with s_start==0,
{
    uint32 s_max_length=_UI32_MAX-s_start+1;
    if(s_length==s_max_length) s_overflow=true;
}
bool t_overflows=false;
if(t_start>0)
{
    uint32 t_max_length=_UI32_MAX-t_start+1;
    if(t_length==t_max_length) t_overflow=true;
}

然后你只是做你的计算,但如果s_overflow为真,你不计算s_end - 你不需要它,因为你已经知道它是0x100000000。 t_overflow也是如此。由于这些已经是特殊情况,就像start = 0一样,它们不应该使代码复杂化。