lainchan / λ

(untitled)

roll · 2022-04-20 21:16
Recently I rediscovered pic related and I got to thinking about bitwise operations/tricks/hacks. So I wanted to make a thread about it. Most programmers probably learn them pretty early on and use them probably pretty rarely. I know some of you will find everything here completely trivial but if you're new to programming or you just find this stuff cool and interesting, stick around and post your best/coolest/most useful bitwise tricks. I'm gonna start with some basic stuff. The basic operations are: operation: symbol (commonly) and : & or : | not : ~ xor : ^ There are others like nor but afaik those don't commonly have their own symbols but are rather constructed from these basic ones (nor would be ~(x | y) where x and y are the two input bits). Also there is the bitshift operations. Let's just say the << and >> operations are logical shifts ( https://en.wikipedia.org/wiki/Bitwise_operation#Boolean_algebra ) unless otherwise specified. Alright now that we're on the same page about the symbols, we can get started. If you don't know how something works I encourage you to figure it out on pen and paper, some of these are very simple. Some cool tricks I use from time to time: >x*2^n x << n >Biggest number you can represent with x bits (1 << x) - 1 >Dividing x by 2 x >> 1 Shifting 1 to the right by the number you want to divide gives you x/2 given that x is divisble by 2 (if not it rounds down to nearest integer). >Set nth bit of a sequence of bits x to 1 x = (1 << n) | x For example if x = 0b1000 and n =2, then: (1 << n) = 0b0100 0b1000 ^ 0b0100 = 0b1100 >Set nth bit of a sequence of bits x to 0 x = ~(1 << n) & x >Toggle nth bit of a sequence of bits x x = (1 << n) ^ x >Is x a power of two? (x & (x - 1)) == 0 This will return true if x is a power of two, false otherwise. >Kernighan's population count count = 0 while (c != 0): x = x & (x - 1) count++ This one counts how many 1s are in x. >Changing the sign of sequence x -x = ~x + 1 >swap x and y x = x ^ y y = x ^ y x = x ^ y No temp variable needed! >Pic related for i in range(4): B ^= A C ^= B A ^= C B ^= A C ^= B Here by "^= " I mean for example B^=A <=> B = B^A. Also this is not really a bit hack/manipulation but still useful and kinda related: If you have a system with n states, you need x=log2(n) number of bits to represent that system's state (round up to integers). This is kinda obvious if you think about it: 2^x is the number of possible states you can express with x bits. For example, a system with 4 possible states can be expressed with 2 bits (00, 01, 10, 11) and this works for any number of bits.
list · 2022-04-20 21:41
>>30870 Oops I made a mistake. Kernighan's population count goes like this: count = 0 while (x != 0): x = x & (x - 1) count++
Jet Black · 2022-04-20 22:18
Fairly simple one: >Is x odd? x & 1
The Fin · 2022-04-20 22:39
>>30873 Nice, simple but useful
Nobody special · 2022-04-21 07:46
Protip: let the compiler do its work when your n is a compile time constant, it'll be more readable.
Jane Doe · 2022-04-21 08:26
>>30883 For sure, some of these I just run quickly on a python interpreter for whatever reason. For actual bit level manipulation these are readable I think but it's not often one has to do that really.
Sabo Engle · 2022-04-21 10:47
Can someone tell me how to use a Karnaugh map?
pop · 2022-04-21 12:05
>>30885 Do you have a specific question? If you want to just generally learn how to use them here's a great video: https://www.youtube.com/watch?v=RO5alU6PpSU
progn · 2022-04-21 21:03
>>30873 Opaque. Use is_even.js instead.
Trinity · 2022-04-22 07:44
>>30897 >.js 1/10 bait, can do better.
Neuromancer · 2022-05-06 17:30
you can use bitwise operation on a switch statement. it's pretty cool, i like it.
Lunch · 2022-05-06 20:00
>>31092 What are you talking about? This is just doing bitwise operations on the value passed to switch, nothing specific to switch. What you probably mean is the technique of switching on multiple integers by merging their bits. By the way, if you want your switches to be as fast as actual FORTRAN jump tables or non standard computed gotos, add a `default: __builtin_unreachable();` case.
Nobody special · 2022-05-10 11:42
>>30870 Recently stumbled across this which is a useful collection of trickery with bitwise logic: https://graphics.stanford.edu/~seander/bithacks.html
Milner · 2022-05-11 04:50
Xor is also useful for speedy and reliable PRNGs. https://en.m.wikipedia.org/wiki/Xorshift
Faye Valentine · 2022-05-15 05:36
first time posting on this site and i love that there is a thread like this OP i highly recommend checking out Hacker's Delight. It's full of hacks and tricks like this that may be of interest to you https://www.amazon.com/Hackers-Delight-2nd-Henry-Warren/dp/0321842685 You also might love AVX512's vpternlog{d,q} instruction: http://0x80.pl/articles/avx512-ternary-functions.html and maybe some stuff involving NPN classification https://people.eecs.berkeley.edu/~alanmi/publications/2013/icfpt13_npn.pdf
word · 2022-05-16 00:15
however file permissions works on POSIX is also bit magic.
synchronized · 2022-05-16 15:51
>>31175 kind of a weird unintuitive design but it did force me to learn bit arithmetic and masking a bit. I'm sure people dealing with subnet masks and such got filtered by it similarly.
Faye Valentine · 2022-08-27 00:44
The funny thing about the 'xor trick' is that it is generally slower than using three MOV statements on most processors because it has to touch the ALU. >>30883 Usually. If you're writing a super-hot inner loop or something that needs to have absolutely minimum latency, and you need to optimize the fuarrrk out of it there's really no way to know for sure without reading the assembly.
Jean Dupont · 2022-09-25 11:58
>>32548 Isn't this trick obsolete by the xchg instruction? This would be faster too though as it's just a register-rename operation within the hardware's register-file.
bit · 2023-08-11 23:04
>>30870 I found this goldmine out of curiosity: https://graphics.stanford.edu/~seander/bithacks.html computer parity of a byte: unsigned char b; // byte value to compute the parity of bool parity = (((b * 0x0101010101010101ULL) & 0x8040201008040201ULL) % 0x1FF) & 1;
Hiro Protagonist · 2023-08-12 22:27
>>36729 almost a year-old bump man. Cool trick. I believe this is just a parallel prefix xor of some kind. You can generalize the same thing using a carryless-multiplication. I believe ARM CPUs support this natively as well through their polynomial types https://wunkolo.github.io/post/2020/05/pclmulqdq-tricks/
Bob Arctor · 2023-08-12 23:54
>>36737 Old threads are worth bumping man
Morpheus · 2023-12-04 00:04
>>36738 I agree. I'd much rather see better old threads hang on longer than get much more quickly replaced by worse threads just because they're newer. The hyper-engaged, instant-gratification mindset of the new web always ends up like kicking dead whales down the beach anyway. I've seen so many great or informative threads here over the years, including this one, that I'd replace at least a third of the catalogue's current threads. The problem with the format is that complicated threads are harder to contribute to than simpler threads, so are at an evolutionary disadvantage unless you relax the technically correct but dubious at higher orders demand that bumps maintain the relative quality and newness of the thread. Worse threads are lesser in absolute quality, so the same type of bumps applied to better threads are better in the context of the whole board, as they compensate for the disadvantage better threads are at under the narrow definition of good contribution. Do your part and keep great threads active, lainons. Or don't, if it's all water under the bridge anyway. Present Day, present Time.
Nell · 2023-12-04 14:10
>>36729 Calculating modulo on a 64 bit integer is pretty expensive, so I did a quick & rough benchmark to see if it's actually faster. int parity_naive(unsigned char b) { int x = 0; for (int i = 0; i < 8; i++) { x ^= (b >> i) & 1; } return x; } int parity_divide_and_conquer(unsigned char b) { b = (b & 15) ^ (b >> 4); b = (b & 3) ^ (b >> 2); b = (b & 1) ^ (b >> 1); return b; } int parity_stanford(unsigned char b) { return (((b * 0x0101010101010101ULL) & 0x8040201008040201ULL) % 0x1FF) & 1; } int main() { volatile int x; for (int k = 0; k < 1000000; k++) { for (volatile int i = 0; i < 255; i++) { #if NAIVE x = parity_naive(i); #elif DIVIDE_AND_CONQUER x = parity_divide_and_conquer(i); #elif STANFORD x = parity_stanford(i); #else #endif } } } real0m0.070s user0m0.070s sys0m0.000s lain@pc:/tmp$ gcc -O3 -DNAIVE x.c && time ./a.out real0m0.273s user0m0.273s sys0m0.000s lain@pc:/tmp$ gcc -O3 -DDIVIDE_AND_CONQUER x.c && time ./a.out real0m0.185s user0m0.184s sys0m0.000s lain@pc:/tmp$ gcc -O3 -DSTANFORD x.c && time ./a.out real0m0.224s user0m0.224s sys0m0.000s Looking at the assembly: 0000000000000000 <parity_naive>: 0: 40 0f b6 c7 movzx eax,dil 4: 89 c2 mov edx,eax 6: 89 c1 mov ecx,eax 8: c1 f9 02 sar ecx,0x2 b: d1 fa sar edx,1 d: 31 ca xor edx,ecx f: 89 c1 mov ecx,eax 11: 31 d7 xor edi,edx 13: c1 f9 03 sar ecx,0x3 16: 89 c2 mov edx,eax 18: 31 f9 xor ecx,edi 1a: c1 fa 04 sar edx,0x4 1d: 31 ca xor edx,ecx 1f: 89 c1 mov ecx,eax 21: c1 f9 05 sar ecx,0x5 24: 31 d1 xor ecx,edx 26: 89 c2 mov edx,eax 28: c1 f8 07 sar eax,0x7 2b: c1 fa 06 sar edx,0x6 2e: 31 ca xor edx,ecx 30: 83 e2 01 and edx,0x1 33: 31 d0 xor eax,edx 35: c3 ret 36: 66 2e 0f 1f 84 00 00 cs nop WORD PTR [rax+rax*1+0x0] 3d: 00 00 00 0000000000000040 <parity_divide_and_conquer>: 40: 89 f8 mov eax,edi 42: 40 c0 ef 04 shr dil,0x4 46: 83 e0 0f and eax,0xf 49: 31 c7 xor edi,eax 4b: 89 f8 mov eax,edi 4d: 83 e7 03 and edi,0x3 50: c0 e8 02 shr al,0x2 53: 31 f8 xor eax,edi 55: 89 c2 mov edx,eax 57: d0 ea shr dl,1 59: 31 d0 xor eax,edx 5b: 83 e0 01 and eax,0x1 5e: c3 ret 5f: 90 nop 0000000000000060 <parity_stanford>: 60: 48 b8 01 01 01 01 01 movabs rax,0x101010101010101 67: 01 01 01 6a: 40 0f b6 ff movzx edi,dil 6e: 48 0f af f8 imul rdi,rax 72: 48 b8 01 02 04 08 10 movabs rax,0x8040201008040201 79: 20 40 80 7c: 48 21 c7 and rdi,rax 7f: 48 b8 03 04 08 10 20 movabs rax,0x80402010080403 86: 40 80 00 89: 48 f7 e7 mul rdi 8c: 48 89 f8 mov rax,rdi 8f: 48 29 d0 sub rax,rdx 92: 48 d1 e8 shr rax,1 95: 48 01 c2 add rdx,rax 98: 48 c1 ea 08 shr rdx,0x8 9c: 8d 04 17 lea eax,[rdi+rdx*1] 9f: 83 e0 01 and eax,0x1 a2: c3 ret There's no actual modulus operation, which I expected, though it is apparently still slower.
Zero Cool · 2023-12-04 14:22
One trick I find myself using fairly often is to quickly compute a power-of-two mask by rounding up an integer to the nearest power-of-two. x |= x >> 1 x |= x >> 2 x |= x >> 4 x |= x >> 8 x |= x >> 16 ... This sequence duplicates the top bit to the bit immediately below, then the top 2 bits to the 2 bits immediately below, then the top 4 bits ... At the end the top set bit has been duplicated to all the lower bits. Useful if you have a collection and you want to avoid expensive modulus operations (e.g. hashmap).
Fred Nurk · 2023-12-24 10:10
Technically not a bit hack but regardless something I believe every programmer should know. Calculating divisions is slow while multiplications are fast on modern hardware, so it's useful to first calculate the reciprocal (i.e. 1/x), then multiply with it. Of course we can't do that directly with integers. But we can make use of a fixed-point format. Calculate in fixed point, then convert back to integers. For example, let's say we want to divide a positive 32 bit integer by 5. We make use of fixed point numbers with a 32 bit integer part and 32 bit fractional part. 1/5 in fixed-point format is (+1 to account for shift right rounding down): 1/y = ((1 << 32) / y) + 1 ==> 1/5 = ((1 << 32) / 5) + 1 = 858993460 [code] To multiply with 1/5: [code] x / y = x * 1/y = (x * 858993460) >> 32 Let's try it! x = 42 (x * 858993460) >> 32 = 8 x = 1337 (x * 858993460) >> 32 = 267 x = 123456789 (x * 858993460) >> 32 = 24691357
Iverson · 2024-01-21 14:06
>>37817 for finding parity, you can do a singular carry-less multiplication too, up to 64-bits https://wunkolo.github.io/post/2020/05/pclmulqdq-tricks/ The "Prefix Xor" section implies that you could just pclmulqdq with 1111111.... and then check a singular bit in the result to get the parity.
Meddler · 2024-01-22 07:11
>>38168 While a neat trick, at least on a 5800X it isn't any faster for 64 bit parity, and much slower for 8 bit. For 64 bit parity it is much shorter though, so better on icache. int parity_clmul(unsigned char b) { __m128i x = _mm_set_epi64x(0, b); __m128i y = _mm_set1_epi8(0xff); __m128i z = _mm_clmulepi64_si128(x, y, 0); return (_mm_cvtsi128_si64(z) >> 7) & 1; } 0000000000001280 <parity_clmul>: 1280:40 0f b6 ff movzx edi,dil 1284:c5 f1 76 c9 vpcmpeqd xmm1,xmm1,xmm1 1288:c4 e1 f9 6e c7 vmovq xmm0,rdi 128d:c4 e3 79 44 c1 00 vpclmullqlqdq xmm0,xmm0,xmm1 1293:c4 e1 f9 7e c0 vmovq rax,xmm0 1298:48 c1 f8 07 sar rax,0x7 129c:83 e0 01 and eax,0x1 129f:c3 ret $ cc -O3 parity.c -march=native -DCLMUL && time ./a.out real0m0.424s user0m0.424s sys0m0.000s FWIW, while looking up whether there were clmul instructions for lower bitsizes (32, 16, 8) I found a comment mentioning the use of a lookup table for 4-bit parity. Applying it to the D&C algorithm made it a bit faster. int parity_divide_and_conquer_lut(unsigned char b) { b = (b & 15) ^ (b >> 4); return (0x6996U >> b) & 1; 00000000000011f0 <parity_divide_and_conquer_lut>: 11f0:89 f9 mov ecx,edi 11f2:83 e7 0f and edi,0xf 11f5:b8 96 69 00 00 mov eax,0x6996 11fa:c0 e9 04 shr cl,0x4 11fd:31 f9 xor ecx,edi 11ff:d3 e8 shr eax,cl 1201:83 e0 01 and eax,0x1 1204:c3 ret $ cc -O3 parity.c -DDIVIDE_AND_CONQUER_LUT && time ./a.out real0m0.140s user0m0.140s sys0m0.000s
Dr. X · 2024-01-22 16:18
>>38171 Alright you nerd-sniped me. I'm the guy that wrote the article and now this is piquing my interest >5800X You support GFNI instructions. I wrote a couple posts about this: https://wunkolo.github.io/post/2020/11/gf2p8affineqb-bit-reversal/ https://wunkolo.github.io/post/2020/11/gf2p8affineqb-int8-shifting/ A byte-parity calculation could just be: uint8_t ByteParity(uint8_t Byte) { const __m128i Parity = _mm_gf2p8affine_epi64_epi8( _mm_set1_epi8(Byte), _mm_set1_epi64x( // Bit 0 is the XOR of all bits in the byte // V 0b11111111'00000000'00000000'00000000'00000000'00000000'00000000'00000000 ), 0x0 ); return static_cast<uint8_t>(_mm_cvtsi128_si32(Parity)); } ByteParity(unsigned char): vpbroadcastb xmm0, edi vgf2p8affineqb xmm0, xmm0, XMMWORD PTR .LC0[rip], 0 vmovd eax, xmm0 ret This might be slow due to some XMM register movement, but you could do up to 16(SSE), 32(AVX), or 64(AVX512)-bytes of byte-parities at the same time if you wanted to.
setq · 2024-06-17 13:25
I was bored and wondered about the (likely) fastest algorithm for getting the sign of a float32 I came up with this: float mysign(float x) { unsigned int y = *(unsigned int *)&x; if ((y & 0x7fffffff) != 0) y |= 0x3f800000; y &= 0xbf800000; return *(float *)&y; } Which compiles down to (gcc -O3): mysign: 50:66 0f 7e c0 movd eax,xmm0 54:89 c2 mov edx,eax 56:81 ca 00 00 80 3f or edx,0x3f800000 5c:a9 ff ff ff 7f test eax,0x7fffffff 61:0f 45 c2 cmovne eax,edx 64:25 00 00 80 bf and eax,0xbf800000 69:66 0f 6e c0 movd xmm0,eax 6d:c3 ret It checks if the number is 0 (exponent and mantissa both 0). If not, it sets the exponent to 0 (= 127-127). It also sets the mantissa to 0. With exponent >0 the value is 1, with exponent =0 the value is 0. The sign is preserved, so you get -1.0, 1.0, -0.0 or 0.0. I haven't tested it extensively, but it should work for all non-NaN numbers.
J.C. Denton · 2024-06-17 14:44
>>31171 >linking to amazon Absolutely jewish https://libgen.is/book/index.php?md5=34DCB990FEBE54887C83E5F9534F1957