假设我在给定的范围中有一个位置 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 ??? ;
}
不确定这是否应该比其他功能更困难,但不知怎的,我没有看到它。
几句话:
答案 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);
}