A binary number is just a number in base-2. Normal (decimal) numbers, like
25 or 672 are in base-10: each digit represents a tens place. So in binary,
each digit (called a bit) represents a twos place. In both of them there's a pattern
involving raising the base to a power; take 672, for example. The 2 is
in the ones place, the 7 is in the tens place, and the 6 is in the hundreds
place. In base-10, the 'places' are all powers of 10 (even the ones place,
since 1 is just 100). This can be shown like so:
2*100 + 7*101 + 6*102 = 672
or, in the other way if that's easier to read:
6*102 + 7*101 + 2*100 = 672
How does that apply to binary? Well in base-10, all the digits range from
0-9, so in base-2, all the digits range from 0-1. Take the binary number
101100. Going right to left, we have:
0*20 +
0*21 +
1*22 +
1*23 +
0*24 +
1*25 = 44.
Counting in binary isn't too hard. There's also a pattern to it.
Here's counting to 15:
0000 = 0
You should see the pattern. You can also count past 15, but you'd need another
bit. After adding one, you could count to 31. If you add another, you could count
to 63. How can this be known? In general, for an unsigned binary number with
n bits, it can represent any number from 0 to 2n-1. Unsigned just means
that it doesn't have a sign bit, so it can only be a positive integer. I'll
get to signed numbers sometime idk. So with 4
bits, that's 24-1 = 15, so 15 is the maximum number it can
represent. Starting from 20, some powers of 2 are:
0001 = 1
0010 = 2
0011 = 3
0100 = 4
0101 = 5
0110 = 6
0111 = 7
1000 = 8
1001 = 9
1010 = 10
1011 = 11
1100 = 12
1101 = 13
1110 = 14
1111 = 15
1: 0
You might recognize 231: it's 1 off from the integer overflow number. On a lot
of games, numbers are stored in regular 32 bit signed ints, so 231-1
is the maximum number that can be stored. If you reach it, then it
usually like wraps around and becomes negative. It's also part of the deal
with computers. A long time ago, there were 8 bit and 16 bit computers.
They usually did math with integers of that size. But it also affected memory
addressing; like if they wanted to access the 50000th byte of memory. I think
8 bit computers used 16 bit addressing, since being limited to 255 bytes of
ram would be horrible. But 16 bit addressing was pretty much
limited to 65535 bytes of ram (or like 64 kb). (although the
8086 had memory segmentation, allowing 20 bit addressing
or about 1 MB of ram). So then people came up
with 32 bit processors, and those can usually address up to like 4 gb of ram.
But not too long ago, that wasn't enough, so then came 64 bit processors that
can address up to like... 264-1 bytes I guess, and we probably
won't EVER need that much.
2: 1
4: 2
8: 3
16: 4
32: 5
64: 6
128: 7
256: 8
512: 9
1024: 10
2048: 11
4096: 12
8192: 13
16384: 14
32768: 15
65536: 16
...
2147483648: 31
4294967296: 32
...
9223372036854775808: 63
18446744073709551616: 64
*note that there are some exceptions to this though, like PAE which
allows 32 bit processors to address even more memory. Idk how that
stuff really works and what I said is probably kinda wrong but oh well.
total = 0
1011010
0 = +0 (1s place)
1 = +2 (2s place)
0 = +0 (4s place)
1 = +8 (8s place)
1 = +16 (16s place)
0 = +0 (32s place)
1 = +64 (64s place)
total = 90
So, 1011010 is 90 in binary. This method isn't too hard to do in your
head. I don't know if it's the best way of doing it, though.
total = 90
-64 = 1000000 64s place
total = 26
can't subtract 32 32s place
-16 = 1010000 16s place
total = 10
- 8 = 1011000 8s place
total = 2
can't subtract 4 4s place
- 2 = 1011010 2s place
total = 0: 1011010 is the complete binary
can't subtract 1 1s place
101101
Remember that each digit is just a 'place' of a power of 2. In this, you
have a total that starts at the number you're trying to convert to binary,
and then you start subtracting the biggest power of 2 that fits in it.
From that, you have the number of bits that can fit in the number, and
you begin working down the powers of 2: skipping them if they're too
big to be subtracted from the number, or subtracting them from the number
and setting the corresponding bit to 1 otherwise. Do this until the
total is 0. Or refer to this
wikihow article; this method is method #2 in it apparently. I'm
sure it has guides for converting from binary too.
10100: 20
01101: 13 (equivalently 1101, but add the extra 0 so they have the same # of digits ig)
AND:
10100
01101
=====
00100 (4)
OR:
10100
01101
=====
11101 (29)
XOR:
10100
01101
=====
11001 (25)
NOT 20:
10100
=====
01011 (11)
NOT 13:
01101
=====
10010 (18)
1001010 (74) after logical shift RIGHT =
0100101 (37)
0011101 (29) after logical shift LEFT =
0111010 (58)
Because it doesn't handle the sign bit specially, it's mostly useful
for unsigned numbers. For signed numbers, try the arithmetic shift,
although note that a left logical shift is identical to a left logical
shift:
10110111 (-73) after arithmetic shift RIGHT =
11011011 (-37)
01101010 (106) after arithmetic shift RIGHT =
00110101 (53)
00101000 (40) after arithmetic shift RIGHT TWICE =
00001010 (10)
There are a few things to note in general for these shifts. First,
a right shift by n bits is equal to a floored division of the number
by 2n. A left shift by n bits is equal to multiplying the
number by 2n. Because bitwise operations are usually
easier for a processor to do than math, any decent compiler or something
should use bit shifting to optimize multiplication/division with powers of 2.
Programming languages that implement shifts might also not have
separate symbols for logical and arithmetic shifts, or it might only
have them for a right shift since the left logical/arithmetic shifts
are identical.
1101000 after circular shift LEFT =
1010001
1001001 after circular shift RIGHT =
1100100
There's also circular shift with carry: the bit that is shifted in
is the carry bit, and the bit that is shifted out becomes the carry bit. But
idk much about it.
local arr = {5, 1, 6, 7, 1, 6, 5}
--7 is the lone number
local function findlonely(arr)
for i = 1, #arr do
local lonely = true
for j = i+1, #arr do
if arr[i] == arr[j] then
lonely = false
end
end
if lonely then
return arr[i]
end
end
end
print(findlonely(arr))
~Output:7
local bit = require("bit") --bitwise operator functions in lua
local arr = {5, 1, 6, 7, 1, 6, 5}
--exact same as above
local function findlonely(arr)
return bit.bxor(unpack(arr))
end
print(findlonely(arr))
~Output:7
Outside of that, bitwise stuff is still really useful. Mostly in lower level stuff, tho. Like what if you wanna make an emulator? If you emulate a CPU, you'll probably want to emulate the flags, too, which are individual bits denoting the state of something, like the result of a comparison or the sign of the previous operation's result idk. Say there's 8 different flags that need to be toggled indivudally. You could use an array of 8 numbers to represent it, and just switch each number between 0 and 1 as needed; but this is a waste of memory! Each "bit" is now just an array of bits in of itself, and each number can technically range from 0 to 255 or even 0 to 232-1 depending on the implementation, but only 0 and 1 are actually used. A simple char is already an array of 8 bits, so just one of them should be able to represent the flags. The trouble comes down to manipulating the indivdual bits in it; it's not as easy as just indexing an array. But this is where bitwise operators come into play! Or maybe in the emulator flag example, you can use C bitfields. But bitwise operators are still useful:
local bit = require("bit")
local function getbit(num, n)
return bit.band(bit.rshift(num, n), 1)
end
print(getbit(0b0010, 1))--note that its like 0 indexed
print(getbit(0b0010, 0))
print(getbit(52, 4))
~Output:1
0
1
local bit = require("bit")
local function setbit(num, n)
return bit.bor(num, bit.lshift(1, n))
end
print(setbit(0, 0))
print(setbit(0b0111, 3))
print(setbit(70, 8))
~Output:1
15
326