Low Level Bit Hacks You Absolutely Must Know
catonmat.net
catonmat.net
12 years later not a single person I have hired could correctly answer that question[1] and most of them (almost all) have been great productive software developers. So "absolutely must know" is entirely dependent on the type of software you write.
[1] I don't ask it in the interview, I ask it after they are hired and have been with us a few weeks just for fun.
Since ISO C99, C's division operator has been required to truncate toward zero. The % operator harmonizes with that and so it yields negative remainders south of zero.
In C90, it was implementation-defined, IIRC.
You really just want to test for zero/nonzero, rather than x % 2 == 1.
>>> -4%2
0
>>> -8%2
0
>>> -9%2
1 ?- x is -4 mod 2.
X = 0.
?- X is -8 mod 2.
X = 0.
?- X is -9 mod 2.
X = 1.
Using declarative integer arithmetic, we can even express very generally that a number X is a multiple of 2: ?- X #= 2*_.
This is true iff there is any integer (indicated by an anonymous variable _) that, multiplied by 2, yields X. It works for positive and negative integers.For example:
?- -4 #= 2*_.
true.
?- -9 #= 2*_.
false.
A very nice reference on the general topic of bitwise operations is Donald Knuth's Bitwise Tricks and Techniques in Volume 4 of The Art of Computer Programming.Python also uses floor division, and the corresponding "sign-of-modulo matches sign-of-divisor", for floating-point numbers. The blog post claims that this can be problematic in certain cases (compared to C's "sign-of-modulo matches sign-of-dividend"), but does not give any concrete examples.
[1] http://python-history.blogspot.com.au/2010/08/why-pythons-in...
The sane language here Common Lisp: it does it in all the ways rather than picking one approach with some hand-waving justification about why it's the good choice.
It has truncate, round, ceiling and floor functions that all return a quotient and remainder.
There are also mod and rem functions that calculate just the remainder from a floor and truncate, respectively.
(-123 % 2 == 1).Dump();
Prints False.% in C# is the remainder operator, not canonical modulus.
>> -2 % 2
-0
>> -3 % 2
-1
Edit: well, at least in Firefox JS. I have no idea if that's actually standard-mandated behaviour or if it's an implementation quirk. The -0 is especially weird (I had written odd but...)Yeah, if they can pass a fizzbuzz question they can probably get that. At which point it behooves the interviewer to explain why (x % 2) == 0 to find an even number is a better test...
Most web developers (front and backend) will never use bitwise and many don't have college degrees so they didn't use it in college. It is actually quite understandable that they won't.
I've used bitwise here and there in backend code in my 20+ years of web dev but never in a case where it couldn't be rewritten as slightly less efficient but more readable code.
Some would call it math.
But I guess that shows you the state of our art.
In most cases you won't see these alone. They will be surrounded by tens of thousands of other lines of code. And where there is one "hack" there are usually others. And at some point you end up with a bug somewhere because one of these smart little hacks is failing. You don't know which one.
This does create unnecessary problems for maintenance in the future. So use it, but use it only when absolutely necessary. Not because you think the code will run faster.
It's also important to note that many things people think make the code faster, don't. Like vector vs. linked list access. Intuitively the linked list seems much simpler for inserting an element. But in almost all cases the vector is faster, because of cache locality.
So what I'm saying is. Do things that make the code probably faster like using a vector, if they don't cloud understanding. If they do, leave them out until you have a reason to use them. The only reason to use micro-optimizations like these is if you measured execution time and know there is a performance bug in this place in the code which your "hack" could resolve.
No measurement -> no optimization. Because the performance problems are almost always in different places than the average developer assumes.
And then add a comment explaining what you did to a 5 year old.
Only if you make certain assumptions about how the integer is represented, I reckon.
test eax, 1
; use result is_even(int):
mov eax, edi
not eax
and eax, 1
ret
I am far from a x86 expert, but I assume this version avoids contention on the flags registers and increases pipelining.edit: my mistake, icc and clang produce the equivalent sequence "not edi; and edi, 1; mov eax, edi; ret".
int isEven(int number)
{
return number & 1;
}
gcc 7.3, -O1, -O2 and -O3 (identical codegen for all optimization levels): isEven(int):
mov eax, edi
and eax, 1
retAnyways, you still prove my point. All those three are pretty close.
-O1 sete instruction is very slightly unoptimal, but even it is just a fractional clock cycle.
Several of the examples in the article are the basic ones, though: test, set, clear or toggle a bit. You might not remember the trick for finding the least significant 1 bit, but it's not as if testing whether a number is odd or even is some sort of arcane knowledge. Anyone who has to resort to Google or Stack Overflow for something so simple is going to struggle in large parts of the programming industry.
More experimentation revealed it was only negative numbers that were being messed up (the original tests passed with positive numbers).
Further bitwise investigation and manual twos-complement calculation, and I discovered the problem to be a mismatch in fixed point precision, where negative numbers weren't being sign extended correctly.
It's not every day you rely on your binary calculation skills, but it does come up.
That's like saying someone is incompetent because their entire career they have only spoken English and someone writes "el al." to them and they don't know what it means. Sure english is based on latin. Do most english speakers know enough latin to know what "et al." means? Probably. Does that make the person incompetent at their job where they only need to speak English? Definitely not.
A large portion of web developers don't have any formal education and the bootcamps and websites don't teach this stuff. As someone who has a computer science degree it seems absurd, one might even say it sounds like incompetence, but it is actually completely understandable.
Most of them can come up with the modulus solution but the bitwise operator solution is less common.
To the other comment in reply to you that talks about Amazon... yeah, that is Amazon... I work for a company with 1/100,000,000 the budget of Amazon (wild probably inaccurate guess). We don't get the people Amazon attracts. We get the people who all those other companies turned down. But you know what? With extremely few exceptions all of them have been great software developers despite not knowing how to tell if a number is even or odd using bitwise.
In the c/c++ game code bases I have worked on, it is very common to have statuses stored as a uint with each bit representing something and very common to set, unset, toggle, reverse bits etc.
I actually read the first part of the article thinking these aren't hacks these are just things people use day in day out. But the later section about setting last 0 or 1 bit etc suddenly because things I wouldn't typically see in the wild.
state = state & ~BIT7;
than to mistype state = state.without(BIT7);Plus there are performance concerns. I remember one graduate introducing a c++ bit field class and tried to replace some core functionality with it and killed performance. Developers prefer to debug non optimized builds, and non optimized builds can end up running slow when you replace simple one liners with classes and inline methods that don't get optimized in debug builds.
[1]I hope this doesn't sound aggressive as it really isn't intended to. It is just everyone is so used to seeing bitwise operations it is as straight forwards as reading "set the third bit" or "toggle the last bit" when we see the C code.
num = num.plus(x);
might be less likely to mistype than
num = num + x;
but nobody would ever do that. Once you've done bitwise operators a few times they become just as comfortable and natural as addition or subtraction.
Like, I get it when people say "most of the stuff you learn getting a CS degree has very limited usefulness day to day". Sure, you're not going to be implementing red-black trees or skip lists in your day-to-day, but you can't possibly claim that having a basic idea of how the bitwise operators work (or the details on how numbers are represented in binary) is in the same category.
Given that most programming jobs are web development jobs (I think, I don't have numbers to back that up), what makes using bit-masks important? That is, why would someone working on a web application need to know how to use them?
My biggest reasoning for desiring that skill is debugging. If you build a system and it doesn't work right for whatever reason, it's good to understand why that might be. Also, having some appreciation for that stuff really helps you make good decisions (but why can't I make 200 HTTP requests while loading my page?!)
For the machine learning and infrastructure work I've done the last 5 years or so I haven't had to do even a single bitmask, shift, etc. directly. Of course we use these things liberally (e.g. hash functions) but it's hardly necessary to write the code itself very often, even in very sophisticated applications.
Javascript only has doubles so you can't even bit twiddle if you wanted - C programmers can't even imagine.
It doesn't have integers to store.
JavaScript's numbers can do everything doubles can; but if the compiler can prove that all values that a particular variable could possibly take on will fit in a smaller type, then it can use that. So a variable really can end up stored as an integer--or never stored anywhere for that matter, like if a loop gets unrolled and the counter is optimized out.
The bitwise operators behave as if integers are 32-bit two's complement. The typed arrays are also two's complement. A smart developer can write code that a smart compiler will compile to bitwise operations on actual physical 32-bit integers.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
https://v8project.blogspot.com/2017/09/elements-kinds-in-v8....
Most C programmers would be too scared to perform bitwise operations on doubles and JS doesn't add anything that makes it safer.
The problem happens when you treat numbers one way and they are treated differently elsewhere. For example -0 when undergoing a bitwise +/- check (val & (1U<<31)) will appear positive when its negative by definition.
The cast to an integer prior to the bitwise operator looses information. You can use the operators safely if you know there is no information to be lost, but it is not type checked like with C. I will say at least JavaScript guarantees twos compliment. You never know when your favourite compiler will decide to break your code in the name of a faster benchmark - correctness of existing programs be damned. Implementation defined used to mean do something sane, but I digress.
((int)dblval) & flag
would require serious justification in a C code review. In JS its all you have. Under those restrictions I'd use it as rarely as possible. But yes you can use it correctly if you try.
Edit: you obviously have to multiply by 8 to convert bytes to bits
You are definitely correct on most modern architectures which have a single operation count leading/trailing zeros that are typically no more expensive than an integer multiply. This has been latency = 3, throughput = 1 for most recent Intel arch.
I think a redo assuming modern architectures is long overdue; i.e. let's work off the basis that count leading/trailing zeros, PDEP/PEXT, etc are fast and cheap and start from there.
Bonus round: if you had a lot of these operations to do, then you still might be able to go faster on x86 on SSE2 or AVX2 by using this trick, as the penalty to pass a SIMD register full of integers to a floating point op or vice versa is just one cycle. This trick becomes obsolete yet again with AVX512, which introduces parallel count leading zeros.
Also multiply adds can be fused so if you're doing that it cab be faster to just multiply by a different number instead of bit twiddling.
I'm not sure which bit hack you're talking about that's done with a multiply or multiply-add. There's a nice use involving De Bruijin sequences for doing lg2 of a single bit that's very instructive - is that what you meant?
When you first learn about 'cool hacks' like these, you'll want to use everywhere.
Then you'll realize that they don't apply to certain data types in certain environments.
Then you'll come to realize that the rules governing which hacks to use are too complex to remember.
Then you'll want to write a program that figures it out for you.
Then you'll realize that those programs already exist, and they are called compilers.
So now you are back where you started. You have made no progress, but you have gained an important insight.
There's a reason a lot of these potentially non-portable tricks show up in high-performance computing (particularly in videogames) and in the embedded space.
Otherwise, remember the first rule of code optimization for beginners: "Don't."
EDIT: And by "they don't apply", I mean they can crash your program or even silently corrupt your data.
For games, this can mean a higher frame rate. For data processing, it can mean the data gets processed significantly faster. It's especially important for things like video encoders, where even a 1% savings in CPU time can translate directly to 1% more potential revenue for the same operating cost at a company that relies heavily on video encoding.
Yeah, saving those cycles doesn't really mean anything to a beginner, but they can be a huge savings for high performance computation applications.
union { int i; float f;} u; u.i=*i;
Now u.f contains the same bit pattern as the integer pointed to by "i" but interpreted as a float.
Compilers know how to generate code efficiently out of this.
Iirc this is implementation defined behavior, e.g. things like endianness alignment or padding, but it's not undefined behavior, whose evil effects can bubble up in unexpected parts of the program that apparently don't even touch the bad cast.
> Accessing an object by means of any other lvalue expression (other than unsigned char) is undefined behavior
https://wiki.sei.cmu.edu/confluence/display/c/EXP39-C.+Do+no...
That said, memcpy and a compiler that understand the memcpy intrinsic is probably safer; that said I have a vague memory of hitting an embedded compiler where memcpy wasn't an intrinsic
Back in my C++ days, I would much prefer pointer-cast to union-struct, because the latter was subject to alignment/padding issues you mention, while for the former, I know of no desktop architecture that could mess up a int* -> float* cast.
I'm also doubly surprised because being able to do that stuff is what I'd consider the selling point of C/C++ over higher-level languages.
Yeah, C and C++ aren't the language most people think they are, and the strict aliasing rules are the most clear manifestation of that.
The replacement is often "gcc with inline assembly" or "gcc with lots of __attribute__ extensions such as __may_alias__" or "gcc with optimizations disabled".
The need doesn't go away, despite the wishes of a language committee now dominated by compiler suppliers. From day 1, long before the language was standardized and even before the original (not ANSI) book was published, C was being used with aliasing. It was, after all, developed to support the creation of Unics (before that became UNIX).
float f;
memcpy(&f, i, sizeof f);
Don't worry about the implied overhead of the call to memcpy; the compiler will replace it with an intrinsic and emit the expected movss instruction.The rest aren't hacks, they're bit manipulations, which are crucial for those who need them, and just about useless to those who don't.
On -03 they compile to the same, both signed and unsigned.
It only ever caught my attention that writing (n & 1) is weird when some TAs looking at my code didn't understand what I was doing during my first college year.
Though if this is a hack then checking the last digit to see if a decimal number is even is also a hack :)
When your processor doesn't have built-in integer division and the corresponding compiler doesn't optimize that and you need it to work fast.
It is easy to forget how much the internet, and the WWW in particular, has changed programming.
It really hasn't. It's just that web programming gets all the attention. Those who write hardware controllers or do embedded use bit manipulation every day.
But, really, if web devs aren't familiar with this, why not? Do they not have settings to toggle in their apps?
It's kind of depressing, but I've adjusted how I ask my problem since it's not actually needed to demonstrate the skills I want to see. It doesn't take long to learn bit manipulation when it's needed, even though it seems like a foundation of computing to me.
Indistinguishable from "can't really integrate shit" without some hours of relearning.
You absolutely must know these because they're dead obvious and if you're just starting out with learning programming these are some of the first things you will/should learn, right after how computers store data.
For example, no python course for scientists is going to go on in-depth about the difference between big and small endian - it just doesn't matter when you're trying to make a simple graphical model, it's far too low level. Binary operators work and are useful in JavaScript, but how many 6 week frontend bootcamps are going to cover them?
What I'm getting to here is that I don't think you can just dismiss things like this because your coding background covered them - there's some pretty clear gaps in a lot of peoples' knowledge and educating them is likely only a good thing.
This sounds like a fast trip to the land of direct comparisons of floating point numbers.
Further info: https://randomascii.wordpress.com/2012/02/25/comparing-float...
Like a few other posters have mentioned, this is all basic bit manipulation stuff that you'd see in any perf sensitive/memory constrained codebase.
[1] https://graphics.stanford.edu/~seander/bithacks.html#RoundUp...
for (int i = 0; i << n; i++) {
...
}
(won't work for a large n)Also... dividing by 2 is (n >> 1) and multiplying by 2 is (n << 1).
I would love to see these updated for SIMD and modern architectures (which change the equation quite drastically for bit hacking with 1 uop PEXT/PDEP/LZCNT/TZCNT), but that makes for a pretty huge area.
For example, Population Count instructions exist. But may actually be microcoded to count one bit per cycle.
Vectorizing your tallies can yield log time speedups:
Faster Population Counts Using AVX2 Instructions
Many of those nifty tricks are found in production systems today.
[1] https://www.amazon.ca/Hackers-Delight-2nd-Henry-Warren/dp/03...
/k3
bh01:{*-1#(2 _vs x)&1}
bh02:{(|(2 _vs x))y}
bh03:{n:|(2 _vs x);n[y]+:1;:[x<0;-8#|n;|n]}
bh04:{n:|(2 _vs x);n[y]-:1;:[x<0;-8#|n;|n]}
bh05:{n:|x;:[n[y]=1;n[y]-:1;n[y]+:1];|n}
bh06:{n:-1#& x;x[n]-:1;x}
bh07:{n:-1#& x;m:& 8;m[n]:+1;m}
bh08:{n:(*|& x)+1;m:n _!#x;x[m]:1;x}
bh09:{n:|x;m:*&0 = n;n:& 8;n[m]:1;|n}
bh10:{n:|x;m:*&0 = n;n[m]:1;|n}
/examples:
bh01 -98
bh02[-133;5]
bh03[120;2] / 120 set 2nd bit
bh03[-120;6] / -120 set 6th bit
bh04[127;4] / 127 unset 4th bit
bh05[0 1 1 1 0 1 0 1;5]
bh06[0 1 0 1 0 1 1 1]
bh07[1 0 1 1 1 1 0 0]
bh08[0 1 0 1 0 0 0 0]
bh09[1 0 1 1 1 1 0 0]
bh10[1 0 1 1 1 1 0 0]
Appreciated the help last time e.g. use of dyad # (take), indexing into list via juxtaposition (bh02) and use of dyad ? (find). > if (x & (1<<n)) {
> n-th bit is set
Yeah, don't get a habit to do that. Instead: if ((x >> n) & 1)
if x is wider type that int or n, the result of (1<<n) may be UB, and in practice the generated code will be buggy.- On a different note: how much of mem-space bloat can be attributed to this low-level neglect? Which of course is partly caused by modern languages / programming paradigms abstracting away this low level details for sake of higher level abstractions (which only seldom break, right ;)
- honorable exception: erlang bit-level pattern matching and all the power that comes with it. Btw. did any other language/lib even try to imitate that feature?
Shifting left by n bits on a signed or unsigned binary number has the effect of multiplying it by 2^n. Shifting right by n bits on a two's complement signed binary number has the effect of dividing it by 2^n, but it always rounds down (towards negative infinity).
For the purposes of doing bit packing and other manipulations, they treated the data as an array of booleans.
But the level of this post is so low as to be next to useless, I clicked fully expecting to be amazed. The title could definitely do with an upgrade.
def point_mul(p, d):
n = p
q = None
for i in range(256):
if d & (1 << i):
if q is None:
q = n
else:
q = point_add(q, n)
n = point_add(n, n)
return q
but when i changed the loop to this : for i in reversed(format(d, 'b')):
if i == '1':
I found out it performed much better.Couldn't you also do: y = x & (~1) ? I'd think that would be slightly more efficient.
Aka. that brings 01101₂→01100₂ correctly, but 01100₂→01100₂ instead of 01100₂→01000₂.
https://codereview.stackexchange.com/questions/75517/n-queen...
Other than that, these are things that you have to do yourself. You can't call compiler.setMySeventhBit();
If you need to be able to flip and check bits, you have to do it yourself.