C# 计算整数小数长度的最快方法?(.NET)

C# 计算整数小数长度的最快方法?(.NET),c#,.net,performance,integer,C#,.net,Performance,Integer,我有一些代码对64位整数进行了大量比较,但是它必须考虑数字的长度,就像它被格式化为字符串一样。我不能更改调用代码,只能更改函数 最简单的方法(除了.ToString().Length)是: 然而,这表现相当糟糕。由于我的应用程序只发送正值,并且长度在2和9之间分布相当均匀(有些偏向9),因此我预先计算了这些值,并使用if语句: static int getLen(long x) { if (x < 1000000) { if (x < 100) return

我有一些代码对64位整数进行了大量比较,但是它必须考虑数字的长度,就像它被格式化为字符串一样。我不能更改调用代码,只能更改函数

最简单的方法(除了.ToString().Length)是:

然而,这表现相当糟糕。由于我的应用程序只发送正值,并且长度在2和9之间分布相当均匀(有些偏向9),因此我预先计算了这些值,并使用if语句:

static int getLen(long x) {
    if (x < 1000000) {
        if (x < 100) return 2;
        if (x < 1000) return 3;
        if (x < 10000) return 4;
        if (x < 100000) return 5;
        return 6;
    } else {
        if (x < 10000000) return 7;
        if (x < 100000000) return 8;
        if (x < 1000000000) return 9; 
        return (int)Math.Truncate(Math.Log10(x)) + 1; // Very uncommon
    }
}
static int getLen(长x){
如果(x<1000000){
如果(x<100)返回2;
如果(x<1000)返回3;
如果(x<10000)返回4;
如果(x<100000)返回5;
返回6;
}否则{
如果(x<10000000)返回7;
如果(x<100000000)返回8;
如果(x<100000000)返回9;
return(int)Math.Truncate(Math.Log10(x))+1;//非常少见
}
}
这使得计算长度的平均值为4

那么,有没有其他技巧可以让这个函数更快呢

编辑:这将作为32位代码(Silverlight)运行

更新:

我采纳了诺曼的建议,对ifs做了一些修改,结果平均只有3次比较。根据肖恩的评论,我删除了Math.Truncate。加在一起,这使事情增加了约10%。谢谢

