Difference between revisions of "Lua API:Bit"
m (Lua category) |
(Fix description of negative numbers in binary and of some bitwise operations) |
||
(4 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | The Bit API provides functions for performing bitwise operations on integer numbers, the Bit API is from the [http://bitop.luajit.org/index.html LuaJIT BitOp library], documentation is | + | The Bit API provides functions for performing bitwise operations on integer numbers, the Bit API is from the [http://bitop.luajit.org/index.html LuaJIT BitOp library], documentation for it is [http://bitop.luajit.org/api.html here] |
If you are unfamiliar with bitwise operations, you may want to check the Wikipedia page on the subject: http://en.wikipedia.org/wiki/Bitwise_operation | If you are unfamiliar with bitwise operations, you may want to check the Wikipedia page on the subject: http://en.wikipedia.org/wiki/Bitwise_operation | ||
== Methods == | == Methods == | ||
+ | |||
+ | Note that as the binary numbers here are 4 bytes long, it's often undesirable to write out all 32 bits every time a number is written. Thus I will use :: as a sort of fill-in meaning all other bits are 0. | ||
+ | |||
+ | === Negative numbers in binary === | ||
+ | |||
+ | For some of the functions below it's better to remember how negative numbers are most often represented in binary. | ||
+ | |||
+ | For positive numbers, it's easy enough, just keep all the bits in place and add to the rightmost bit, and as the number gets bigger, the change will trickle left as bigger and ''more significant'' bits get changed. | ||
+ | |||
+ | For negative numbers however, the usual approach is to make the leftmost bit or the ''most significant bit'' into something that keeps track of which sign the number has, invert all other bits and add one. This is also called ''two's complement'' of a number, and looks like this: | ||
+ | |||
+ | 0 000 0010 --> 2 Start with positive number you want to invert | ||
+ | 1. 1 000 0010 Set the most significant bit (or ''sign bit'') to 1 | ||
+ | 2. 1 111 1101 Invert all other bits | ||
+ | 3. 1 111 1110 --> -2 And, finally, add 1 to the result | ||
+ | ^ -------- (1) | ||
+ | ^^^ ^^^^ (2) | ||
+ | |||
+ | '''(1):''' Sign bit. This says "Hey, the number here is negative." | ||
+ | |||
+ | '''(2):''' Rest of the bits. | ||
+ | |||
+ | This allows addition and subtraction of binary numbers without needing to worry about sign bit and overflows. For example, if you want to add numbers -2 and 5, you can do it like this: (4 bits for shortness) | ||
+ | |||
+ | 1 110 --> -2 | ||
+ | + 0 101 --> 5 | ||
+ | |||
+ | 1. 0 + 1 = 1, set first bit of result to 1 | ||
+ | 2. 1 + 0 = 1, set second bit of result to 1 | ||
+ | 3. 1 + 1 = 2, but since 2 is greater than 1 set third bit of result to 0 and add 1 to the next bit | ||
+ | 4. 1 + 0 and + 1 more = 2, again greater than 1, so set fourth bit of result to 0, but since this is the sign bit ignore the overflow | ||
+ | |||
+ | = 0 011 --> 3 | ||
+ | |||
+ | To learn more about it, check the Wikipedia page for ''two's complement'': https://en.wikipedia.org/wiki/Two%27s_complement | ||
+ | |||
=== bit.tobit === | === bit.tobit === | ||
− | + | ||
− | + | <code>bit.tobit(number input)</code> | |
+ | |||
+ | Converts a 64-bit integer into a 32-bit integer by removing all bits above 32nd. You do not need to use this function, as the below functions will do this automatically. Here's some example values: | ||
+ | |||
+ | bit.tobit(100) --> 100 | ||
+ | bit.tobit(-100) --> -100 | ||
+ | bit.tobit(2147483647) --> bit.tobit(2^31 - 1) = 2147483647 | ||
+ | bit.tobit(2147483648) --> bit.tobit(2^31) = -2147483648 | ||
+ | |||
+ | |||
=== bit.tohex === | === bit.tohex === | ||
− | + | ||
− | Converts its first argument to a | + | <code>bit.tohex(number input, number length)</code> |
+ | |||
+ | '''By default, length is 8, if you leave it out.'''<br /> | ||
+ | Converts its first argument to a hexadecimal representation. Returns a string. | ||
+ | |||
+ | How many hexadecimal digits to end up with is specified by the length argument. The length argument can also be negative, which specifies that the end result should have all-caps letters instead of small letters. | ||
+ | |||
+ | '''Examples:''' | ||
+ | |||
+ | bit.tohex(1) --> "00000001" | ||
+ | bit.tohex(0xdeadbeef) --> "deadbeef" | ||
+ | bit.tohex(15, 2) --> "0f" | ||
+ | bit.tohex(0xdeadbeef, 2) --> "ef" | ||
+ | bit.tohex(0xdeadbeef, -4) --> "BEEF" | ||
+ | bit.tohex(0xdeadbeef, 25) --> "deadbeef" | ||
+ | |||
+ | |||
=== bit.bnot === | === bit.bnot === | ||
− | + | ||
− | Returns the bitwise | + | <code>bit.bnot(number input)</code> |
− | === bit.band | + | |
− | + | Returns the bitwise NOT from its argument. | |
− | + | ||
− | + | What happens during a bitwise NOT looks kinda like this: you start off with a value like 25 (<code>::00011001</code>) and you flip every bit specially and return the value, so... | |
− | Returns | + | |
+ | ~ 0000 0000 0000 0000 0000 0000 0001 1001 | ||
+ | = 1111 1111 1111 1111 1111 1111 1110 0110 | ||
+ | |||
+ | |||
+ | === bit.band === | ||
+ | |||
+ | <code>bit.band(number input, [number input...])</code> | ||
+ | |||
+ | Returns the binary AND of all its arguments combined. | ||
+ | |||
+ | A binary AND is an operation that requires two numbers: the only bits that remain in the return value are ones that occur in both of the numbers. This is an easy way to pick out a single bit from a "bit string" - an integer that is actually used as a row of bits that can be used as boolean values. You can then store 8 booleans in a single byte, whereas usually a single boolean is a single byte! | ||
+ | |||
+ | :: 0001 1001 & | ||
+ | :: 0011 0011 & | ||
+ | :: 0001 1010 | ||
+ | = 0001 0000 | ||
+ | |||
+ | |||
+ | === bit.bor === | ||
+ | |||
+ | <code>bit.bor(number input, [number input...])</code> | ||
+ | |||
+ | Returns the binary inclusive OR of all its arguments combined. | ||
+ | |||
+ | Binary OR is really simple: if one of the arguments has a bit there, then the end result has a bit there too. This is also a simple way to deal with flags - you'll know that a bit will be turned on if you pass it through a binary OR. Binary OR happens like so: | ||
+ | |||
+ | :: 0001 1001 | | ||
+ | :: 0011 1101 | | ||
+ | :: 0100 1000 | ||
+ | = 0111 1101 | ||
+ | |||
+ | |||
+ | === bit.bxor === | ||
+ | |||
+ | <code>bit.bxor(number input, [number input...])</code> | ||
+ | |||
+ | Returns the binary exclusive OR of all its arguments combined. | ||
+ | |||
+ | Binary exclusive OR or XOR for short is interesting: the end result's bit only gets toggled on when only one of the arguments has this bit toggled on. For flags, you can utilize it to toggle a bit - if it's on it will be off and if it's off it'll be on. | ||
+ | |||
+ | :: 0011 0011 ^ | ||
+ | :: 0010 1001 ^ | ||
+ | :: 1100 1100 | ||
+ | = 1101 0110 | ||
+ | |||
=== bit.lshift, bit.rshift, bit.arshift === | === bit.lshift, bit.rshift, bit.arshift === | ||
− | + | ||
− | + | <code>bit.lshift(number input, number shift)</code> | |
− | number bit. | + | |
− | + | Shifts the input bits to the left by (shift) bits, replacing empty cells with 0 and discarding overflowing cells. | |
− | + | ||
− | + | Left shifting is something really easy to imagine: every bit just gets moved once (or more) to the left and the ends are replaced with 0. If you have a bit at the far left end it gets eaten and is lost forever. Not to worry usually though. | |
+ | |||
+ | Another great utilization of left shifting is that it's a really quick way of multiplying a number by two for some times. Shifting to the left by two is the same as multiplying a number by four. Note that this performs logical shift meaning that it will also move the ''sign bit''. | ||
+ | |||
+ | :: 0000 0010 << 2 | ||
+ | = 0000 1000 | ||
+ | |||
+ | '''Examples:''' | ||
+ | |||
+ | bit.lshift(2, 2) --> 8 | ||
+ | bit.lshift(2, 34) --> 8 | ||
+ | |||
+ | '''Important note: the shift count has to be between 1 and 32. Otherwise the number will be modulus-ed to it.''' | ||
+ | |||
+ | |||
+ | |||
+ | <code>bit.rshift(number input, number shift)</code> | ||
+ | |||
+ | Shifts the input bits to the right by (shift) bits, replacing empty cells with 0 and discarding overflowing cells. | ||
+ | |||
+ | Right shifting is something really easy to imagine: every bit just gets moved once (or more) to the right and the ends are replaced with 0. If you have a bit at the far right end it gets eaten and is lost forever. Not to worry usually though. | ||
+ | |||
+ | Another great utilization of right shifting is that it's a really quick way of dividing a number by two for some times. Shifting to the right by two is the same as dividing a number by four. Note that this performs logical shift meaning that it will also move the ''sign bit''. | ||
+ | |||
+ | 1111 1111 :: >>> 2 | ||
+ | = 0011 1111 1100 0000 :: | ||
+ | |||
+ | '''Examples:''' | ||
+ | |||
+ | bit.rshift(-2, 5) --> 134217727 | ||
+ | bit.rshift(4, 1) --> 2 | ||
+ | bit.rshift(4, 33) --> 2 | ||
+ | |||
+ | '''Important note: the shift count has to be between 1 and 32. Otherwise the number will be modulus-ed to it.''' | ||
+ | |||
+ | |||
+ | |||
+ | <code>bit.arshift(number input, number shift)</code> | ||
+ | |||
+ | Shifts the input bits to the right by (shift) bits, copying over the sign bit for the new bits added from the left. This keeps the sign right in place and still allows us to shift all we want. | ||
+ | |||
+ | 1001 1001 0000 0000 :: >> 2 | ||
+ | = 1110 0110 0100 0000 :: | ||
+ | |||
+ | '''Examples:''' | ||
+ | |||
+ | bit.arshift(-4, 1) --> -2 | ||
+ | bit.arshift(-32, 5) --> -1 | ||
+ | bit.arshift(32, 5) --> 1 | ||
+ | |||
+ | '''Important note: the shift count has to be between 1 and 32. Otherwise the number will be modulus-ed to it.''' | ||
+ | |||
=== bit.rol, bit.ror === | === bit.rol, bit.ror === | ||
− | + | ||
− | + | <code>bit.rol(number input, number bits)</code> | |
− | + | ||
− | + | <code>bit.ror(number input, number bits)</code> | |
+ | |||
+ | Exactly like bit shifting, except it copies the bits from the right to the bits on the left instead of just feasting upon them. | ||
+ | |||
+ | :: 0000 1111 ROR 2 | ||
+ | = 1100 0000 :: 0000 0011 | ||
+ | :: 1100 0011 1100 0011 ROR 2 | ||
+ | = 1111 0000 1111 0000 | ||
+ | |||
+ | '''Important note: the rotation count has to be between 1 and 32. Otherwise the number will be modulus-ed to it.''' | ||
+ | |||
=== bit.bswap === | === bit.bswap === | ||
− | + | ||
− | Swaps the | + | <code>bit.bswap(number input)</code> |
+ | |||
+ | Swaps the ''byte order'' of the argument. | ||
+ | |||
+ | In a some places, TPT uses low-endian (least significant byte on the left) instead of usually used big-endian (most significant byte on the left) order of bytes, such as in TPT saves in the OPS 1 format. This function makes things easier and just flips the byte order for you. | ||
+ | |||
+ | 0110 1010 0101 1001 | ||
+ | = 1001 0101 1010 0110 | ||
+ | |||
+ | '''Examples:''' | ||
+ | |||
+ | bit.tohex(bit.bswap(0x01020304)) --> "04030201" | ||
+ | bit.tohex(bit.bswap(0xdeadbeef), -8) --> "EFBEADDE" | ||
+ | bit.bswap(5) --> 83886080 | ||
+ | |||
[[Category:Lua]] | [[Category:Lua]] |
Latest revision as of 18:00, 19 August 2024
The Bit API provides functions for performing bitwise operations on integer numbers, the Bit API is from the LuaJIT BitOp library, documentation for it is here
If you are unfamiliar with bitwise operations, you may want to check the Wikipedia page on the subject: http://en.wikipedia.org/wiki/Bitwise_operation
Contents
Methods
Note that as the binary numbers here are 4 bytes long, it's often undesirable to write out all 32 bits every time a number is written. Thus I will use :: as a sort of fill-in meaning all other bits are 0.
Negative numbers in binary
For some of the functions below it's better to remember how negative numbers are most often represented in binary.
For positive numbers, it's easy enough, just keep all the bits in place and add to the rightmost bit, and as the number gets bigger, the change will trickle left as bigger and more significant bits get changed.
For negative numbers however, the usual approach is to make the leftmost bit or the most significant bit into something that keeps track of which sign the number has, invert all other bits and add one. This is also called two's complement of a number, and looks like this:
0 000 0010 --> 2 Start with positive number you want to invert 1. 1 000 0010 Set the most significant bit (or sign bit) to 1 2. 1 111 1101 Invert all other bits 3. 1 111 1110 --> -2 And, finally, add 1 to the result ^ -------- (1) ^^^ ^^^^ (2)
(1): Sign bit. This says "Hey, the number here is negative."
(2): Rest of the bits.
This allows addition and subtraction of binary numbers without needing to worry about sign bit and overflows. For example, if you want to add numbers -2 and 5, you can do it like this: (4 bits for shortness)
1 110 --> -2 + 0 101 --> 5
1. 0 + 1 = 1, set first bit of result to 1 2. 1 + 0 = 1, set second bit of result to 1 3. 1 + 1 = 2, but since 2 is greater than 1 set third bit of result to 0 and add 1 to the next bit 4. 1 + 0 and + 1 more = 2, again greater than 1, so set fourth bit of result to 0, but since this is the sign bit ignore the overflow
= 0 011 --> 3
To learn more about it, check the Wikipedia page for two's complement: https://en.wikipedia.org/wiki/Two%27s_complement
bit.tobit
bit.tobit(number input)
Converts a 64-bit integer into a 32-bit integer by removing all bits above 32nd. You do not need to use this function, as the below functions will do this automatically. Here's some example values:
bit.tobit(100) --> 100 bit.tobit(-100) --> -100 bit.tobit(2147483647) --> bit.tobit(2^31 - 1) = 2147483647 bit.tobit(2147483648) --> bit.tobit(2^31) = -2147483648
bit.tohex
bit.tohex(number input, number length)
By default, length is 8, if you leave it out.
Converts its first argument to a hexadecimal representation. Returns a string.
How many hexadecimal digits to end up with is specified by the length argument. The length argument can also be negative, which specifies that the end result should have all-caps letters instead of small letters.
Examples:
bit.tohex(1) --> "00000001" bit.tohex(0xdeadbeef) --> "deadbeef" bit.tohex(15, 2) --> "0f" bit.tohex(0xdeadbeef, 2) --> "ef" bit.tohex(0xdeadbeef, -4) --> "BEEF" bit.tohex(0xdeadbeef, 25) --> "deadbeef"
bit.bnot
bit.bnot(number input)
Returns the bitwise NOT from its argument.
What happens during a bitwise NOT looks kinda like this: you start off with a value like 25 (::00011001
) and you flip every bit specially and return the value, so...
~ 0000 0000 0000 0000 0000 0000 0001 1001 = 1111 1111 1111 1111 1111 1111 1110 0110
bit.band
bit.band(number input, [number input...])
Returns the binary AND of all its arguments combined.
A binary AND is an operation that requires two numbers: the only bits that remain in the return value are ones that occur in both of the numbers. This is an easy way to pick out a single bit from a "bit string" - an integer that is actually used as a row of bits that can be used as boolean values. You can then store 8 booleans in a single byte, whereas usually a single boolean is a single byte!
:: 0001 1001 & :: 0011 0011 & :: 0001 1010 = 0001 0000
bit.bor
bit.bor(number input, [number input...])
Returns the binary inclusive OR of all its arguments combined.
Binary OR is really simple: if one of the arguments has a bit there, then the end result has a bit there too. This is also a simple way to deal with flags - you'll know that a bit will be turned on if you pass it through a binary OR. Binary OR happens like so:
:: 0001 1001 | :: 0011 1101 | :: 0100 1000 = 0111 1101
bit.bxor
bit.bxor(number input, [number input...])
Returns the binary exclusive OR of all its arguments combined.
Binary exclusive OR or XOR for short is interesting: the end result's bit only gets toggled on when only one of the arguments has this bit toggled on. For flags, you can utilize it to toggle a bit - if it's on it will be off and if it's off it'll be on.
:: 0011 0011 ^ :: 0010 1001 ^ :: 1100 1100 = 1101 0110
bit.lshift, bit.rshift, bit.arshift
bit.lshift(number input, number shift)
Shifts the input bits to the left by (shift) bits, replacing empty cells with 0 and discarding overflowing cells.
Left shifting is something really easy to imagine: every bit just gets moved once (or more) to the left and the ends are replaced with 0. If you have a bit at the far left end it gets eaten and is lost forever. Not to worry usually though.
Another great utilization of left shifting is that it's a really quick way of multiplying a number by two for some times. Shifting to the left by two is the same as multiplying a number by four. Note that this performs logical shift meaning that it will also move the sign bit.
:: 0000 0010 << 2 = 0000 1000
Examples:
bit.lshift(2, 2) --> 8 bit.lshift(2, 34) --> 8
Important note: the shift count has to be between 1 and 32. Otherwise the number will be modulus-ed to it.
bit.rshift(number input, number shift)
Shifts the input bits to the right by (shift) bits, replacing empty cells with 0 and discarding overflowing cells.
Right shifting is something really easy to imagine: every bit just gets moved once (or more) to the right and the ends are replaced with 0. If you have a bit at the far right end it gets eaten and is lost forever. Not to worry usually though.
Another great utilization of right shifting is that it's a really quick way of dividing a number by two for some times. Shifting to the right by two is the same as dividing a number by four. Note that this performs logical shift meaning that it will also move the sign bit.
1111 1111 :: >>> 2 = 0011 1111 1100 0000 ::
Examples:
bit.rshift(-2, 5) --> 134217727 bit.rshift(4, 1) --> 2 bit.rshift(4, 33) --> 2
Important note: the shift count has to be between 1 and 32. Otherwise the number will be modulus-ed to it.
bit.arshift(number input, number shift)
Shifts the input bits to the right by (shift) bits, copying over the sign bit for the new bits added from the left. This keeps the sign right in place and still allows us to shift all we want.
1001 1001 0000 0000 :: >> 2 = 1110 0110 0100 0000 ::
Examples:
bit.arshift(-4, 1) --> -2 bit.arshift(-32, 5) --> -1 bit.arshift(32, 5) --> 1
Important note: the shift count has to be between 1 and 32. Otherwise the number will be modulus-ed to it.
bit.rol, bit.ror
bit.rol(number input, number bits)
bit.ror(number input, number bits)
Exactly like bit shifting, except it copies the bits from the right to the bits on the left instead of just feasting upon them.
:: 0000 1111 ROR 2 = 1100 0000 :: 0000 0011 :: 1100 0011 1100 0011 ROR 2 = 1111 0000 1111 0000
Important note: the rotation count has to be between 1 and 32. Otherwise the number will be modulus-ed to it.
bit.bswap
bit.bswap(number input)
Swaps the byte order of the argument.
In a some places, TPT uses low-endian (least significant byte on the left) instead of usually used big-endian (most significant byte on the left) order of bytes, such as in TPT saves in the OPS 1 format. This function makes things easier and just flips the byte order for you.
0110 1010 0101 1001 = 1001 0101 1010 0110
Examples:
bit.tohex(bit.bswap(0x01020304)) --> "04030201" bit.tohex(bit.bswap(0xdeadbeef), -8) --> "EFBEADDE" bit.bswap(5) --> 83886080