缩放(长整数)时防止溢出并保持精度

时间:2013-07-01 12:37:40

标签: integer precision long-integer integer-overflow

假设我在给定的范围中有一个位置 pos ,这样:

0< = pos < 范围

该范围内的该位置可以包括两个不同的上下文,一个是范围是整数值,即 pos < 范围< 2 31 ,另一个范围是长整数,即最多 pos < 范围< 2 63 。如果我想在这些上下文之间移动,我需要将位置缩放到新范围,以便将其正确舍入到最接近的(长整数)整数值。所以,从技术上讲,我想做的就是:

pos new = floor( pos old * range / 范围

不幸的是,这种直截了当的方法并没有成功,因为它会溢出(因为 pos old * range new 可以大到~2 94 )如果我先进行乘法运算,或者如果我先进行除法则给出舍入误差。使用浮点值进行数学运算通常也没有帮助,因为它们没有提供足够的精度,因此也可能导致不正确的舍入(我只有双精度可用)。

我找到了一种正确地从整数范围扩展到长整数范围的方法:

public long scaleUp(int oldPos, int oldRange, long newRange) {
    return (newRange / oldRange) * oldPos + 
           (newRange % oldRange) * oldPos / oldRange;
}

这可以确保计算在任何时候都不会超出长整数的限制,也不会因为过早的舍入而失去准确性(模数会捕获在第一个分区中丢失的部分)。

我现在想弄清楚的是,这是一种反向缩放的方法:

public int scaleDown(long oldPos, long oldRange, int newRange) {
    return ??? ;
}

不确定这是否应该比其他功能更困难,但不知怎的,我没有看到它。

几句话:

  • 我想避免使用浮点运算,因为我总是觉得很难说服自己,由于四舍五入,某些公式在一些非常罕见的情况下产生意外结果是不可能的
  • 我宁愿不使用BigInteger库
  • 虽然这里的代码示例是Java,但这确实是一个与语言无关的问题

3 个答案:

答案 0 :(得分:0)

我找到了一个不是100%完成的答案,但涵盖了我的程序中出现的所有特殊情况。您可以在我在数学StackExchange上发布的相应问题的答案中找到派生的详细信息:https://math.stackexchange.com/q/433729/84557

这是粗略的轮廓:

public int scaleDown(long oldPos, long oldRange, int newRange) {
    if (oldPos <= Long.MAX_VALUE/newRange)
        return (int) (oldPos*newRange/oldRange);
    assert oldRange >= newRange*newRange : "Case not supported yet"; // Never happens in my code
    int newPos = (int) (oldPos / (oldRange/newRange));
    if (!isOk(newPos)) newPos--; // Check might be implementation specific
    return newPos;
}

不完整,但也许它对某人有用。

答案 1 :(得分:0)

我参加这个聚会非常非常晚,但我遇到了同样的问题。

请注意,我一直在为x使用pos,为y使用oldRange,为w使用newRange ,即求解等式z中的x/y = z/w

以下是我考虑过的解决方案:

我可以使用浮点数,但是64位整数的精度是否可接受完全取决于平台是否支持尾数至少为64位的浮点数。 (许多平台都没有。)

我可以将输入域重新定义为小于输出范围的输入域不超过一半,然后按比例放大 - 即q = y / w; scaleUp(x / q / 2, y / q / 2, w);,在最坏的情况下,最低位的位将完全丢失,在最好的情况下,根本不会丢失任何信息,但我不想在任何情况下丢失低位。

我可以使用某种二进制搜索来查找要从x / (y / w)中减去的值以补偿错误,但我不认为使用循环或递归对性能的影响是可以接受的

现在我提出的最好的事情就是通常计算x / (y / w) - (y % w) * x / y并且当乘法会溢出时 - 授予的,只应该用于相对较小的输入域(当然输入较少)而不仅仅是计算x * w / y) - 使用诸如this one之类的算法来捕获产品的高位。

但我仍然觉得 是一种更简单的方法。

答案 2 :(得分:0)

编辑:该解决方案不正确,请参见评论中的讨论。

我有一个没有特殊情况的解决方案。我没有正确性的证明,但看起来似乎很合理,我找不到反例。

int scaleDown2(long longPos, long longRange, int shortRange) {
    int p = longPos / (longRange / shortRange);
    return p - ((p * (longRange % shortRange)) / longRange);
}