两个建议:

  • 分析并将常见案例放在首位
  • 在最坏的情况下,执行二进制搜索以最小化比较的数量。您可以使用三个比较来决定8个备选方案

  • 除非分布非常倾斜,否则这种组合可能买不到多少东西。

    不确定这是否更快。。但你总是可以数

    static int getLen(long x) {
        int len = 1;
        while (x > 0) {
            x = x/10;
            len++
        };
        return len
    }

    你说的长度是什么意思?零的数量还是所有?有重要的数字,但你得到了一般的想法

    public static string SpecialFormat(int v, int sf)  
    {  
         int k = (int)Math.Pow(10, (int)(Math.Log10(v) + 1 - sf));  
         int v2 = ((v + k/2) / k) * k;  
         return v2.ToString("0,0");  
    }
    

    这是一个二进制搜索版本,我已经测试过了,它可以处理64位整数,每次使用5个比较

    int base10len(uint64_t n) {
      int len = 0;
      /* n < 10^32 */
      if (n >= 10000000000000000ULL) { n /= 10000000000000000ULL; len += 16; }
      /* n < 10^16 */
      if (n >= 100000000) { n /= 100000000; len += 8; }
      /* n < 100000000 = 10^8 */
      if (n >= 10000) { n /= 10000; len += 4; }
      /* n < 10000 */
      if (n >= 100) { n /= 100; len += 2; }
      /* n < 100 */
      if (n >= 10) { return len + 2; }
      else         { return len + 1; }
    }
    
    intbase10len(uint64\u t n){
    int len=0;
    /*n<10^32*/
    如果(n>=10000000000000ull){n/=10000000000000ull;len+=16;}
    /*n<10^16*/
    如果(n>=100000000){n/=100000000;len+=8;}
    /*n<100000000=10^8*/
    如果(n>=10000){n/=10000;len+=4;}
    /*n<10000*/
    如果(n>=100){n/=100;len+=2;}
    /*n<100*/
    如果(n>=10){返回len+2;}
    else{return len+1;}
    }
    

    我怀疑这会比你现在做的更快。但这是可以预测的。

    您在代码中评论说,10位或更多的数字是非常罕见的,因此您的原始解决方案不错

    我做了一些测试,这似乎比您现在的代码快2-4倍:

    static int getLen(long x) {
        int len = 1;
        while (x > 9999) {
            x /= 10000;
            len += 4;
        }
        while (x > 99) {
            x /= 100;
            len += 2;
        }
        if (x > 9) len++;
        return len;
    }
    
    编辑:
    以下是一个使用更多Int32操作的版本,如果您没有x64应用程序,该版本应该工作得更好:

    static int getLen(long x) {
        int len = 1;
        while (x > 99999999) {
            x /= 100000000;
            len += 8;
        }
        int y = (int)x;
        while (y > 999) {
            y /= 1000;
            len += 3;
        }
        while (y > 9) {
            y /= 10;
            len ++;
        }
        return len;
    }
    

    我还没有对此进行测试,但基本法的变化表明:

    Log10(x)=Log2(x)/Log2(10)

    如果实现正确,Log2应该比Log10快一点。

    来自Sean Anderson的:

    查找整数的以10为基数的整数日志

    unsigned int v; // non-zero 32-bit integer value to compute the log base 10 of 
    int r;          // result goes here
    int t;          // temporary
    
    static unsigned int const PowersOf10[] = 
        {1, 10, 100, 1000, 10000, 100000,
         1000000, 10000000, 100000000, 1000000000};
    
    t = (IntegerLogBase2(v) + 1) * 1233 >> 12; // (use a lg2 method from above)
    r = t - (v < PowersOf10[t]);
    
    当输入 在32位上均匀分布 值,因为76%的输入是 第一次比较发现,21%的人 第二次比较发现,2%的人 被第三个抓住,等等 (将剩余部分削减90% 每次比较)。因此, 上需要少于2.6个操作 一般

    static int getDigitCount(int x)
    {
    整数位数=(x<0)?2:1;//“0”有一个位数,负数需要空格作为符号
    while(x>9)//在'9'之后需要更多
    {
    x/=10;//分而治之
    数字++;
    }
    返回数字;
    }
    
    这是一种简单的方法

    private static int GetDigitCount(int number)
    {
        int digit = 0;
    
        number = Math.Abs(number);
    
        while ((int)Math.Pow(10, digit++) <= number);
    
        return digit - 1;
    }
    

    我怀疑这接近最佳状态。不过,我有兴趣看看答案-p您可以将返回稍微简化为'return 1+(int)Math.Log10(x)'我相信ToString()方法有多慢?在我的测试中,ToString().Length()比if/return方法慢35倍左右。我不知道C#,但我猜Math.log(x)在计算之前会将x转换为双精度。对于log(x),您是否有可能遇到浮点舍入问题?(例如,对于10^n,四舍五入到~10^n-1^-15)很好。我以不同的方式列出了ifs,这有点帮助。谢谢没有太多的偏差,但是重新组织ifs平均删除了一个比较。我认为这种差异扼杀了它——它的速度大约是纯if/返回方式的两倍。我并不感到惊讶。该算法是为对数基数2设计的,在这种情况下,可以用移位来代替除法,移位通常要快得多。但它确实很漂亮:-)是的,完整的长度就像你刚才做的一样。问题是调用Math.Log10会降低性能。仅仅使用Log10方式会导致代码速度慢很多倍。是的,它本身并不坏,我只是希望对它进行更多的调整。嗯,我插入了那个版本,速度大幅下降。嗯,最坏的情况应该只是稍微慢一点。。。可能是因为您的实际数据看起来与我创建的随机测试数据完全不同,而且我将其作为x64运行,这对Int64操作的影响较小。如果您愿意,可以尝试我发布的新版本。使用动态远远不是最快的解决方案。在我的示例中,动态使用int或long。这是替换动态关键字int或long类型的方法。
    unsigned int v; // non-zero 32-bit integer value to compute the log base 10 of 
    int r;          // result goes here
    
    r = (v >= 1000000000) ? 9 : (v >= 100000000) ? 8 : (v >= 10000000) ? 7 : 
        (v >= 1000000) ? 6 : (v >= 100000) ? 5 : (v >= 10000) ? 4 : 
        (v >= 1000) ? 3 : (v >= 100) ? 2 : (v >= 10) ? 1 : 0;
    
    static int getDigitCount( int x )
        {
        int digits = ( x < 0 ) ? 2 : 1; // '0' has one digit,negative needs space for sign
        while( x > 9 ) // after '9' need more
            {
            x /= 10; // divide and conquer
            digits++;
            }
        return digits;
        }
    
    private static int GetDigitCount(int number)
    {
        int digit = 0;
    
        number = Math.Abs(number);
    
        while ((int)Math.Pow(10, digit++) <= number);
    
        return digit - 1;
    }
    
        private static int GetDigitCount(dynamic number)
        {
            dynamic digit = 0;
    
            number = Math.Abs(number);
    
            while ((dynamic)Math.Pow(10, digit++) <= number)
                ;
    
            return digit - 1;
        }
    
        public static int GetDigit(this int number)
        {
            return GetDigitCount(number);
        }
    
        public static int GetDigit(this long number)
        {
            return GetDigitCount(number);
        }
    
    int x = 100;
    int digit = x.GetDigit();  // 3 expected.