【问题标题】:Fastest way to calculate the decimal length of an integer? (.NET)计算整数十进制长度的最快方法? (。网)
【发布时间】:2009-03-24 23:04:45
【问题描述】:

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

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

(int)Math.Truncate(Math.Log10(x)) + 1;

但是,这表现相当差。由于我的应用程序只发送正值,并且长度相当均匀地分布在 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
    }
}

这可以通过平均 4 次比较来计算长度。

那么,我还有什么其他技巧可以让这个功能更快吗?

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

更新:

我采纳了 Norman 的建议,稍微改变了 ifs,结果平均只有 3 次比较。根据肖恩的评论,我删除了 Math.Truncate。总之,这推动了大约 10% 的增长。谢谢!

【问题讨论】:

  • 我怀疑这已接近最佳状态。不过,我有兴趣看到任何答案;-p
  • 我相信你可以将返回稍微简化为'return 1 + (int) Math.Log10(x)'
  • ToString() 方法有多慢?
  • 在我的测试中,ToString().Length() 比 if/return 方式慢大约 35 倍。
  • 我不懂 C#,但我猜 Math.log(x) 在进行计算之前会将 x 转换为 double。您是否有可能遇到 log(x) 的浮点舍入问题? (例如,对于 10^n,四舍五入到 ~ 10^n - 1^-15)

标签: c# .net performance integer


【解决方案1】:

两个建议:

  1. 分析并将常见案例放在首位。
  2. 进行二分搜索以在最坏的情况下最大限度地减少比较次数。您可以使用 3 次比较在 8 种备选方案中做出决定。

除非分布非常倾斜,否则这种组合可能不会给您带来太多收益。

【讨论】:

  • 不错。我以不同的方式布置了 ifs,这有点帮助。谢谢!
  • 没有太多偏差,但重新组织 ifs 平均消除了比较。
【解决方案2】:

来自 Sean Anderson 的Bit Twiddling Hacks

查找整数的以 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]);

以 10 为底的整数对数由下式计算 首先使用其中一种技术 以上用于查找对数基数 2. 通过 关系 log10(v) = log2(v) / log2(10),我们需要将它乘以 1/log2(10),大约是 1233/4096,或 1233 后跟一个权利 12 的班次。需要加一 因为 IntegerLogBase2 轮 向下。最后,由于值 t 是 只是一个可能不正确的近似值 加一,精确值由下式找到 减去 v

此方法需要多 6 次操作 比 IntegerLogBase2。可能会加速 up(在内存快的机器上 访问)通过修改日志库 2 上面的表查找方法,以便 条目保存为 t 计算的内容 (即,预添加、-mulitply 和 -转移)。这样做总共只需要 9 次操作即可找到 以 10 为底,假设有 4 个表 已使用(v 的每个字节一个)。

就计算 IntegerLogBase2 而言,该页面上提供了几种替代方案。对于各种高度优化的整数运算,它是一个很好的参考。

您的版本也有一个变体,除了它假设值(而不是值的以 10 为底的对数)是均匀分布的,因此会进行指数排序的搜索:

以显而易见的方式找到整数的以 10 为底的整数对数

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;

这种方法在输入时效果很好 在 32 位上均匀分布 值,因为 76% 的输入是 被第一次比较发现,21% 是 被第二次比较捕获,2% 是 被第三个抓住,依此类推 (将剩余的砍掉 90% 每次比较)。因此, 需要少于 2.6 次操作 平均。

【讨论】:

    【解决方案3】:

    这是我测试过的二进制搜索版本,它每次只使用五次比较即可处理 64 位整数。

    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; }
    }
    

    我怀疑这会比你已经在做的更快。但这是可以预见的。

    【讨论】:

    • 我认为分歧会扼杀它——它的速度大约是纯 if/return 方式的两倍。
    • 我并不感到惊讶。该算法是为以 2 为底的对数设计的,在这种情况下,可以用移位代替除法,这通常要快得多。但它确实很漂亮:-)
    【解决方案4】:

    我做了一些测试,这似乎比你现在的代码快 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;
    }
    

    【讨论】:

    • 嗯,我插入了那个版本,速度大大下降了。
    • 嗯,最坏的情况应该只是稍微慢一点......也许是因为你的实际数据看起来与我创建的随机测试数据完全不同,我也将它作为 x64 运行,它对Int64 操作。如果您愿意,可以尝试我发布的新版本。
    【解决方案5】:

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

    【讨论】:

    • 是的,它本身并不 ,我只是希望能再调整一下。
    【解决方案6】:

    这个我没测试过,但是基本定律的变化说:

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

    如果实现得当,Log2 应该比 Log10 快一点。

    【讨论】:

      【解决方案7】:
      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;
          }
      

      【讨论】:

        【解决方案8】:

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

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

        【讨论】:

          【解决方案9】:

          你说的长度是什么意思?零的数量还是一切? This 有重要的数字,但你明白了

          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");  
          }
          

          【讨论】:

          • 是的,完整的长度就像你刚刚做了 ToString().Length。问题是调用 Math.Log10 会降低性能。仅使用 Log10 方式会导致代码慢很多倍。
          【解决方案10】:

          这是一个简单的方法。

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

          如果 number 是 unsigned int 则不需要“Math.Abs​​(number)”。

          我用所有数字类型做了扩展方法。

              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.
          

          【讨论】:

          • 使用动态远非最快的解决方案。
          • 在我的示例中动态使用 int 或 long。那是替换动态关键字 int 或 long 类型的方法。
          猜你喜欢
          • 2020-12-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-26
          • 2010-12-13
          • 2020-01-10
          • 2013-06-26
          相关资源
          最近更新 更多