3

Note: I asked this a while ago and it didn't get any answers but it did make me create an interesting fiddle, which is at the end of the post. Check the comments if you're interested. In short, trunc and imul, new additions to the standard, are there to fill a gap when it comes to treating certain types of values properly, without just turning them into 0. Try the fiddle and look at the chart, it should all be clear. Also, check the perf for an idea of the performance difference.

original post

tl;dr: Is there ever a reason to use the new Math.trunc, especially when going for performance? Would the same apply to Math.ceil? (ps: sorry if I don't make much sense, been up all night, can hardly think clearly :'( )

Earlier today I was reading up on what new ES6 stuff I can use in Firefox and Chrome. That was when I came across Math.imul (integer multiplication, 32-bit) and Math.trunc (truncates number to integer part). Math.trunc struck me as odd that it would exist, since that same truncation can simply be done with Math.imul(n, 1) where n is a floating point number. I had forgotten that the same thing can be done with bitwise operations, but after a bit of searching I found 2 SO questions that where related:

However, neither there nor anywhere else did I find information on comparative performance. So I made a quick jsperf and it benched Math.imul(Math.PI, 1) at 50 times faster than Math.trunc(Math.PI). So I thought something's up, since, if Math.imul is so much faster, why would anyone ever use Math.trunc and why add it into the spec when Math.imul can do its job?

After reading the aforementioned questions, I made this fiddle which builds a table with all the return values for various inputs to relevant functions (including the bitwise operations). As mentioned in the threads, they behave differently depending on their input - bitwise operations deal only with numbers (and treat everything as a number obviously), while the Math functions, excluding imul care about whether their input is a number or not. So, now there's some reason why we would need trunc.

I made another jsperf and it confirmed what the table showed, that imul behaves exactly like a bitwise operation. Which raises a new question: why do we have Math.imul, also part of the new spec, when we already have the widely used bitwise operators? The jsperf also showed another interesting fact: Math.floor doesn't treat its input like the bitwise functions but it's just as fast! 0_o

So I've gone from, "why have trunc" to "why have imul" to "why not do everything through floor?". In fact, if you look at the fiddle I made and uncomment certain test functions, you can emulate both ceil and trunc through floor (if you don't care about -0). I ran a silly jsperf that showed that emulating ceil through floor was just as fast, and emulating trunc through floor actually was ~10 times faster than trunc itself. Then I fixed it to check for all types of input and it now seems (on Firefox) that I get exactly what should be expected: emulating trunc and ceil through floor gives the same performance as calling them directly. However that's only if you care about getting NaN when NaN is the input and stuff like that. If you don't care about those and especially if you know all your numbers are floating point, you can get many times the performance of ceil out of floor by simply doing Math.floor(n+1).

Finally here's the jsperf. Firefox absolutely destroys Chrome on these kinds of operations (dat asm.js optimization), but the results are unclear to me. So:

  • First of all, are my tests (both the fiddle and perf) even correct?
  • Is the need to check for non-integer input that important when you're casting to an integer? (because if not, the naive ceil emulation will grant loads more performance)
  • Have I missed any other behaviors that these functions have, that would justify having trunc for instance?

It seems to me, the bitwise operators are shorter, faster and more efficient when it comes to casting to integers or even rounding (if done carefully). Is there any good reason to use something like Math.trunc other than code clarity?

edit - updated fiddle with polyfill from here -- also, someone updated the jsperf with the polyfill here

Community
  • 1
  • 1
mechalynx
  • 1,306
  • 1
  • 9
  • 24
  • Is `Math.imul` 50 times faster on *all* browsers, or just whichever you tested on? And is it faster in synthetic tests or in everyday situations? Is it properly supported by all browsers? Especially all browsers *in use* (which means even a few year old versions)? – Some programmer dude Jul 29 '14 at 07:03
  • I could be wrong but, You compare `Math.trunc` to `Math.ceil` for performance, shouldn't you use the polyfill from here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc ? – Willem D'Haeseleer Jul 29 '14 at 07:07
  • @JoachimPileborg No, `Math.imul` was 50x when specifically comparing it to `Math.trunc` with `Math.PI` as input on Firefox. I only tested Chrome after I fixed my jsperf. The whole ~50 times thing is an artifact of my bad perf crafting. Opera seems to perform and behave like Chrome in pretty much everything so I only ran the fiddle on it. These are ES6 features I'm testing, but the bitwise operators, `ceil` and `floor` should be widely supported. – mechalynx Jul 29 '14 at 07:07
  • @WillemD'haeseleer I'm only measuring Firefox and the Blink browsers. I'm testing the whole thing because I'm writing an extension for both, so I'm ignoring IE and Safari, though I would be interested in how they perform on the bitwise operations and how they implement the older `Math` functions. – mechalynx Jul 29 '14 at 07:09
  • Sorry but, I don't feel like that is an answer to my question, I could possibly be misunderstanding your post a bit, but I fail to see how your comment reply is even related to my question. – Willem D'Haeseleer Jul 29 '14 at 07:10
  • @WillemD'haeseleer sorry, you're right. I just can't think clearly now. I didn't add the polyfill originally since MDN says Chrome supports `trunc` but it clearly does not. I'll add it and post the new version of the fiddle and perf. – mechalynx Jul 29 '14 at 07:13
  • Math.imul is needed for multiplying 32 bit integers. It's not primarily intended for casting. – curiousdannii Sep 15 '14 at 07:12
  • @curiousdannii That's obvious, but what's your argument? That despite the performance hit of the _intended way_ we shouldn't examine alternatives? If you know your data, know what you need from it, validate it and understand the different casting methods, it can be an order of magnitude faster to use `imul` or the bitwise casts. – mechalynx Sep 15 '14 at 10:36
  • That's fine, and I was surprised to see it operating so much faster. I was just addressing this: "Which raises a new question: why do we have Math.imul, also part of the new spec, when we already have the widely used bitwise operators?" Math.imul is there to fill a gap. – curiousdannii Sep 15 '14 at 10:58
  • @curiousdannii I know. If you see the original question and the fiddle, it all started with "why use `.trunc`" and turned into a whole other thing by the end. I think it's safe to say that both `trunc` and `imul` have a place based on the tables but it's nice to know when not to waste your cpu if you don't need the careful value handling of `trunc` or `imul`. It could avoid a few nasty bugs too :P – mechalynx Sep 15 '14 at 11:11
  • edited the question for those who might come across it. – mechalynx Sep 15 '14 at 11:16

0 Answers0