Builds on Bit Tricks, which you haven’t finished yet.
Add Without + or - (Sum of Two Integers)
You get two integers, a and b. Return their sum. The code may not use the + operator or the - operator anywhere.
The only tools left are the bitwise operators and shifts, which work on the binary digits of a number.
1 is 01 and 2 is 10. No place has a 1 in both numbers, so there is no carry, and the sum without carries is 11, which is 3.Carry Add
a, b ➔ total = a ^ b, carry = (a & b) << 1, repeat until carry is 0 ➔ signed 32-bit result| 1 | def add_without_plus(a: int, b: int) -> int: |
| 2 | """Sum of Two Integers: add without + or -; XOR adds without carries, AND shifted left finds the carries. |
| 3 | Time: O(1), at most 32 rounds | Space: O(1) |
| 4 | """ |
| 5 | # mask: the low 32 bits; sign_max: the largest signed 32-bit value |
| 6 | mask, sign_max = 0xFFFFFFFF, 0x7FFFFFFF |
| 7 | a, b = a & mask, b & mask |
| 8 | |
| 9 | # 1. Add Without Carries, Then Move The Carries: |
| 10 | # a ^ b is the sum of every bit place without carries. (a & b) << 1 holds the carries, each one place to the left. |
| 11 | # Repeat with the carries as the new b. Each round moves the carries one place left, and the mask drops them after bit 31. |
| 12 | while b: |
| 13 | total = (a ^ b) & mask |
| 14 | carry = ((a & b) << 1) & mask |
| 15 | a, b = total, carry |
| 16 | |
| 17 | # 2. Convert Back To A Signed Value: |
| 18 | # A pattern above sign_max has bit 31 set, which means a negative number in 32-bit two's complement. |
| 19 | return a if a <= sign_max else ~(a ^ mask) |
a + b equals the original sum, modulo 2 ** 32, after every round.a ^ b is the sum of every bit place without carries, and (a & b) << 1 holds exactly the carries, each one place to the left, so set a to total, set b to carry, and repeat until b is 0. A place makes a carry only when both numbers have a 1 there, which is what a & b marks, and the carry is added to the next place, which is the << 1. Each round moves every carry at least one place left, so the carries leave the 32-bit word after at most 32 rounds. In Python, cut a, b, total and carry to 32 bits with mask, and end with a if a <= sign_max else ~(a ^ mask): a negative Python integer has no fixed width, so for -1 + 1 without the mask the loop never ends, and for -5 + -3 returning a gives 4294967288 where -8 is right.
See it run
Sum of Two Integers, step by step
round 0a 23b 41
Rows from the top: a, b, total (a ^ b) and carry ((a & b) << 1). Each column is one bit position, with the highest position on the left. The next round uses total as a and carry as b.
both 1a bit place where a and b both have a 1, so it makes a carrycarrya carry, already moved one place to the left
Start: a = 23 and b = 41. The loop repeats while b is not 0.
Gotcha 1 of 4
- The trap
Gotcha 1: Using no 32-bit mask in Python
Mistakea, b = a ^ b, (a & b) << 1Fixtotal = (a ^ b) & mask carry = ((a & b) << 1) & mask a, b = total, carryWhat happens: A Python integer has no fixed width, so a negative number behaves as if infinitely many 1 bits stood to its left. For
-1 + 1, every round finds a 1 in both numbers one place higher than in the round before, so the carry moves left forever andbnever becomes 0.maskkeeps only the low 32 bits, so the carry leaves the word after at most 32 rounds.Example: Sum of Two Integers (LC 371): a = -1, b = 1Mistake: the loop never endsFix: returns 0
- Equal values must cancel, as in Single Number (LC 136), so XOR alone is enough and there is no carry to move. See XOR Cancel
- The numbers are digit arrays or strings, handled one decimal digit at a time, as in Multiply Strings (LC 43), not whole integers combined with bit operators. See Digit Manipulation
Deep Dive
Why XOR and AND add two numbers
Bit Tricks already taught shifts and masks. This card adds one idea: an addition splits into a sum without carries and a separate carry.
Look at one bit place. If a and b have different bits there, the sum bit is 1 and nothing carries. If both bits are 0, the sum bit is 0. If both bits are 1, the sum bit is 0 and the place sends a carry to the next place.
So a ^ b is the sum of every place without carries. a & b marks the places that send a carry. The carry belongs to the next place on the left, so the carries are (a & b) << 1.
The true sum is (a ^ b) + ((a & b) << 1). The loop repeats this with a = total and b = carry. Each round has the same sum as before, and the carry moves further left.
Example with a = 23 and b = 41, in 8 bits: 00010111 and 00101001. Round 1 gives total = 62 and carry = 2. The carries in rounds 1 to 6 are 2, 4, 8, 16, 32 and 64. After 7 rounds b is 0, and a is 64. That is 23 + 41.
Why the loop ends, and why Python needs the mask
Each round moves every carry at least one place to the left. In a 32-bit word, a carry that moves past bit 31 is cut off by & mask. So after at most 32 rounds, b is 0.
A Python integer has no fixed width. A negative number acts as if it had infinitely many 1 bits to the left. For -1 + 1, a 1 meets a 1 one place higher in every round. The carry never leaves, and the loop without a mask does not end.
mask = 0xFFFFFFFF keeps the low 32 bits. With the mask, -1 + 1 ends after 32 rounds with a = 0. I ran this and the result is 0.
Reading the 32-bit result as a signed number
After the loop, a is a pattern of 32 bits. Python reads it as a number from 0 to 4294967295. Bit 31 counts as the value 2147483648, not as a sign.
A negative sum has bit 31 set. For -5 + -3, the loop ends with a = 4294967288. The final line a if a <= sign_max else ~(a ^ mask) fixes this. a ^ mask flips all 32 bits, and ~ turns that into the negative number with the same 32 bits. The result is -8.
The cost is at most 32 rounds, and each round does a constant amount of work. The time is O(1) for a fixed width, and the space is O(1).
If the true sum does not fit in 32 signed bits, the result wraps around. For 2147483647 + 1, the function returns -2147483648, as a 32-bit machine would.
60-Second Pitch
"I split the addition into two parts. a ^ b is the sum of every bit place without carries. (a & b) << 1 is the carries, each one place to the left. I set a to the first part and b to the second, and I repeat until b is 0. Each round moves the carries at least one place left, so there are at most 32 rounds. In Python I cut every value to 32 bits with mask, because a negative integer has no fixed width. At the end, if a is above sign_max, I convert it with ~(a ^ mask). That is O(1) time and O(1) space."
Common Questions
- What is the time and space complexity?
O(1)time andO(1)space for 32-bit integers. The loop runs at most 32 rounds, and each round does a constant amount of work.- Why does the loop need a mask in Python?
A Python integer has no fixed width, so a negative number acts as if it had infinitely many 1 bits on its left. The carry then never leaves.
maskkeeps the low 32 bits, so the carry leaves after at most 32 rounds.- How do you get the signed result at the end?
If
ais abovesign_max = 0x7FFFFFFF, bit 31 is set. Return~(a ^ mask).a ^ maskflips all 32 bits, and~makes the negative number with the same 32 bits. For-5 + -3, this gives-8.- Why is one round not enough?
A carry can create a new carry. For
2 + 3, round 1 givestotal = 1andcarry = 4. These two values still have to be combined, so the loop continues untilbis0.
SWE Reasoning
Spot it
- The statement forbids the
+and-operators. - The statement asks you to combine two integers with only bit operators.
- A process repeats until a carry is gone, as in adding two binary strings by hand. Compare Add Binary (LC 67), where the inputs are strings.
- Negative integers are allowed, and the language may have no fixed integer width.
The invariant, step by step
- Start with
aandbcut to 32 bits bymask. The suma + bis the original sum, modulo2 ** 32. - In a round,
total = a ^ bis the sum of every place without carries. carry = (a & b) << 1holds the carries. Each carry sits one place to the left of the place that made it.- The new pair
(total, carry)has the same sum modulo2 ** 32as the old pair(a, b). - Each round moves every carry at least one place left. After at most 32 rounds, every carry has left the word, and
b = 0. - Then
ais the sum as a 32-bit pattern. The last line converts it to a signed value.
Traps
- No 32-bit mask in Python. A negative number never loses its infinite 1 bits. For
-1 + 1the loop does not end. - No
<< 1on the carry. The carry is added back to the same place that made it. For2 + 3, the function returns3instead of5. return awithout the sign conversion. For-5 + -3, the function returns4294967288instead of-8.if b:instead ofwhile b:. One round is not enough, because a carry can make another carry. For2 + 3, one round leavestotal = 1andcarry = 4, and the function returns1.
Practice
Builds on Bit Tricks, which you haven’t finished yet.
Add Without + or - (Sum of Two Integers)
You get two integers, a and b. Return their sum. The code may not use the + operator or the - operator anywhere.
The only tools left are the bitwise operators and shifts, which work on the binary digits of a number.
1 is 01 and 2 is 10. No place has a 1 in both numbers, so there is no carry, and the sum without carries is 11, which is 3.Carry Add
a, b ➔ total = a ^ b, carry = (a & b) << 1, repeat until carry is 0 ➔ signed 32-bit result| 1 | def add_without_plus(a: int, b: int) -> int: |
| 2 | """Sum of Two Integers: add without + or -; XOR adds without carries, AND shifted left finds the carries. |
| 3 | Time: O(1), at most 32 rounds | Space: O(1) |
| 4 | """ |
| 5 | # mask: the low 32 bits; sign_max: the largest signed 32-bit value |
| 6 | mask, sign_max = 0xFFFFFFFF, 0x7FFFFFFF |
| 7 | a, b = a & mask, b & mask |
| 8 | |
| 9 | # 1. Add Without Carries, Then Move The Carries: |
| 10 | # a ^ b is the sum of every bit place without carries. (a & b) << 1 holds the carries, each one place to the left. |
| 11 | # Repeat with the carries as the new b. Each round moves the carries one place left, and the mask drops them after bit 31. |
| 12 | while b: |
| 13 | total = (a ^ b) & mask |
| 14 | carry = ((a & b) << 1) & mask |
| 15 | a, b = total, carry |
| 16 | |
| 17 | # 2. Convert Back To A Signed Value: |
| 18 | # A pattern above sign_max has bit 31 set, which means a negative number in 32-bit two's complement. |
| 19 | return a if a <= sign_max else ~(a ^ mask) |
a + b equals the original sum, modulo 2 ** 32, after every round.a ^ b is the sum of every bit place without carries, and (a & b) << 1 holds exactly the carries, each one place to the left, so set a to total, set b to carry, and repeat until b is 0. A place makes a carry only when both numbers have a 1 there, which is what a & b marks, and the carry is added to the next place, which is the << 1. Each round moves every carry at least one place left, so the carries leave the 32-bit word after at most 32 rounds. In Python, cut a, b, total and carry to 32 bits with mask, and end with a if a <= sign_max else ~(a ^ mask): a negative Python integer has no fixed width, so for -1 + 1 without the mask the loop never ends, and for -5 + -3 returning a gives 4294967288 where -8 is right.
See it run
Sum of Two Integers, step by step
round 0a 23b 41
Rows from the top: a, b, total (a ^ b) and carry ((a & b) << 1). Each column is one bit position, with the highest position on the left. The next round uses total as a and carry as b.
both 1a bit place where a and b both have a 1, so it makes a carrycarrya carry, already moved one place to the left
Start: a = 23 and b = 41. The loop repeats while b is not 0.
Gotcha 1 of 4
- The trap
Gotcha 1: Using no 32-bit mask in Python
Mistakea, b = a ^ b, (a & b) << 1Fixtotal = (a ^ b) & mask carry = ((a & b) << 1) & mask a, b = total, carryWhat happens: A Python integer has no fixed width, so a negative number behaves as if infinitely many 1 bits stood to its left. For
-1 + 1, every round finds a 1 in both numbers one place higher than in the round before, so the carry moves left forever andbnever becomes 0.maskkeeps only the low 32 bits, so the carry leaves the word after at most 32 rounds.Example: Sum of Two Integers (LC 371): a = -1, b = 1Mistake: the loop never endsFix: returns 0
- Equal values must cancel, as in Single Number (LC 136), so XOR alone is enough and there is no carry to move. See XOR Cancel
- The numbers are digit arrays or strings, handled one decimal digit at a time, as in Multiply Strings (LC 43), not whole integers combined with bit operators. See Digit Manipulation
Deep Dive
Why XOR and AND add two numbers
Bit Tricks already taught shifts and masks. This card adds one idea: an addition splits into a sum without carries and a separate carry.
Look at one bit place. If a and b have different bits there, the sum bit is 1 and nothing carries. If both bits are 0, the sum bit is 0. If both bits are 1, the sum bit is 0 and the place sends a carry to the next place.
So a ^ b is the sum of every place without carries. a & b marks the places that send a carry. The carry belongs to the next place on the left, so the carries are (a & b) << 1.
The true sum is (a ^ b) + ((a & b) << 1). The loop repeats this with a = total and b = carry. Each round has the same sum as before, and the carry moves further left.
Example with a = 23 and b = 41, in 8 bits: 00010111 and 00101001. Round 1 gives total = 62 and carry = 2. The carries in rounds 1 to 6 are 2, 4, 8, 16, 32 and 64. After 7 rounds b is 0, and a is 64. That is 23 + 41.
Why the loop ends, and why Python needs the mask
Each round moves every carry at least one place to the left. In a 32-bit word, a carry that moves past bit 31 is cut off by & mask. So after at most 32 rounds, b is 0.
A Python integer has no fixed width. A negative number acts as if it had infinitely many 1 bits to the left. For -1 + 1, a 1 meets a 1 one place higher in every round. The carry never leaves, and the loop without a mask does not end.
mask = 0xFFFFFFFF keeps the low 32 bits. With the mask, -1 + 1 ends after 32 rounds with a = 0. I ran this and the result is 0.
Reading the 32-bit result as a signed number
After the loop, a is a pattern of 32 bits. Python reads it as a number from 0 to 4294967295. Bit 31 counts as the value 2147483648, not as a sign.
A negative sum has bit 31 set. For -5 + -3, the loop ends with a = 4294967288. The final line a if a <= sign_max else ~(a ^ mask) fixes this. a ^ mask flips all 32 bits, and ~ turns that into the negative number with the same 32 bits. The result is -8.
The cost is at most 32 rounds, and each round does a constant amount of work. The time is O(1) for a fixed width, and the space is O(1).
If the true sum does not fit in 32 signed bits, the result wraps around. For 2147483647 + 1, the function returns -2147483648, as a 32-bit machine would.
60-Second Pitch
"I split the addition into two parts. a ^ b is the sum of every bit place without carries. (a & b) << 1 is the carries, each one place to the left. I set a to the first part and b to the second, and I repeat until b is 0. Each round moves the carries at least one place left, so there are at most 32 rounds. In Python I cut every value to 32 bits with mask, because a negative integer has no fixed width. At the end, if a is above sign_max, I convert it with ~(a ^ mask). That is O(1) time and O(1) space."
Common Questions
- What is the time and space complexity?
O(1)time andO(1)space for 32-bit integers. The loop runs at most 32 rounds, and each round does a constant amount of work.- Why does the loop need a mask in Python?
A Python integer has no fixed width, so a negative number acts as if it had infinitely many 1 bits on its left. The carry then never leaves.
maskkeeps the low 32 bits, so the carry leaves after at most 32 rounds.- How do you get the signed result at the end?
If
ais abovesign_max = 0x7FFFFFFF, bit 31 is set. Return~(a ^ mask).a ^ maskflips all 32 bits, and~makes the negative number with the same 32 bits. For-5 + -3, this gives-8.- Why is one round not enough?
A carry can create a new carry. For
2 + 3, round 1 givestotal = 1andcarry = 4. These two values still have to be combined, so the loop continues untilbis0.
SWE Reasoning
Spot it
- The statement forbids the
+and-operators. - The statement asks you to combine two integers with only bit operators.
- A process repeats until a carry is gone, as in adding two binary strings by hand. Compare Add Binary (LC 67), where the inputs are strings.
- Negative integers are allowed, and the language may have no fixed integer width.
The invariant, step by step
- Start with
aandbcut to 32 bits bymask. The suma + bis the original sum, modulo2 ** 32. - In a round,
total = a ^ bis the sum of every place without carries. carry = (a & b) << 1holds the carries. Each carry sits one place to the left of the place that made it.- The new pair
(total, carry)has the same sum modulo2 ** 32as the old pair(a, b). - Each round moves every carry at least one place left. After at most 32 rounds, every carry has left the word, and
b = 0. - Then
ais the sum as a 32-bit pattern. The last line converts it to a signed value.
Traps
- No 32-bit mask in Python. A negative number never loses its infinite 1 bits. For
-1 + 1the loop does not end. - No
<< 1on the carry. The carry is added back to the same place that made it. For2 + 3, the function returns3instead of5. return awithout the sign conversion. For-5 + -3, the function returns4294967288instead of-8.if b:instead ofwhile b:. One round is not enough, because a carry can make another carry. For2 + 3, one round leavestotal = 1andcarry = 4, and the function returns1.