补充10进制整数公式的证明在哪里?

huangapple go评论76阅读模式
英文:

Where is the proof for complement of base 10 integer formula?

问题

所以,对于 https://leetcode.com/problems/complement-of-base-10-integer/discuss/879910/Java-100-faster-solution-trick-to-find-2(digits-of-N)-1-greater-(11111)-and-subtract-N-from-it,是否有人能够提供为什么 2^(二进制位数的长度) - 1 给出了一个充满 111....1111 的完整二进制数字的证明呢?如果有人能够更整洁地重新表述这个问题,请进行修改。

英文:

So,for https://leetcode.com/problems/complement-of-base-10-integer/discuss/879910/Java-100-faster-solution-trick-to-find-2(digits-of-N)-1-greater-(11111)-and-subtract-N-from-it can anyone provide the proof to why 2^(length of binary digits) - 1 gives the full 111....1111 binary number full of ones. If anyone can rewrite this question neater please do.

答案1

得分: 1

> 有人能够证明为什么 (2^{二进制位数}-1) 给出一个全为1的二进制数字?

在(无符号)二进制表示法中,由 N 个“1”数字组成的数字表示为

\(2^0 + 2^1 + 2^2 + .... + 2^{N-1}\)

其中 (N \geq 1)。

因此,我们实际上需要证明命题 P(N)

\(2^N - 1 = 2^0 + 2^1 + 2^2 + .... + 2^{N-1}\),其中 \(N \geq 1\)。

