如何获取 int 中的位数?
有没有比此方法更简洁的方法来获取 int 中的位数?
int numDigits = String.valueOf(1000).length();
Is there a neater way for getting the number of digits in an int than this method?
int numDigits = String.valueOf(1000).length();
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(29)
您的基于字符串的解决方案完全没问题,没有什么“不整洁”的。你必须意识到,从数学上来说,数字没有长度,也没有数字。长度和数字都是特定基数(即字符串)中数字的物理表示的属性。
基于对数的解决方案在内部执行(某些)与基于字符串的解决方案相同的操作,并且可能(不明显)更快,因为它只生成长度并忽略数字。但我实际上不会认为它的意图更清晰——这是最重要的因素。
Your String-based solution is perfectly OK, there is nothing "un-neat" about it. You have to realize that mathematically, numbers don't have a length, nor do they have digits. Length and digits are both properties of a physical representation of a number in a specific base, i.e. a String.
A logarithm-based solution does (some of) the same things the String-based one does internally, and probably does so (insignificantly) faster because it only produces the length and ignores the digits. But I wouldn't actually consider it clearer in intent - and that's the most important factor.
对数是你的朋友:
注意:仅对 n > 有效。 0。
The logarithm is your friend:
NB: only valid for n > 0.
最快的方法:分而治之。
假设您的范围是 0 到 MAX_INT,那么您有 1 到 10 位数字。您可以使用分而治之的方法接近此间隔,每个输入最多进行 4 次比较。首先,通过一次比较将 [1..10] 分为 [1..5] 和 [6..10],然后使用一次比较将每个长度 5 的区间划分为一个长度 3 和一个长度 2 的区间。长度2区间需要再比较一次(共3次比较),长度3区间又可以分为长度1区间(解)和长度2区间。因此,您需要进行 3 或 4 次比较。
没有除法,没有浮点运算,没有昂贵的对数,只有整数比较。
代码(长但快):
基准测试(JVM 预热后)- 请参阅下面的代码以了解基准测试是如何运行的:
2145ms
一样快
一样快
与基线一样快的倍
完整代码:
再次,基准测试:
编辑
在编写基准测试之后,我从 Java 6 中偷偷了解了 Integer.toString,我发现它使用了:
我根据我的分而治之解决方案对它进行了基准测试:
我的解决方案大约是 Java 6 解决方案的 4 倍。
The fastest approach: divide and conquer.
Assuming your range is 0 to MAX_INT, then you have 1 to 10 digits. You can approach this interval using divide and conquer, with up to 4 comparisons per each input. First, you divide [1..10] into [1..5] and [6..10] with one comparison, and then each length 5 interval you divide using one comparison into one length 3 and one length 2 interval. The length 2 interval requires one more comparison (total 3 comparisons), the length 3 interval can be divided into length 1 interval (solution) and a length 2 interval. So, you need 3 or 4 comparisons.
No divisions, no floating point operations, no expensive logarithms, only integer comparisons.
Code (long but fast):
Benchmark (after JVM warm-up) - see code below to see how the benchmark was run:
2145ms
as fast as baseline
as fast as baseline
times as fast as baseline
Full code:
Again, benchmark:
Edit
After I wrote the benchmark, I took a sneak peak into Integer.toString from Java 6, and I found that it uses:
I benchmarked it against my divide-and-conquer solution:
Mine is about 4x as fast as the Java 6 solution.
对你的基准测试的两个评论:Java是一个复杂的环境,有即时编译和垃圾收集等等,所以为了得到公平的比较,每当我运行基准测试时,我总是:(a)附上两个测试在一个循环中按顺序运行它们 5 或 10 次。通常,第二次循环的运行时间与第一次的运行时间有很大不同。 (b) 在每次“方法”之后,我都会执行 System.gc() 来尝试触发垃圾收集。否则,第一种方法可能会生成一堆对象,但不足以强制进行垃圾收集,然后第二种方法会创建一些对象,堆耗尽,然后运行垃圾收集。然后第二个途径就被“收费”,用于捡起第一个途径留下的垃圾。非常不公平!
也就是说,上述两项在此示例中都没有产生显着差异。
不管有没有这些修改,我得到的结果与你的结果非常不同。当我运行这个时,是的,toString 方法的运行时间为 6400 到 6600 毫秒,而 log 方法 topok 的运行时间为 20,000 到 20,400 毫秒。对我来说,日志方法非但没有稍微快一点,反而慢了 3 倍。
请注意,这两种方法涉及非常不同的成本,因此这并不完全令人震惊:toString 方法将创建大量必须清理的临时对象,而 log 方法需要更密集的计算。所以也许区别在于,在内存较少的机器上,toString 需要更多的垃圾收集轮次,而在处理器较慢的机器上,额外计算 log 会更痛苦。
我还尝试了第三种方法。我写了这个小函数:
运行时间为 1600 到 1900 毫秒——不到我机器上 toString 方法的 1/3,以及 log 方法的 1/10。
如果您的数字范围很广,您可以通过除以 1,000 或 1,000,000 来进一步加快速度,以减少循环次数。我没玩过那个。
Two comments on your benchmark: Java is a complex environment, what with just-in-time compiling and garbage collection and so forth, so to get a fair comparison, whenever I run a benchmark, I always: (a) enclose the two tests in a loop that runs them in sequence 5 or 10 times. Quite often the runtime on the second pass through the loop is quite different from the first. And (b) After each "approach", I do a System.gc() to try to trigger a garbage collection. Otherwise, the first approach might generate a bunch of objects, but not quite enough to force a garbage collection, then the second approach creates a few objects, the heap is exhausted, and garbage collection runs. Then the second approach is "charged" for picking up the garbage left by the first approach. Very unfair!
That said, neither of the above made a significant difference in this example.
With or without those modifications, I got very different results than you did. When I ran this, yes, the toString approach gave run times of 6400 to 6600 millis, while the log approach topok 20,000 to 20,400 millis. Instead of being slightly faster, the log approach was 3 times slower for me.
Note that the two approaches involve very different costs, so this isn't totally shocking: The toString approach will create a lot of temporary objects that have to be cleaned up, while the log approach takes more intense computation. So maybe the difference is that on a machine with less memory, toString requires more garbage collection rounds, while on a machine with a slower processor, the extra computation of log would be more painful.
I also tried a third approach. I wrote this little function:
That ran in 1600 to 1900 millis -- less than 1/3 of the toString approach, and 1/10 the log approach on my machine.
If you had a broad range of numbers, you could speed it up further by starting out dividing by 1,000 or 1,000,000 to reduce the number of times through the loop. I haven't played with that.
还不能发表评论,所以我将作为单独的答案发布。
基于对数的解决方案不会计算非常大的长整数的正确位数,例如:
基于对数的解决方案计算大整数中的位数不正确
Can't leave a comment yet, so I'll post as a separate answer.
The logarithm-based solution doesn't calculate the correct number of digits for very big long integers, for example:
Logarithm-based solution calculates incorrect number of digits in large integers
使用 Java
在开头使用
import java.lang.Math.*;
使用 C
使用
inclue math.h
一开始Using Java
use
import java.lang.Math.*;
in the beginningUsing C
use
inclue math.h
in the beginning由于整数以 10 为基数的位数仅为 1 + truncate(log10(number)),因此您可以执行以下操作:
已编辑,因为我上次编辑修复了代码示例,但不是描述。
Since the number of digits in base 10 of an integer is just 1 + truncate(log10(number)), you can do:
Edited because my last edit fixed the code example, but not the description.
另一种字符串方法。简短而甜蜜——对于任何整数
n
。Another string approach. Short and sweet - for any integer
n
.Marian 的解决方案适用于长 类型的数字(最多 9,223,372,036,854,775,807),以防有人想要复制和粘贴它。
在程序中,我写这个是因为数字达到 10000 的可能性更大,所以我为它们创建了一个特定的分支。无论如何,这不会产生重大影响。
Marian's solution adapted for long type numbers (up to 9,223,372,036,854,775,807), in case someone want's to Copy&Paste it.
In the program I wrote this for numbers up to 10000 were much more probable, so I made a specific branch for them. Anyway it won't make a significative difference.
普通的旧数学怎么样?除以 10,直到达到 0。
How about plain old Mathematics? Divide by 10 until you reach 0.
我看到人们使用 String 库甚至使用 Integer 类。这没有什么问题,但是获取位数的算法并不复杂。我在这个例子中使用了 long,但它与 int 的效果一样好。
I see people using String libraries or even using the Integer class. Nothing wrong with that but the algorithm for getting the number of digits is not that complicated. I am using a long in this example but it works just as fine with an int.
我可以尝试吗? ;)
基于德克的解决方案
Can I try? ;)
based on Dirk's solution
玛丽安的解决方案,现在有了三元:
因为我们可以。
Marian's Solution, now with Ternary:
Because we can.
没有 String API,没有 utils,没有类型转换,只有纯 java 迭代 ->
如果您愿意,您可以做多更大的价值。
no String API, no utils, no type conversion, just pure java iteration ->
You can go long for bigger values if you please.
好奇,我尝试对其进行基准测试...
结果是:
现在我想知道我的基准测试是否确实意味着什么,但我确实在多次运行基准测试本身时得到了一致的结果(毫秒内的变化)...:)看起来尝试优化这个是没有用的...
编辑:根据 ptomli 的评论,我在上面的代码中用“i”替换了“number”,并在 5 次运行中得到了以下结果:
Curious, I tried to benchmark it ...
the results are :
Now I am left to wonder if my benchmark actually means something but I do get consistent results (variations within a ms) over multiple runs of the benchmark itself ... :) It looks like it's useless to try and optimize this...
edit: following ptomli's comment, I replaced 'number' by 'i' in the code above and got the following results over 5 runs of the bench :
通过设计(基于问题)。这是分而治之的替代方法。我们首先定义一个枚举(考虑到它仅适用于无符号整数)。
现在我们将定义一个类来遍历枚举的值并进行比较并返回适当的长度。
该解决方案的运行时间与分治法相同。
With design (based on problem). This is an alternate of divide-and-conquer. We'll first define an enum (considering it's only for an unsigned int).
Now we'll define a class that goes through the values of the enum and compare and return the appropriate length.
The run time of this solution is the same as the divide-and-conquer approach.
这个递归方法怎么样?
What about this recursive method?
简单的解决方案:
simple solution:
一个非常简单的解决方案:
A really simple solution:
或者,您可以检查长度是否大于或小于所需的数字。
}
Or instead the length you can check if the number is larger or smaller then the desired number.
}
我还没有看到基于乘法的解决方案。基于对数、除法和字符串的解决方案对于数百万个测试用例来说将变得相当笨拙,因此这里有一个针对整数的解决方案:
在基数 10 中,这是有效的,因为 n 本质上是与 9、99、999 进行比较... 因为 min 是 9, 90, 900... 并且 n 被 9, 90, 900 减去...
不幸的是,仅通过替换 < 的每个实例,这不能移植到
long
code>int 由于溢出。另一方面,它恰好适用于底座 2 和 10(但对于大多数其他底座来说严重失败)。您需要一个溢出点查找表(或除法测试......呃)I haven't seen a multiplication-based solution yet. Logarithm, divison, and string-based solutions will become rather unwieldy against millions of test cases, so here's one for
ints
:In base 10, this works because n is essentially being compared to 9, 99, 999... as min is 9, 90, 900... and n is being subtracted by 9, 90, 900...
Unfortunately, this is not portable to
long
just by replacing every instance ofint
due to overflow. On the other hand, it just so happens it will work for bases 2 and 10 (but badly fails for most of the other bases). You'll need a lookup table for the overflow points (or a division test... ew)人们想要这样做主要是因为他/她想要“呈现”它,这主要意味着它最终需要显式或隐式地“toString-ed”(或以另一种方式转换);在呈现之前(例如打印)。
如果是这种情况,那么只需尝试明确必要的“toString”并计算位即可。
One wants to do this mostly because he/she wants to "present" it, which mostly mean it finally needs to be "toString-ed" (or transformed in another way) explicitly or implicitly anyway; before it can be presented (printed for example).
If that is the case then just try to make the necessary "toString" explicit and count the bits.
我们可以使用递归循环来实现这一点
We can achieve this using a recursive loop
我在查看 Integer.java 源代码后编写了这个函数。
I wrote this function after looking
Integer.java
source code.计算
int
变量中位数的有效方法之一是定义一个带有所需数量的条件语句的方法 digitsCounter。方法很简单,我们将检查可以包含
n
位数字的每个范围:0 : 9 是
单
位数字10 : 99 是
双
数字100 : 999 是
三位数
等等...更简洁的方法是删除对下限的检查,因为如果我们按顺序进行。
One of the efficient ways to count the number of digits in an
int
variable would be to define a method digitsCounter with a required number of conditional statements.The approach is simple, we will be checking for each range in which a
n
digit number can lie:0 : 9 are
Single
digit numbers10 : 99 are
Double
digit numbers100 : 999 are
Triple
digit numbers and so on...A cleaner way to do this is to remove the check for the lower limits as it won't be required if we proceed in a sequential manner.
理想情况下,只要整数不为零,整数除以 10 多次就会返回位数。因此可以创建一个简单的方法,如下所示。
Ideally, an integer divided by 10 multiple times will return the number of digits as long as the integer is not zero. As such a simple method to do so can be created as below.
这取决于你所说的“整洁”是什么意思。我认为下面的代码相当简洁,而且运行速度很快。
它基于 玛丽安 答案,扩展为可处理所有
long
值并使用? 进行渲染。 :
运算符。It depends on what you mean by "neat". I think the following code is fairly neat, and it runs fast.
It is based on Marian's answer, extended to work with all
long
values and rendered using the? :
operator.以下是 JDK 开发人员对此类解决方案的看法。这是 JDK 17(类
Long
):请注意,如果需要,该方法会考虑减号。
不幸的是该方法没有公开。
就性能而言,您可以从评论中看到,与替代方案相比,JDK 开发人员至少对此进行了一些思考。我猜想
偏向于较低数字的分而治之方法的效果会稍差
更好,因为 CPU 可以比整数更快地进行整数比较
乘法。但差异可能很小,无法测量。
无论如何,我希望这个方法已经在 JDK 中公开,这样人们就不会开始滚动自己的方法。
Here is what such solution looks from the JDK developers. This is JDK 17 (class
Long
):Note that the method takes into account a minus sign, if necessary.
Unfortunately the method is not exposed.
In terms of performance you can see from the comments that the JDK developer has at least given this some thought compared to alternatives. I would guess that
a divide-and-conquer method skewed toward lower numbers would perform slightly
better, because the CPU can do integer comparisons a bit faster than integer
multiplications. But the difference may so small that it is not measurable.
In any case, I wish this method had been exposed in the JDK so that people would not start rolling their own method.
这是我制作的一个非常简单的方法,适用于任何数字:
它的工作方式是使用数字计数器变量,即 10 = 1 位数字空间。例如 .1 = 1 十分之一 => 1 位数字空格。因此,如果您有
int number = 103342;
,您将得到 6,因为这相当于后面的 .000001 个空格。另外,有没有人有更好的numberCounter
变量名称?我想不出更好的办法了。编辑:只是想到了更好的解释。本质上,这个 while 循环所做的就是将你的数字除以 10,直到它小于 1。本质上,当你将某个数字除以 10 时,你会将它移回一个数字空间,因此你只需将它除以 10,直到数字中的位数达到 <1 为止。
这是另一个可以计算小数位数的版本:
Here's a really simple method I made that works for any number:
The way it works is with the number counter variable is that 10 = 1 digit space. For example .1 = 1 tenth => 1 digit space. Therefore if you have
int number = 103342;
you'll get 6, because that's the equivalent of .000001 spaces back. Also, does anyone have a better variable name fornumberCounter
? I can't think of anything better.Edit: Just thought of a better explanation. Essentially what this while loop is doing is making it so you divide your number by 10, until it's less than one. Essentially, when you divide something by 10 you're moving it back one number space, so you simply divide it by 10 until you reach <1 for the amount of digits in your number.
Here's another version that can count the amount of numbers in a decimal: