5 ms·
JavaScript: Double Bitwise Not (~~) (2010)
- s4i 3y ago> it’s faster and takes up less characters. It’s not quite as readable though, but I’m hoping that ~~ will slowly become a very well-known technique in the JS arena, so we can all use it without fearing accusations. Yeah, no thanks. I'll take well named functions over these kind of syntax tricks any day.
- CookiesOnMyDesk 3y agoDo you use a well named function like and(), or do you use the double ampersand (&&) syntax trick? What about a not() function, or the exclamation mark syntax trick (!)? :) Every use of operators can be considered a syntax trick until it becomes common, which is what the author hopes will happen to ~~
- golergka 3y agoThis operator is not currently common for intended audience of my code. And also, it's intended meaning is so much less often useful than meaning of other operators, that I don't think it ever becomes common.
- maweki 3y ago> Every use of operators can be considered a syntax trick That's blatantly false for strict languages, like JavaScript. For a function, all arguments are evaluated before the function is evaluated. For operators this is not true. On the and-case, the second argument is only evaluated if the first one evaluates to something true-ish. You can only use this supposed and()-function, if you pass closures instead of values and not every language has those.
- iforgotpassword 3y agoThen we should consider these reserved keywords and run a transpiler on our code that converts those to the unholy && and || syntax that makes the code really hard to read. You shouldn't expect every junior programmer to invest the necessary time to learn the meaning of all this archaic symbol salad. While at it we should also remove curly braces and go with Basic's if/end if, function/end function and so on.
- cristeigabriel 3y ago`&&` and `||` are very well defined (for the better part, there's languages where `||` does more than just Logical OR.), this double-not hack is just a hack, and can be achieved in many more ways. Stop trying to be obfuscatingly clever, the right time and place for that is outside of professional environments. You don't go around applying tricks from Hacker's Delight without explaining them, do you?
- mablopoule 3y agoBut using the '&&' operator is common enough, while using something like the Javascript void operator would not be, and so would need in a shared codebase a reason for usage other than to try to look smart.
- s4i 3y agoWell, surely that comparison is a bit unfair. :) In my books, using `~~` is closer to shit like abusing `[]+[]` to get an empty string, than using `&&` which is its own separate, widely used operator in the language. You could maybe compare `~~` to something like `if (!someString)` but, again, that's a widely used and way more easily understood pattern.
- lopis 3y agoNot just widely used in this language but in others, and the meaning of & is clear even in English.
- benj111 3y ago&& is a syntax element whereas ~~ is 2. Further you're using a side effect of ~ to get rounding. It isn't being used for its intended purpose. I get your point and to some extent agree with it. It's more in the class of While(foo++ = bar++) You can do it, but you're to some extent abusing constructs. If it becomes widely used then it's acceptable, but if not it deemed as bad code. Or clever, depending on your world view.
- darrenf 3y agoOf course in Perl, a language not exactly immune from "syntax tricks", `and` doesn't always behave the same as `&&` :) $ perl -E 'say 1,2,3,4 and 5; say 1,2,3,4 && 5' 1234 1235
- tyingq 3y agoNot defending it as sensible, but that difference in precedence is deliberate...not an oversight or side effect.
- cristeigabriel 3y agoThis is not the same at all. What OP does is a *hack* to convert some arbitrary value to a number. This should be very explicit, to avoid having 9999999999999999999999999999999999 and 1 more way to do the same thing.
- fallingknife 3y agoThe issue is that it's a JS only syntax trick. Things like x || y, or even !!x work in most languages, so would be readable to someone who has never used JS before and is trying to read your code base. ~~x would be int32(x) or int(x) in most other languages, so it's not going to be something that's universally learned. Also, there are multiple ways to convert to int (~~ is equivalent to truncate). Why leave it ambiguous instead of using Math.trunc / Math.floor / Math.ceil?
- ahofmann 3y agoI would hate reading code that uses stuff like this. Give things readable names, please! I'm a dumb programmer, don't make my job even harder. Edit: I just realized that the post is from 2010. This makes the post even more horrible. In 2010 you had to deal with internet explorer and other very ugly compatibility stuff on the client side. Javascript was an absolute pain and if someone would write code like this on the frontend, I would run as far away as I could.
- BigJono 3y agoNothing is more unreadable than "readable" names. I absolutely detest working with code that uses for example stuff like ImmutableJS, that turns every property access into `.get('')` instead of a simple `.` operator. I wouldn't use this for no reason or just to be fancy, but if you're working in a context that legitimately requires integer division all over the place, you should use syntax for it if it's available. I'd be annoyed if I had to work with something maths-y that has gigantic lines full of Math.trunc() everywhere.
- moritzwarhier 3y agoI don't think having to use .get() for property access is equatable to "readable names". It is also not the reason for this in the library you mentioned AFAIK, although I haven't used it so far. I think the reason for the .get() is that it is not achievable to have deep immutability in JS with regular property access (Object.freeze is not "deep" either). That aside, I agree it's not ergonomic, but it has nothing to do with readable names. And converting a string to an integer is not possible with a native JS operator intended for this purpose, so I'd prefer Math.trunc any day. Regarding the immutable.js thing, the Records/Tuples TC39 proposal aims for a syntax and runtime addition to allow immutable objects with regular dot property access.
- lakpan 3y agoTo be fair early JS development was full of “one weird tricks” that made code unreadable. Thankfully jslint came along and started cleaning up this sort of stuff. Now ESLint and its plugins target a lot more unreadable code, like the `no-array-reduce` rule.
- bmo-at 3y agoI actually think this is a bad idea. Sure, it looks cool and is interesting. I always prefer making code explicit, as it keeps the code more readable.
- Asraelite 3y agoI thought `| 0` was the de facto way to do this in JavaScript. It was even made part of the asm.js standard iirc. Is there a difference between `| 0` and `~~`?
- lifthrasiir 3y agoNo [1]. Their difference only matters for code golfing (because `~~` binds much tighter than `|0`). [1] https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseNOT https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseNOT vs. https://tc39.es/ecma262/#sec-numberbitwiseop https://tc39.es/ecma262/#sec-numberbitwiseop
- krackers 3y agoIf implementation of ~~ is supposedly faster than Math.floor() for positives, shouldn't the JIT pick up on this with a guard/deopt block?
- lifthrasiir 3y agoBecause `Math.floor` is correct for all numbers but `~~` will be incorrect outside of int32 ranges and non-numbers (`Math.floor("asdf")` is NaN but `~~"asdf"` is 0). Basically it is a shorthand for limited cases, but it is so convenient for those cases that I think it is a worthy trick to know with caveats. ADDED: As per performance, there is a difference, but it is hard to quantify exactly. `Math.floor` with large enough argument won't fit in any integer type, so JIT can't assume that. But it is mostly the case that floating point arithmetics are as fast as integer arithmetics in modern architectures, so unless we completely know how floor outputs are to be used, that argument can't be used to prefer either `Math.floor` or `~~`.
- mpixel 3y agoBitwise operators only work on the first 32-bits, so it isn't as easy as `instead of doing the Math.floor operations, check for edge cases and just the same operation as ~~`
- oytis 3y agoThis is awful.
- anon23432343 3y ago1) Add the year! This is ~~2010! Is this even still valid today? Is it really faster in 2023? 2) this "trick" has problems if you don't understand the caveats it has. 3) This kind of micro optimisation isn't usually were your code is slow. Any css shift will be more expensive then this. 4) IF you want to use this "trick" then make sure everybody who will need to read your code really understands what you are doing here. Dont be that smart pants coder were no body else then you can read your code! This does not make you a good programer!
- shutupnerd0000 3y agoThan
- lifthrasiir 3y agoI've mentioned in other comments, but it should be noted that `~~x` or `x | 0` is not same as `Math.floor(x)`. It is rather a shorthand for `Math.trunc(x)` that is almost correct for x between -2^31 to 2^31 - 1. (Almost, because `Math.trunc(-0.5)` should return -0 while `~~(-0.5)` is +0.) There is also a variant `x >>> 0` which works for x between 0 to 2^32 - 1. Essentially this trick exploits how JS initially implemented bitwise operations while having no integer-only types. It is safe to use---ECMAScript is unlikely to change bitwise operators in future versions---but only useful for limited cases. Those limited cases, like low-level emulations, happen to be cases where such tricks are very convenient though. As long as you don't naively convert `Math.floor(x)` with `x | 0` or `x >>> 0` I'm fine with them, but this article seems to encourgate that.
- nrabulinski 3y agoI wanted to comment the same thing but then I realized that this post predates Math.trunc by 4 years, hence it makes sense why it’s described in terms of floor and ceil instead :-)
- falcor84 3y agoNice, I don't recall I ever saw `x >>> 0`, bit it reminds me of the infamous "x goes to zero" operator `x --> 0` [0] and its even more expressive sibling, the "x slides to zero" operator [1]. [0] https://stackoverflow.com/q/1642028/493553 https://stackoverflow.com/q/1642028/493553 [1] https://stackoverflow.com/a/8909176/493553 https://stackoverflow.com/a/8909176/493553
- conaclos 3y agoI already used `x >>> 0` to ensure that the integer is a unsigned integer on 32bits. `x|0` ensures that the integer is signed on 32bits.
- eloisius 3y ago> ~~‘s flooring capabilities make it a better alternative to Math.floor if you know you’re dealing with positives — it’s faster and takes up less characters. Good lord. This takes me back to 2012 when all the cool kids were putting bangs before functions so that you didn’t have to use semicolons anywhere. I’ve basically decided as a career goal that I’ll never write JavaScript for money again, so it’s no skin off my nose, but why does the JavaScript world constantly go through these bizarre contortions of misusing syntax for vague “performance” gains at the expense of communicating intent?
- bruce343434 3y agoI think the post makes a good argument in favor of it. It's shorter, meaning it's cleaner, less visual noise. You just have to learn what it does. That's part of the job. That's what it means to be a skilled developer. It's absolutely not hard to remember that this is a "round to zero, or make it zero if it's something weird", and that functionality seems very useful.
- eloisius 3y agoIt's not "less noise" just because it's shorter. Code golf is extremely hard to read exactly because it's so terse. If Math.ceil is making your code hard to read, there's probably something else going on. Being a good coder means elegantly expressing intent with code, not memorizing hacks. As for performance, if misusing an operator like this is actually faster, then that means there should be an easy patch to V8 that would make it faster without miscommunicating intent. Regardless, just like readability, if Math.ceil even registered as anything in a performance profile you must be doing something very weird. Like implementing Torch in JavaScript weird.
- bruce343434 3y agoIt's just a matter of internalizing the "intent" of ~~. And then suddenly the code is "clean" again. Just like `!!` is a socially acceptable way of casting to bool.
- 3y ago
- vanderZwan 3y agoThe slides from 2009 that the article links to are full of severely outdated advice[0]. I just gave the "parseInt" trick a try, for example, and the built-in wins or is equal in performance[1] (although I'm mildly disappointed that `parseInt` isn't significantly faster). [0] https://www.slideshare.net/madrobby/extreme-javascript-performance https://www.slideshare.net/madrobby/extreme-javascript-perfo... [1] https://jsbenchit.org/?src=00e164b878d3c009649d4f8a9de6e7f6 https://jsbenchit.org/?src=00e164b878d3c009649d4f8a9de6e7f6
- lifthrasiir 3y agoIn fact at that point JS was so slow especially in MSIE that VBScript was often faster than equivalent JS. It was a rough time.
- dmitrijbelikov 3y agolet a = 1, b = 2 ~~b // 2 ~~a // 1 ~~(b - a) // 0
- gushogg-blake 3y agoHow does this actually work? Both the post and MDN state that each bit in the 32 bit representation is inverted, resulting in: 5 => 00000000000000000000000000000101 ~5 => 11111111111111111111111111111010 But parseInt("11111111111111111111111111111010", 2) is 4,294,967,290, not -6. (Even if the first bit were interpreted as the sign, shouldn't this be -2,147,483,642?)
- gushogg-blake 3y agoOK here's how I think it works: from the wikipedia page on two's complement[1], the sign bit is the bit directly to the left of the most significant bit (not the first bit in the 32 bit representation), giving: 5 => 0101 And the only way I can get this to give -6 is if the bitwise NOT operator takes this entire representation (0101), inverts the bits, then creates a new sign bit and inverts that, giving: ~5 => 11010 [1]: https://en.wikipedia.org/wiki/Two's_complement https://en.wikipedia.org/wiki/Two's_complement
- pzmarzly 3y ago> (Even if the first bit were interpreted as the sign, shouldn't this be -2,147,483,642?) No, in two-complement (U2) encoding, negative numbers "start from the top". This lets CPUs have same addition and subtraction instructions for signed and unsigned integers, and rely on overflows to get correct value. E.g. (-1)_i8 + 2_i8 = b11111111 + b00000010 = b(1)00000001 = 1
- gushogg-blake 3y agoAh yes that makes sense. Always assumed the sign bit was just a flag.
- cristeigabriel 3y agoreminds me of `!!` which has been more common in my experience, as a way to coerce something negatable into a bool. > This is obviously a fair bit slower than ~~. I don't think the code posted before this comment is particularly fast, but are we sure this double-not is faster?
- m4rc3lv 3y agoIt's very readable to me, nice article!
- tacone 3y agoDo we really need syntactic sugar for bitwise operators in a language like Javascript? I would rather have longer keywords for that and free those tokens for more widely used use cases (as having just | VS |> for the proposed pipe operator).