====== Chess - Programming - de Bruijn Sequence - 8-bit Example ====== An 8-bit value has 8 digits. * The position can be represented by 3 bits. * Log2(8) = 3. ---- ===== Using a de Bruijn sequence ===== int lsb_pos(std::uint8_t v) { // 1. Define a De Bruijn Bit Table. static constexpr int DeBruijnBitTable[8] = { 1 , 2 , 7 , 3 , 8 , 6 , 5 , 4 }; // 2. Get the LSB. std::uint8_t pop_lsb = (v & -v); // 3. Get which Index to use in the De Bruijn Bit Table. std::uint8_t hash = std::uint8_t (pop_lsb * 0x1DU) >> 5; // 4. Get the specific Table value. int r = DeBruijnBitTable[hash]; return r; } **NOTE:** This shows 4 steps: * **Step 1**: Describes a pre-built De Bruijn Bit Table. * **Step 2**: Returns the LSB (Least Significant Bit) from the value being checked against. * **Step 3**: Uses the LSB from Step 2 to determine which Index should be referred to from the De Bruijn Bit Table. * **Step 4**: Uses the Index from Step 3 to return the actual hash value. The big question is where do the following come from: * **>>5**: As an 8-bit number can fit into 3-bits, we want to remove the other 5 bits not needed 11111111 >> 101 = 111. * This leaves the most significant 3 bits. * **0x1D**: This value has to be calculated through trial-and-error. * In binary is 00011101. The de Bruijn sequence. * Using the binary number and look at it 3 characters at a time, it contains all the sequence numbers with are no duplicates: * 000 001 011 111 110 101 010 100 * The last code requires circulating to the first bit. ---- ===== The de Bruijn sequence of this 8-bit value ===== ^bit^pop_lsb^pop_lsb * 0x1D^hash^index^index order^final order^ ^:::^:::^:::^(1st 3 bits)^:::^(index + 1)^(bit at index order position)^ |1|1|0001 1101|000|0|1|1| |2|2|0011 1010|001|1|2|2| |3|4|0111 0100|011|3|4|3| |4|8|1110 1000|111|7|8|7| |5|16|1101 0000|110|6|7|6| |6|32|1010 0000|101|5|6|5| |7|64|0100 0000|010|2|3|7| |8|128|1000 0000|100|4|5|4| **NOTE:** This is a complete hash in which a value with only one bit set is nicely pushed into a 3-bit number. * Using the table above with the index, will return the value of the original pop_lsb. * **hash**: Uses the first 3 bits from the previous step, **pop_lsb * 0x1D**. * **index**: The number represented by the hash. * **index order**: The sequence of the numbers. * **final order**: The bit at the position of the **index order**. * Prepare an array with the corresponding number of digits at this position. constexpr int table [8] = { 1 , 2 , 7 , 3 , 8 , 6 , 5 , 4 }; * This table is made by using the **index order** to determine the next number from the **final order** columns. * The de Bruijn sequence must start with the first digit, the 0 element. * This is to prevent overflow and to allow the whole to circulate naturally during multiplication (left shift operation). ---- ===== Explaining the usage of this de Bruijn sequence ===== constexpr int table [8] = { 1 , 2 , 7 , 3 , 8 , 6 , 5 , 4 }; The c++ code above performs the following function. * It takes a number from 1 to 8 to lookup. * It multiplies this number by 0x1D, which is 29 Decimal. This is the Magic Number. * It right-shifts this by 5 to determine the specific bit position of that lookup value. ^Lookup^Lookup multiple by the magic number^Right-shifted by the offset^ ^:::^(29 in this example)^(5 in this example)^ |1|29|0| |2|58|1| |7|203|6| |3|87|2| |8|232|7| |6|174|5| |5|145|4| |4|116|3| **NOTE:** This shows that: * A lookup of **1** returns 0, which is the first bit. * A lookup of **2** returns 1, which is the second bit. * A lookup of **3** returns 2, which is the third bit. * ...and so on. Every lookup returns a unique and distinct value. ---- ===== The Magic Number ===== **NOTE**: Here, the **1D** magic number was used. * However there may be other **magic numbers** that might also work. * As long as a valid De Bruijn sequence is determined that uniquely provides the bit sequences this could be used instead. ---- ===== Hash Function Calculation ===== hash(x) = (x ∗ dEBRUIJN) >> SHIFT **NOTE:** * **N**: is the width of the unsigned integer type. * **dEBRUIJN**: is the appropriate de Bruijn sequence of length to exhaust the combination of characters being used, 0, 1. * **SHIFT**: Can be calculated using this formula: **(int)(N − Log2(N))**. * Using the 8-bit example: * Log2(8) = 2.40823996531 * 8 - 2.40823996531 = 5.59176003469 * Round down to make an integer results in 5. ---- ===== References ===== https://onihusube-hatenablog-com.translate.goog/entry/2019/09/20/230416?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=nui,sc