如何在C#中获取整数的第一位数字?

88

在 C# 中,获取一个整数的第一位数字的最佳方法是什么?我想到了将整数转为字符串,找到字符串的第一个字符,然后再将其转回整数。

int start = Convert.ToInt32(curr.ToString().Substring(0, 1));

虽然这种方式能够解决问题,但感觉可能有一种好的、简单的基于数学的解决方案。字符串操作感觉不太优雅。

编辑:无论速度差异如何,使用mystring[0]而不是Substring()仍然只是一种字符串操作。


这甚至比递归方法还要慢。=) - J. Steen
我的错,我忘记重置秒表了 =),它比其他的真的慢很多。 - Quan Mai
2
这将会在负数上失败。 - plinth
2
一个整数的第一位始终是1,并进行零抑制(01010101010100101011011011100101)LOL。 - Zachary Scott
26个回答

232

基准测试

首先,您必须确定您所说的“最佳”解决方案是什么意思,当然,这考虑到算法的效率、可读性/可维护性以及将来出现错误的可能性。不过,仔细的单元测试通常可以避免这些问题。

我对每个示例运行了1000万次,并且结果值是经过的ElapsedTicks数量。

接下来,按照速度从慢到快,这些算法如下:

将字符串转换为字符数组,取第一个字符

int firstDigit = (int)(Value.ToString()[0]) - 48;

结果:

12,552,893 ticks

使用对数

int firstDigit = (int)(Value / Math.Pow(10, (int)Math.Floor(Math.Log10(Value))));

结果:

9,165,089 ticks

循环

while (number >= 10)
    number /= 10;

结果:

6,001,570 ticks

条件语句

int firstdigit;
if (Value < 10)
     firstdigit = Value;
else if (Value < 100)
     firstdigit = Value / 10;
else if (Value < 1000)
     firstdigit = Value / 100;
else if (Value < 10000)
     firstdigit = Value / 1000;
else if (Value < 100000)
     firstdigit = Value / 10000;
else if (Value < 1000000)
     firstdigit = Value / 100000;
else if (Value < 10000000)
     firstdigit = Value / 1000000;
else if (Value < 100000000)
     firstdigit = Value / 10000000;
else if (Value < 1000000000)
     firstdigit = Value / 100000000;
else
     firstdigit = Value / 1000000000;

结果:

1,421,659 ticks

展开优化的循环

if (i >= 100000000) i /= 100000000;
if (i >= 10000) i /= 10000;
if (i >= 100) i /= 100;
if (i >= 10) i /= 10;

结果:

1,399,788 ticks

注意:

每个测试调用Random.Next()以获取下一个int


22
这很琐碎。我欣赏你所做的,内容相当丰富,但对于99%的应用来说,这有些过度了。我担心一些初学者程序员会留下不好的印象。(过于注重底层细节而牺牲可读性)不过还是很有趣的 :) - Samantha Branham
3
+1 支持基准测试,但我认为这太过于复杂了。如果您对性能如此担忧,那么您可能一开始就不应该使用C# :) - GrahamS
17
+1 - 很棒的东西。这种镀金、豪华型号的过度设计显然表明你热爱编码,为你点赞。而且,Stuart,我不认为这会污染那些毫无准备的新手们的思想。希望这能教他们培养对软件的热爱。 - Mark Brittingham
Random()函数在范围内生成线性分布的数字。例如,其中90%的数字将大于1000000000。因此,“条件判断”解决方案平均需要进行约10次检查。只需重新排序检查顺序,我们就可以使其运行速度更快。 - Bulat
2
@Vikash - 将 char 强制转换为 int 会计算出字符的代码点,因此将字符 0 转换后的值为 48。减去 48 实际上是将其转换为整数。另一个例子,将字符 5 转换为 int 的值为 53,从中减去 48 的结果为 5。 - John Rasch
显示剩余13条评论

130

以下是方法

int i = Math.Abs(386792);
while(i >= 10)
    i /= 10;

i 将包含您需要的内容


31

试试这个

