说我有一个双精度x,其值> 0且<100万。我想将其小数点向左移动,直到> 100万且<1000万。因此,例如23.129385变为2313938.5。
我现在所做的只是乘以10,直到达到停止状态。但是,我经常执行此算法,因此,如果我可以以某种方式对其进行优化,将会很有帮助。与x的大小无关的恒定时间解决方案显然是理想的,但到目前为止,我还无法提出一个解决方案。
答案 0 :(得分:3)
某些语言,例如带有frexp的C ++,非常便宜地将二进制指数公开为整数。
如果幸运的话,您可以有一个预先计算的查找表pow2to10
,它从2k可能的二进制指数到10的幂。拥有另一个10的幂的查找表lookup10
。现在您的计算看起来像:
frexp(x , &n);
int i = pow2to10[n];
if (lookup10[i+1] <= x) {
i++;
}
double result = x * lookup10[i];
现在,您将拥有3个数组查找,一个比较和一个乘法,而不是一系列乘法。如果要在紧密循环中执行此操作,请将pow2to10
存储为short int
的数组,尝试将范围调整为所需的范围,并且查找将位于可适合L1的数据结构中缓存。
如果不是很幸运,可以重复乘以10,而不是重复乘以,而是与一系列已知的10的幂进行比较。请注意,如果您使用的是高级语言,则可能会发现执行指令的开销胜过比较与乘法的节省。进行二进制搜索以减少查找可能很诱人,但我敢打赌线性搜索会更好,因为这有助于分支预测。
答案 1 :(得分:0)
您没有说哪种语言或哪种类型的CPU,或者数字的分配方式(例如,如果大多数数字小于5,但很少有大数字,或者..);但是...
我能想到的最快的标量版本(可能是C和现代的80x86 CPU)是:
// x is between 1 and 999999
unsigned long x_int = x; // Integer comparisons are possibly faster
double multiplier;
if(x_int < 1000) {
// x is between 1 and 999
if(x_int < 100) {
// x is between 1 and 99
if(x_int < 10) {
// x is between 1 and 9
multiplier = 1000000;
} else {
// x is between 10 and 99
multiplier = 100000;
}
} else {
// x is between 100 and 999
multiplier = 10000;
}
} else {
// x is between 1000 and 999999
if(x_int < 10000) {
// x is between 1000 and 9999
multiplier = 1000;
} else {
// x is between 10000 and 999999
if(x_int < 100000) {
// x is between 10000 and 99999
multiplier = 100;
} else {
// x is between 100000 and 999999
multiplier = 10;
}
}
}
x *= multiplier;
这最多可添加2或3个分支,每个值一个乘法。 注意:对于现代80x86,可以用CMOVcc
指令代替最终分支。
如果您经常这样做;那么下一步将是尝试使用SIMD同时执行多个值(随后是多线程/多CPU)。