在C#中获取无符号长整型数的位数

10
我正在尝试确定C# ulong数字中的位数,我正在尝试使用一些数学逻辑而不是使用ToString().Length来实现。我还没有对这两种方法进行基准测试,但已经看到其他帖子使用System.Math.Floor(System.Math.Log10(number)) + 1来确定数字的位数。似乎很好用,直到从999999999999997过渡到999999999999998时,我开始得到错误的计数。
有人遇到过这个问题吗?
我曾看到类似的Java强调的帖子@Why log(1000)/log(10) isn't the same as log10(1000)?,还有一篇帖子@How to get the separate digits of an int number?,它指出了如何使用%运算符可能以更多的代码实现相同的功能。
以下是我用来模拟此问题的代码。
Action<ulong> displayInfo = number => 
 Console.WriteLine("{0,-20} {1,-20} {2,-20} {3,-20} {4,-20}", 
  number, 
  number.ToString().Length, 
  System.Math.Log10(number), 
  System.Math.Floor(System.Math.Log10(number)),
  System.Math.Floor(System.Math.Log10(number)) + 1);

Array.ForEach(new ulong[] {
 9U,
 99U,
 999U,
 9999U,
 99999U,
 999999U,
 9999999U,
 99999999U,
 999999999U,
 9999999999U,
 99999999999U,
 999999999999U,
 9999999999999U,
 99999999999999U,
 999999999999999U,
 9999999999999999U,
 99999999999999999U,
 999999999999999999U,
 9999999999999999999U}, displayInfo);

Array.ForEach(new ulong[] {
 1U,
 19U,
 199U,
 1999U,
 19999U,
 199999U,
 1999999U,
 19999999U,
 199999999U,
 1999999999U,
 19999999999U,
 199999999999U,
 1999999999999U,
 19999999999999U,
 199999999999999U,
 1999999999999999U,
 19999999999999999U,
 199999999999999999U,
 1999999999999999999U
}, displayInfo);

提前感谢

Pat


2
你不需要使用“%”运算符,整数除法就足够了。而且,“更多的代码”是一个单语句循环。并不是那么多的代码。 - Konrad Rudolph
2
ToString().Length 这个调用真的那么昂贵吗?也许它会慢一点,但对于任何值它都是准确的。对我来说,准确性比效率更加可取。 - Jeff LaFay
1
当使用Log10函数时得到不正确的结果是有道理的,因为该函数使用double类型,无法精确表示非常大的整数(请参见http://en.wikipedia.org/wiki/Double_precision_floating-point_format)。 - Justin
我同意,我并没有看到使用 ToString().Length 有什么特别错误的地方。此外,只要签名正确,您可以将您的 Action 更改为普通方法。 - Daniel T.
请参见https://dev59.com/olHTa4cB1Zd3GeqPUcVz如果您想要“速度”,请查看“级联if”版本(可以分成几个部分,类似于迭代方法)。否则,可能会在此处使用`ToString`。`if`可以移动到List的快速中断迭代中。 - user166390
如果您决定使用ToString,请确保指定正确的(不变的)文化 - 有些文化中整数有分隔符,例如100,000,因此在这些文化中,ToString().Length将是错误的。 - Alexei Levenkov
4个回答

7

log10将涉及浮点数转换,因此存在舍入误差。对于双精度浮点数而言,误差非常小,但对于精确的整数来说却是个大问题!

除了.ToString()方法和浮点数方法之外,如果你想要实现该功能,那么我认为你必须使用迭代方法,但我建议使用整数除法而不是取模。

整数除以10。结果是否大于0?如果是,则继续迭代。如果不是,则停止。所需迭代次数即为数字位数。

例如,5 -> 0;1次迭代 = 1位数字。

1234 -> 123 -> 12 -> 1 -> 0;4次迭代 = 4位数字。


1
只是个提示:这种方法的轻微修改实际上可以仅使用整数乘法和比较来完成。查看我回复中的查找表应该会有所启示(前一句不需要查找表)。 - user166390
1
当然,可以反过来做。如果乘法操作更快,这可能更可取。 - winwaed
它确实更快。问题是是否可察觉;-) 我只是觉得留下评论,因为这在我完成帖子后的那个晚上让我印象深刻,并意识到我以前从未见过它被建议作为解决此问题的方法(这可能意味着几件事情...)。 - user166390

6
我会使用 ToString().Length,除非你知道这将被调用数百万次。
“过早的优化是万恶之源”- Donald Knuth

3

根据文档

默认情况下,Double类型包含15位小数精度,但内部维护最大17位数字。

我怀疑您遇到了精度限制。 您的值999,999,999,999,998可能已经达到了精度上限。并且由于在调用Math.Log10之前必须将ulong转换为double,所以会看到此错误。


0

其他答案已经解释了为什么会发生这种情况。

下面是一个相当快速的方法来确定整数的“长度”(有些情况除外)。这本身并不是非常有趣——但我在这里包括它,因为使用这种方法“与Log10结合使用”可以在不需要第二个log调用的情况下完美地获得无符号长整型的整个范围的精度。

// the lookup would only be generated once
// and could be a hard-coded array literal
ulong[] lookup = Enumerable.Range(0, 20)
    .Select((n) => (ulong)Math.Pow(10, n)).ToArray();
ulong x = 999;
int i = 0;
for (; i < lookup.Length; i++) {
    if (lookup[i] > x) {
        break;
    }
}
// i is length of x "in a base-10 string"
// does not work with "0" or negative numbers

这种查找表的方法可以轻松地转换为任何进制。相比迭代除以基数的方法,这种方法应该更快,但是性能分析留给读者自己练习。(直接的 if-then 分支被分成“组”可能更快,但对我来说太过重复了。)

编码愉快。


将查找表设为静态并使用二分查找而不是直接迭代可能会进一步加快此解决方案的速度。 如果需要更快的速度,将二分查找展开成一系列if语句可能是最快的方法。 - Alexei Levenkov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接