public int GetFirstDigit(int number) {
  if ( number < 10 ) {
    return number;
  }
  return GetFirstDigit ( (number - (number % 10)) / 10);
}

编辑

有几个人要求循环版本。

public static int GetFirstDigitLoop(int number)
{
    while (number >= 10)
    {
        number = (number - (number % 10)) / 10;
    }
    return number;
}

1
递归用于这种情况实在太糟糕了,你应该使用循环。 - Welbog
1
我希望优化器能够将第一个版本视为可以进行尾调用优化的内容,这样就不会变慢了。 - rmeador
@Welbog:你真丢人。为什么偏爱循环?没有理由。Keltex:你有测量过吗?慢于什么?...你的解决方案?肯定不是。从未听说过优化编译器吗?这是如此微不足道的尾递归,编译器将完全消除它。 - Konrad Rudolph
1
但是,@Jared,在这里减去最后一位数字是完全没有必要的。只需除以10即可。 - Konrad Rudolph
应该像Anton的帖子一样写成“while (number >= 10)”,对吧? - Patrick McDonald
显示剩余5条评论

27

我最能想到的是:

int numberOfDigits = Convert.ToInt32(Math.Floor( Math.Log10( value ) ) );

int firstDigit = value / Math.Pow( 10, numberOfDigits );

你可以通过使用divisor=Convert.ToInt32(Math.Pow(10, Math.Floor(Math.Log10(value)))); firstDigit = value/divisor来避免整数和双精度浮点数之间的不必要转换。 - MartinStettner
不幸的是,当值为1(返回0)或值为负时,这种方法无法正常工作。解决方法是:int numberOfDigits = 1 + Convert.ToInt32(Math.Floor(Math.Log10(Math.Abs(value)))); - DanDan

18

Anton的答案变体:

 // cut down the number of divisions (assuming i is positive & 32 bits)
if (i >= 100000000) i /= 100000000;
if (i >= 10000) i /= 10000;
if (i >= 100) i /= 100;
if (i >= 10) i /= 10;

我想我会把它留在 /10/1000。你仍然可以更积极地进行除法,但是会少一些混乱。对创意加一分 :) - Samantha Branham
这让我想起了BCL中的一个函数,有人发现该方法非常难读,但基本上是在进行循环比较,并将比较分组为十个或更多个,因为现代CPU能够更好地处理一组比较。 - meandmycode
它只是非常丑陋,因为C#没有一种在编译时生成这些常量的方法(而在运行时执行此操作比您尝试防止的操作更昂贵)。在像Lisp这样的语言中,您可以控制评估时间,快速和优雅的方式是相同的。 :-) - Ken
展开循环肯定比一堆 if/else 语句好。它既优雅又高效。 - Randolpho
3
如果将第一个“if”改为“while”,它也可以在64位上运行(但更加“丑陋”)。+1 - MartinStettner
@MartinStettner:没错,不是每个人都能写出真正丑陋的代码 :) - Mike Dunlavey

5
int myNumber = 8383;
char firstDigit = myNumber.ToString()[0];
// char = '8'