归纳证明

  1. 基本情况,(N = 1)。

     \(2^N - 1 = 2^1 - 1\)
             = 2 - 1
             = 1
             = 2^0
    
     因此,`P(1)` 已被证明。
    
  2. 假设 P(N) 成立,我们需要证明 P(N+1) 也成立;即证明 P(N) => P(N+1),其中 (N \geq 1)。

    假设 P(N) 成立,即 (2^N - 1 = 2^0 + 2^1 + 2^2 + .... + 2^{N-1})。

     \((2^N - 1) \cdot 2 = 2 \cdot (2^0 + 2^1 + 2^2 + .... + 2^{N-1})\)
                       = 2 \cdot 2^0 + 2 \cdot 2^1 + 2 \cdot 2^2 + 2 \cdot 2^{N-1}
                       = 2^1 + 2^2 + 2^3 + ... + 2^N
    
     \((2^N - 1) \cdot 2 + 1 = 1 + (2^N - 1) \cdot 2\)
                       = 2^0 + (2^N - 1) \cdot 2
                       = 2^0 + 2^1 + 2^2 + 2^3 + ... + 2^N
    

    但是

     \((2^N - 1) \cdot 2 + 1 = 2 \cdot 2^N - 2 \cdot 1 + 1
                       = 2^{N+1} - 2 + 1
                       = 2^{N+1} - 1
    

    因此

     \(2^{N+1} - 1 = 2^0 + 2^1 + 2^2 + 2^3 + ... + 2^N\)
    

    因此,P(N) => P(N+1),其中 (N \geq 1),已被证明。

  3. 根据步骤 1,P(1) 已被证明。

    根据步骤 2,P(N) => P(N+1),其中 (N \geq 1),已被证明。

    因此,通过归纳法,对于所有 (N \geq 1),P(N) 已被证明。

证毕。

英文:

> Can anyone provide the proof to why 2^(length of binary digits) - 1 gives the full 111....1111 binary number full of ones.

In (unsigned) binary notation, the number consisting of N "one" digits means

2^0 + 2^1 + 2^2 + .... + 2^(N-1)

where N >= 1.

So what we actually need to prove is Proposition P(N)

2^N - 1 = 2^0 + 2^1 + 2^2 + .... + 2^(N-1) where N >= 1.

Proof by induction

  1. Base case, N = 1.

    2^N - 1 = 2^1 - 1
            = 2 - 1
            = 1
            = 2^0
    
    Thus `P(1)` is proven
    
  2. Given that P(N) is true, we need to show that P(N+1) is also true; i.e. prove that P(N) => P(N+1) where N >= 1

    Assume P(N) is true; i.e 2^N - 1 = 2^0 + 2^1 + 2^2 + .... + 2^(N-1)

    (2^N - 1) * 2     = 2 * (2^0 + 2^1 + 2^2 + .... + 2^(N-1))
                      = 2 * 2^0 + 2 * 2^1 + 2 * 2^2 + 2 * 2^(N-1)
                      = 2^1 + 2^2 + 2^3 + ... + 2^N
    
    (2^N - 1) * 2 + 1 = 1   + (2^N - 1) * 2
                      = 2^0 + (2^N - 1) * 2
                      = 2^0 + 2^1 + 2^2 + 2^3 + ... + 2^N
    

    but

    (2^N - 1) * 2 + 1 = 2 * 2^N - 2 * 1 + 1
                      = 2^(N+1) - 2 + 1
                      = 2^(N+1) - 1
    

    and therefore

    2^(N+1) - 1       = 2^0 + 2^1 + 2^2 + 2^3 + ... + 2^N
    

    Thus P(N) => P(N+1) where N >= 1 is proven

  3. From step 1, P(1) is proven

    From step 2, P(N) => P(N+1) where N >= 1 is proven

    Therefore, by induction, P(N) for all N >= 1 is proven.

QED.

答案2

得分: 0

链接的代码与高效无缘。相对于基本位操作而言,Math.pow代价昂贵得令人难以置信,用循环来找出N的位边界也是不必要的低效操作。

为什么2^5-1在二进制中总是一个全1位序列?

因为2^x字面上定义了二进制数制。如果对你更方便,让我们讨论十进制:

10^1是多少?是10。10^2呢?是100。10^3是1000,以此类推。从其中减去1,你分别得到999999:最高位数字(十进制中是9,但在二进制中是1)。所以,当x是一个基数(十进制是“基数10”,二进制是“基数2”),y是任意正整数时,x^y-1在该基数中输出的数字的最高位重复了y次。在二进制中,2^18-1是18个1。出于相同的原因,10^18-1给出了18个9

如何更高效地计算Math.pow(2,n)?

同样的解释:因为2^x字面上描述了二进制数学,而计算机在这方面非常擅长,位操作就是二进制数学。全都是在二进制中进行的:

1是,嗯,十进制1。
10是十进制2。
101是十进制5。
1010是十进制10。

总的原则是,如果你在末尾加上一个0,那就相当于将数字乘以2。回到十进制,如果我给你任何一个数字,比如12493821345,在纸上写下,然后让你把它乘以10(记住,十进制=基数10,二进制=基数2),你只需在末尾加一个零。对于二进制也是一样的:在末尾加一个0,相当于乘以2。

因此,代替使用Math.pow(2, n),你可以简单地在数字后加上n个零。这可以通过位移运算符来实现:“加5个零”等同于“将位向左移动5个位置”。除了位移操作__效率更高__。

因此,如果我想要计算2^10-1,高效的方法是:

int n = 10;
int y = (1 << n) - 1;
// y 现在是 1023,确实是 2^10 - 1。

因此,应该是 int pow = (1 << n) - 1;

如何从N=5迅速得到111

你这个问题的实际含义是:在输入数字中,最高位的1位在哪个位置。

5的二进制是101,所需的数字是2(因为在计算机中计数是从0开始的,第2位是第3个位)。换句话说,你的问题是:最高的1位是哪个。Java有一个方法可以做到这一点:Integer.highestOneBit(5)。这个方法不返回最高1位的位置,而是返回只有该位为1的数字。换句话说,5是101,7是111。对于这两个数字,highestOneBit返回100(4)。将其左移1位并减去1,你就可以将5和7都转换为7,这正是你想要的。

注意,这样可以执行的操作要少得多。还要注意,这些函数经过了__高度__调优。Java性能工程师在这些函数上投入了很大的精力,以找到在大多数处理器上运行最快的实现,考虑到预测性流水线和CPU缓存的不确定性。他们往往在这方面做得很好,所以你应该使用他们的东西。Java是开源的,你可以查看实现代码。

你还能进一步优化吗?

当然可以。

我真正需要做的是首先翻转所有位,然后“反翻转”前导零位。给定一个字节中的5,比如0000 0101,我们想要翻转所有位,但不翻转前导零位:0000 0010 = 2,这就是正确的答案。Java有一个“翻转所有位”的运算符(NOT运算符,~),所以我们只需要“翻转所有位” + “将所有那些前导位清零”。

这就得到了:

public static int complement(int n) {
    if (n == 0) return 1; // 显然是一个特殊情况;我会认为它是0。
    return ~n & ((Integer.highestOneBit(n) << 1) - 1);
}

没有循环(highestOneBit的实现也没有循环),没有昂贵的数学操作,只有基本的位操作。除了你的特殊情况,这是一行代码。

让我们来测试一下!

complement(5) --> 101 --> 010 --> 2
complement(7) --> 111 --> 000 --> 0
complement(8) --> 1000 --> 0111 --> 7

看起来效果很好。

英文:

The linked-to code is far from efficient. Math.pow is (relative to basic bit manipulation) incredulously expensive, and a loop to figure out the bit bound of N is also unneccessarily inefficient.

Why is 2^5-1 always, in bits, a sequence of all-1 bits?

Because 2^x is the literal definition of the binary number system. If it's more convenient for you, let's talk decimal:

What's 10^1? It's 10. What's 10^2? It's 100. 10^3 is 1000, etcetera. Subtract 1 from any of these and you get, respectively, 9, 99, and 999: The highest digit (which in decimal is 9, but in binary it is 1). So, x^y-1 where x is a base (decimal is 'base 10', binary is 'base 2') and y is any positive integer gives you the highest digit in that base, repeated y times, if you print that number in that base. 2^18-1 in binary is 18x 1. For the same reason 10^18-1 gives you 18x 9.

How can we do Math.pow(2,n) more efficiently

Same explanation: Because 2^x is literally describing binary math and computers are really good at those, and bitwise ops are binary math. All in binary:

1 is, well, decimal 1.
10 is decimal 2.
101 is decimal 5.
1010 is decimal 10.

The general principle is, if you just shove a 0 on the end, it's the same as multiplying the number by 2. Going back to decimal, if I give you any number, let's say 12493821345, written on a piece of paper, and I Ask you to write that number multiplied by 10 (remember, decimal = base 10, binary = base 2), you just.. add a zero on the end. Same for binary: Adding a 0 at the end is the same as multiplying by 2.

So, instead of Math.pow(2, n), you can just.. add n zeroes. Which you do with the bit shift operator: "add 5 zeroes" is the same as "shift the bits 5 positions to the left". Except bit shift is vastly more efficient.

Thus, if I want 2^10-1, the efficient way to do that is:

int n = 10;
int y = (1 &lt;&lt; n) -1;
// y is now 1023, which is indeed 2^10 -1.

thus, that should have been int pow = (1 &lt;&lt; n) - 1;.

How do you get from N=5 to 111 quickly?

What you're really getting at with this question is: What is the position of the highest 1 bit in your input number.

5 in binary is 101 and the desired number is 2 (because counting in computers is 0 based, position 2 is where you find the 3rd bit) as in: We need 3 1 bits. In other words, your question is: What's the highest 1 bit. Java has a method for that: Integer.highestOneBit(5). This doesn't return the position of the highest 1 bit, but that number where only that bit is set to 1. In other words, 5 is 101, and 7 is 111. For both of these, highestOneBit returns 100 (4). shift that left by 1 and subtract 1 and you turn both 5 and 7 into 7, which is what you want.

Note how this will perform far fewer operations. Note also that these functions are HIGHLY tuned. Quite literally java performance engineers delved real deep on these to find the implementation that runs fastest on most processors taking into account the vagaries of predictive pipelining and CPU caches. They tend to get this sort of thing right, so, you should use their stuff. Java is open source, you can check out the implementation if you want.

Can you optimize even more?

But of course we can.

All I really need to do is to first just flip all the bits, but then 'unflip' the leading zeroes. Given 5 in, say, a byte: 0000 0101, we want to flip all bits, but not the leading zeroes: 0000 0010 = 2 which is the right answer. Java has a 'flip all bits' operator (the NOT operator, ~), so all we need is 'flip all bits' + 'zero out all those leading bits'.

That gets us to:

public static int complement(int n) {
if (n == 0) return 1; // apparently an edge case; I&#39;d say it&#39;s 0.
return ~n &amp; ((Integer.highestOneBit(n) &lt;&lt; 1) - 1);
}

No loops. (the impl of hOB also has no loops), no pricey Math operations. All basic bit twiddling. Other than your edge case, a one-liner.

Let's test it!

complement(5) --&gt; 101 -&gt; 010 -&gt; 2
complement(7) --&gt; 111 -&gt; 000 -&gt; 0
complement(8) --&gt; 1000 -&gt; 0111 -&gt; 7

seems to be working great.

huangapple
  • 本文由 发表于 2020年10月11日 11:15:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/64300290.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定