Jump to content
SamC

(Probably) really simple JS question

Recommended Posts

Hi everyone, got a random JS question. Could anyone explain to me how this line works?

var randomNum = ((Math.random() * 2 | 0) + 1) - 1;

It assigns either 0 or 1 to randomNum. What I don't get is the bitwise operator. I mean I get it compares two binary numbers, and two 0s is 0, 1 and 0 is 1, 1 and 1 is 1 etc.. but what happens with a decimal here?

// Let's say Math.random() gives us 0.12005029823663982
var randomNum = ((Math.random() * 2 | 0) + 1) - 1;
// So...
((0.12005029823663982 * 2) | 0) + 1) - 1;
//...
((0.24010059647) | 0) + 1) - 1;
// Now what?

Do you even need the bit at the end?

 + 1) - 1;

From what I've read (and tested), the output number from the between the first set of parenthesis alone will be 0 or 1.

Not really sure what's going on. Any advice would be great, thanks.

Share this post


Link to post
Share on other sites

I guess the or operator | enforces a type conversion from float (return of Math.random) into integer.

So left part of the or is intended to ranges from [0...2[ (not containing 2.0, since Math.random ranges [0..1[) and the result of the or is either 0 or 1.

Not sure about the +1 and -1 though...

Share this post


Link to post
Share on other sites

Prettier converts you initial code

var randomNum = ((Math.random() * 2 | 0) + 1) - 1;

to

var randomNum = ((Math.random() * 2) | 0) + 1 - 1

and that is

var randomNumPretty = (Math.random() * 2) | 0

because 1 - 1 is 0 and there is no point in adding 0.

So that means your variable is a random number between 0 and 1 (inclusive of 0, but not 1)  multiplied by 2 or it is 0 if the result of Math.random is 0.

Interesting what Prettier is doing there. Why does Prettier do that though?
Does this answer your question?

Share this post


Link to post
Share on other sites

Possibly because multiplication goes over OR since you have Math.random() times 2 OR 0, saying that since there will never be the case of running the OR 2 part behind Math.random it puts Math.random() times 2 in brackets and evaluates that first. Then it just leaves the OR 0 part as well as the + 1 - 1 part. We can then take the + 1 - 1 part away and are left with just Math.random times 2 OR 0.

Share this post


Link to post
Share on other sites

The logical operator or still converts the result from the multiplication to be integer, kind of Math.floor(Math.random()*2).

To make confusion perfect, result should be same using double bit complement like this:

var randomNum = ~~(Math.random() * 2);

Result is either 0 or 1 with 50/50 probability.

More info about rounding/truncating here:

https://stackoverflow.com/questions/596467/how-do-i-convert-a-float-number-to-a-whole-number-in-javascript

Share this post


Link to post
Share on other sites

Ok, now at least I understand it better. The | 0 part of it checks if math.random() is 0 or something else, if it is 0 it leaves it as that and if it is more than 0 it will make it become 1. Good to learn this.

But just like you say, that + 1 - 1 part of it is just confusing or poorly written code.
Perhaps this is/was there put on purpose to see how Sam would handle explaining it?
If you don't mind Sam, is there more context to this or just a line of random code?

Share this post


Link to post
Share on other sites
8 hours ago, happywire said:

The | 0 part of it checks if math.random() is 0 or something else, if it is 0 it leaves it as

sorry: No!

Any logical operation like OR '|', AND '&', EXOR '^', Bitwise complement '~' or even shift can only be processed on integers. Using one of these operators enforce JavaScript to typecast any other type into integer. If one of the operators is a float (like return value of Math.random), then decimals are just truncated (a 1.99999 gets 1), if its a string, JS tries to convert it into an integer first.

The logical OR (in contrast to the boolean OR '||') is executed in any case. Even if '| 0' does not seem to make any sense, its just there to enforce the type conversion, so the result is an integer.

Using a logical operator in contrast to Math.floor basically optimizes for performance (no method call).

And if you need a true boolean result and love obfuscated code, try this:

var randomNum = !!~~(Math.random() * 2);

Maybe this snippet helps to clarify it:

	var i, cntYes=0, cntNo=0;
	for(i=0; i<10000; ++i)
		{
		var YesIfTrue = !!~~(Math.random() * 2);	// Boolean result 50/50
		cntYes += YesIfTrue;	// Count only true results
		cntNo += !YesIfTrue;	// Count only not true results
		}
	console.log('Yes: '+cntYes+' No: '+cntNo);

 

  • Like 3

Share this post


Link to post
Share on other sites

Thank you all for this little lesson 🙂 

b13yM8M.png

 

  • Like 2

Share this post


Link to post
Share on other sites

Ha, thx heaps for the detailed reply. As you can see I understood it wrong and thank you for getting back about it. Much appreciated!

So, I am just learning JS basics (doing this course at the moment udemyDOTcom/javascript-bootcamp-2016/ ), and to clarify/repeat, what is happening here is called type coercion as explained here for example https://eloquentjavascript.net/01_values.html#h_AY+YGu6qyM , right?

Integer is a full number, so no comma behind it, in this case either 0 or 1.

Since Math.random outputs floating numbers, meaning with comma and lots of numbers behind the comma, then those numbers, the decimals, are just cut off, truncated, like you say.

10 minutes ago, Autofahrn said:

The logical OR (in contrast to the boolean OR '||') is executed in any case. Even if '| 0' does not seem to make any sense, its just there to enforce the type conversion, so the result is an integer. 

Using a logical operator in contrast to Math.floor basically optimizes for performance (no method call).

Then you say this and I think this is highly important to understand because no method is called. So instead of converting the floating result with Math.floor one can just use the logical operator to get an integer, right?

The snippet shows how many times from 10'000 runs Math.random() outputs a true integer without type coercion and how many times it outputs a floating number, right?

  • Like 2

Share this post


Link to post
Share on other sites
Posted (edited)

Wow, there's some really great replies here, thanks everyone 🙂

For clarity, the context is 'this' (quite literally): http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/

That line came from this section on the page:

3. Fix this when method is assigned to a variable

So what I've read previously in this thread and about bitwise OR, is this what happens?

// Let's forget about the + 1 -1 stuff for now
// Math random returns a float between 0-1 (not inclusive of 1)
var randomNum = ((Math.random() * 2 | 0);
// Once this float is multiplied by two (keeping the decimal places shorter)
var randomNum = (1.6 | 0);
// After forced type coercion to int and trucation (because of bitwise OR)
var randomNum = (1 | 0);
// Binary values are compared
// Bitwise OR: 1 compared to 0 = 1
var randomNum = 1;

// OR
var randomNum = (0.7 | 0)
// After forced type coercion to int and trucation (because of bitwise OR)
var randomNum = (0 | 0);
// Binary values are compared
// Bitwise OR: 0 compared to 0 = 0
var randomNum = 0;

Just did a super quick demo to try and solidify this in my head, if you don't use vscode and quokka plugin, try it, it's awesome 😛 

581765625_Screenshot2019-03-14at09_48_30.thumb.png.39c44a28b2143d40da5bb963c8ea0cbc.png

@happywire if you're just starting out with JS, I can highly recommend this course:

https://www.udemy.com/understand-javascript/

Anthony is an incredible teacher. This isn't just your usual "make a project with me" i.e. just code along, finish the project, learn next to nothing and not be able to implement anything you've learned in different scenarios kind of course. This is much more about the theory of how JS works.

Edited by SamC
Fixed typo in code snippet
  • Like 2

Share this post


Link to post
Share on other sites
Posted (edited)

@SamC: Exactly

@happywire: You are completely right except the last point:

1 hour ago, happywire said:

The snippet shows how many times from 10'000 runs Math.random() outputs a true integer without type coercion and how many times it outputs a floating number, right?

The snippet performs the boolean evaluation of Math.random() some thousend of times:

var YesIfTrue = !!~~(Math.random() * 2);	// Boolean result 50/50

You either get a boolean true or boolean false for each iteration.

YesIfTrue is set, if the truncation of (Math.random() * 2) is equal or above 1.0 (or Math.random() returns a value equal or above 0.5).

The less obfuscated version of that line would be:

var YesIfTrue = Math.random() >= 0.5;

Which can be unrolled to:

var YesIfTrue;
if(Math.random() >= 0.5)
	YesIfTrue = true;
else
	YesIfTrue = false;

which is what actually happens behind the scenese: A numerical comparison on two floats along with a "jump" to either assignment statement.

While there still is some "jumps are bad" in many minds, that's not necessarily true for JavaScript. In fact the version using the comparison may effectively execute faster than the obfuscated "true boolean" version, but the unrolled version is slowest (10Mio iterations, Firefox 65):

True Boolean: Yes: 4999123 No: 5000877 in 54ms
Logical: Yes: 9997660 No: 10002340 in 51ms
Comparison: Yes: 14996131 No: 15003869 in 39ms
Unrolled: Yes: 19995907 No: 20004093 in 98ms
Floor: Yes: 24996634 No: 25003366 in 57ms
	var i, cntYes=0, cntNo=0;
	var tStart, tEnd;
	
	tStart = window.performance.now();
	for(i=0; i<10000000; ++i)
		{
		var YesIfTrue = !!~~(Math.random() * 2);
		cntYes += YesIfTrue;
		cntNo += !YesIfTrue;
		}
	tEnd = window.performance.now();
	console.log('True Boolean: Yes: '+cntYes+' No: '+cntNo+' in '+(tEnd-tStart)+'ms');

	tStart = window.performance.now();
	for(i=0; i<10000000; ++i)
		{
		var YesIfTrue = ~~(Math.random() * 2);
		cntYes += YesIfTrue;
		cntNo += !YesIfTrue;
		}
	tEnd = window.performance.now();
	console.log('Logical: Yes: '+cntYes+' No: '+cntNo+' in '+(tEnd-tStart)+'ms');

	tStart = window.performance.now();
	for(i=0; i<10000000; ++i)
		{
		var YesIfTrue = Math.random() >= 0.5;
		cntYes += YesIfTrue;
		cntNo += !YesIfTrue;
		}
	tEnd = window.performance.now();
	console.log('Comparison: Yes: '+cntYes+' No: '+cntNo+' in '+(tEnd-tStart)+'ms');

	tStart = window.performance.now();
	for(i=0; i<10000000; ++i)
		{
		var YesIfTrue;
		if(Math.random() >= 0.5)
			YesIfTrue = true;
		else
			YesIfTrue = false;
		cntYes += YesIfTrue;
		cntNo += !YesIfTrue;
		}
	tEnd = window.performance.now();
	console.log('Unrolled: Yes: '+cntYes+' No: '+cntNo+' in '+(tEnd-tStart)+'ms');

	tStart = window.performance.now();
	for(i=0; i<10000000; ++i)
		{
		var YesIfTrue = Math.floor(Math.random() * 2);
		cntYes += YesIfTrue;
		cntNo += !YesIfTrue;
		}
	tEnd = window.performance.now();
	console.log('Floor: Yes: '+cntYes+' No: '+cntNo+' in '+(tEnd-tStart)+'ms');

 

Edited by Autofahrn
Mentioned numbers are taken from Firefox 65
  • Like 1
  • Thanks 2

Share this post


Link to post
Share on other sites

I get different results:

NkXXPrF.png

So for me the logical was the fastest 😮 

Second try:

OWCqulR.png

  • Like 1

Share this post


Link to post
Share on other sites
2 minutes ago, bernhard said:

I get different results: 

The thread finally switched into Browser Benchmarking now. My numbers were taken from Firefox, which returns now as an integer only. On Vivaldi (Chromium) this is a float and execution times are generally longer (same machine) and Logical indeed is fastest:

True Boolean: Yes: 5001949 No: 4998051 in 201.30000000062864ms
Logical: Yes: 10001446 No: 9998554 in 173.00000000250293ms
Comparison: Yes: 15001053 No: 14998947 in 177.5999999990745ms
Unrolled: Yes: 20000397 No: 19999603 in 240.49999999988358ms
Floor: Yes: 24999139 No: 25000861 in 186.0999999989872ms

So watch your step when optimizing for performance in JS.

  • Like 1

Share this post


Link to post
Share on other sites

Edge (still same machine):

True Boolean: Yes: 4998588 No: 5001412 in 3542.50002880837ms
Logical: Yes: 9999775 No: 10000225 in 3009.699778367602ms
Comparison: Yes: 14999604 No: 15000396 in 3001.5002759201707ms
Unrolled: Yes: 19998134 No: 20001866 in 3037.500176051153ms
Floor: Yes: 25002010 No: 24997990 in 4482.999860439448ms

 

  • Haha 1

Share this post


Link to post
Share on other sites

And the winner is IE11 (if larger numbers would count):

True Boolean: Yes: 5000933 No: 4999067 in 4538.500146602597ms
Logical: Yes: 10001746 No: 9998254 in 3660.800002048595ms
Comparison: Yes: 14998940 No: 15001060 in 3914.8997532723097ms
Unrolled: Yes: 19998348 No: 20001652 in 4056.1001404568105ms
Floor: Yes: 24999035 No: 25000965 in 5620.100176307227ms

should reset Yes&No counts somewhere, right?

Share this post


Link to post
Share on other sites
12 hours ago, SamC said:

@happywire if you're just starting out with JS, I can highly recommend this course:

https://www.udemy.com/understand-javascript/

Anthony is an incredible teacher. This isn't just your usual "make a project with me" i.e. just code along, finish the project, learn next to nothing and not be able to implement anything you've learned in different scenarios kind of course. This is much more about the theory of how JS works.

Yeah, someone else also did highly recommend to do that course. Have been watching the first couple hours through this site http://learnwebdev.net/ and had so many concepts suddenly make a lot of sense. Window object, this, execution context, lexical scope. Just the first 2h of that cleared up a lot of what I was missing all this time.

  • Like 1

Share this post


Link to post
Share on other sites
11 hours ago, happywire said:

Yeah, someone else also did highly recommend to do that course. Have been watching the first couple hours through this site http://learnwebdev.net/ and had so many concepts suddenly make a lot of sense. Window object, this, execution context, lexical scope. Just the first 2h of that cleared up a lot of what I was missing all this time.

Nice one :) go get the full course, it's so worth it!

Share this post


Link to post
Share on other sites

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...