这不是和我发布的一模一样吗?只是用 [] 代替了 Substring。 - Dinah
不:它返回一个字符而不是字符串,[]查找应该更快。 - Joel Coehoorn
没错,但仍然是相同的基本方法——解析字符串表示。我认为@Dinah正在寻找一些...你知道....不同的东西。 - Randolpho
@Randolpho:没错。如果我表达不清楚,对不起。我更新了问题以澄清。 - Dinah
我也会返回 firstDigit-'0' 来获取整数。(在 C 中可以工作...我猜在 C# 中也可以) - Nicolas De Irisarri

5
我和 Lennaert 有同样的想法。
int start = number == 0 ? 0 : number / (int) Math.Pow(10,Math.Floor(Math.Log10(Math.Abs(number))));

这也适用于负数。

4
如果你认为Keltex的答案很丑陋,那么试试这个,它真的很丑陋,但更快。它使用展开的二分搜索算法来确定长度。
 ... leading code along the same lines
/* i<10000 */
if (i >= 100){
  if (i >= 1000){
    return i/1000;
  }
  else /* i<1000 */{
    return i/100;
  }
}
else /* i<100*/ {
  if (i >= 10){
    return i/10;
  }
  else /* i<10 */{
    return i;
  }
}

顺便说一句,MartinStettner也有同样的想法。


@Rasch:啊,这就是狂喜!它应该被隐藏起来,只在特殊场合下拿出来,并低声阅读...谁说编程没有灵魂? - Mike Dunlavey
那么 i=(...moreugly...)i>=100?i>=1000?i/1000:i/100:i>=10?i/10:i; 怎么样? - David Murdoch

3

我刚刚偶然发现了这个旧问题,并倾向于提出另一个建议,因为迄今为止没有一个答案能够返回所有可能输入值的正确结果,而且它仍然可以更快地实现:

public static int GetFirstDigit( int i )
{
    if( i < 0 && ( i = -i ) < 0 ) return 2;
    return ( i < 100 ) ? ( i < 1 ) ? 0 : ( i < 10 )
            ? i : i / 10 : ( i < 1000000 ) ? ( i < 10000 )
            ? ( i < 1000 ) ? i / 100 : i / 1000 : ( i < 100000 )
            ? i / 10000 : i / 100000 : ( i < 100000000 )
            ? ( i < 10000000 ) ? i / 1000000 : i / 10000000
            : ( i < 1000000000 ) ? i / 100000000 : i / 1000000000;
}

这适用于所有有符号整数值,包括最小的有符号整数-2147483648,它没有正数对应。 Math.Abs(-2147483648)会触发System.OverflowException,而--2147483648计算结果为-2147483648
该实现可以看作是迄今为止两种最快实现优点的结合。它使用二分查找并避免不必要的除法。一个快速的基准测试(100,000,000次迭代的循环索引)显示它比目前最快的实现快两倍。
它在2,829,581个滴答后完成。
为了比较,我还测量了当前最快实现的已校正变体,它花费了5,664,627个滴答。
public static int GetFirstDigitX( int i )
{
    if( i < 0 && ( i = -i ) < 0 ) return 2;
    if( i >= 100000000 ) i /= 100000000;
    if( i >= 10000 ) i /= 10000;
    if( i >= 100 ) i /= 100;
    if( i >= 10 ) i /= 10;
    return i;
}

在我的电脑上,对于这个测试,需要16,561,929个时钟周期才能得到同样的修正结果。

public static int GetFirstDigitY( int i )
{
    if( i < 0 && ( i = -i ) < 0 ) return 2;
    while( i >= 10 )
        i /= 10;
    return i;
}

像这样简单的函数可以很容易地被证明为正确,因为在当前硬件上迭代所有可能的整数值不需要花费太多时间,只需几秒钟即可。这意味着重要的是以非常易读的方式实现它们,因为以后根本没有必要修复其中的错误。


3
非常简单(而且可能相当快,因为它只涉及比较和一个除法):
if(i<10)
   firstdigit = i;
else if (i<100)
   firstdigit = i/10;
else if (i<1000)
   firstdigit = i/100;
else if (i<10000)
   firstdigit = i/1000;
else if (i<100000)
   firstdigit = i/10000;
else (etc... all the way up to 1000000000)

1
那接下来就是10k、100k、1mil、10mil、100mil等等? - Pawel Krakowiak
过早优化的经典例子 BTW - Patrick McDonald
@Patrick McDonald:如果你指的是我的原始问题——我根本没有尝试进行优化。只是想学习。我是一个自学的程序员,这意味着我错过了所有计算机科学专业所得到的严格数学教育。我想学习如何不依赖于字符串操作来完成所有事情。 - Dinah
这里有一个替代方案:步骤1)在stackoverflow.com上发布问题“X的第一个数字是什么?” 步骤2)等待30分钟并检查最佳答案。 - Keltex
@Keltex “步骤1…”:当然这就是我正在做的。不确定如何更清楚地表达。对于SO上的任何问题都可以这样说。但这并不意味着我没有先尝试自己解决它。 - Dinah
显示剩余19条评论

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