如何有效地移动数字中的小数点直到达到某个阈值

时间:2019-07-07 17:59:58

标签: algorithm math performance

说我有一个双精度x,其值> 0且<100万。我想将其小数点向左移动,直到> 100万且<1000万。因此,例如23.129385变为2313938.5。

我现在所做的只是乘以10,直到达到停止状态。但是,我经常执行此算法,因此,如果我可以以某种方式对其进行优化,将会很有帮助。与x的大小无关的恒定时间解决方案显然是理想的,但到目前为止,我还无法提出一个解决方案。

2 个答案:

答案 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)。