I'm both a JavaScript / Node n0ob....but I was recently working on a project using both. I was exploring the /node_modules/ folder and I came across a particular line of code that didn't seem to immediately make sense to me. The goal is to determine if a number is odd or not.
The specific line of code is:
return !!(~~i & 1);
My question is 'Why do we need the ~~'
(related to: How to determine if a number is odd in JavaScript which shows most of the answers using % 2)
I think I understand the individual pieces.
!! is the negation operator (twice) and will give us a true/false value from a truthy/falsey value
~~ is a way to truncate a value. 8.234 becomes 8.
& is the bitwise and operator.
But I'm still questioning if we need the ~~, and if so, why?
I've been using N..toString(2) - for example:
4.0.toString(2)
> "100"
4.1.toString(2)
> "100.0001100110011001100110011001100110011001100110011"
To try and understand what the binary representations look like. In the second example, the right-most digit is a 1, but when I plug 4.1 in as the value for i, it still correctly sees that the number is even:
!!(~~4.1 & 1)
> false
!!(4.1 & 1)
> false
I've read that Javascript uses the IEEE 754 standard, and I've also read that all of the bitwise operators being used here do some implicit converting that I don't fully understand and maybe there are considerations that I'm not seeing?
Thanks
The double ~~ is used to convert a string to an number, just like !! is used to convert a truthy/falsey value to a boolean.
var a = "4";
console.log(typeof a + " " + a); //string
console.log(typeof ~a + " " + a); //converts to number, but you get (a - 1)
console.log(typeof ~~a + " " + a); // reverses the previous operation, keeping the number type
Now, as you realized, it's not necessary in this operations because in Javascript, when you do bitwise operations on strings they're first converted to numbers, so the double tilde becomes superfluous.
What I suspect was the intention of the maker of that snippet is that you can't really classify a decimal number as odd or even, so he just cast it as a number to get rid of the decimals, which ~~ does.
Related
This question already has answers here:
Why does JavaScript handle the plus and minus operators between strings and numbers differently?
(7 answers)
Closed 5 years ago.
Why does Javascript give an output of 0 when I use the odd operator?
What is the difference between subtraction and addition with a string?
var x = 1;
console.log(x+'1') // Outputs 11
console.log(x-'1') // Outputs 0 -- but why?
So how can I do mathematical calculations?
The + operator has one of two three meanings in javascript. The first is to add numbers, the second is to concatenate strings. When you do 1 + '1' or '1' + 1 the operator will convert one operand that is not a string to a string first, because one other operand is already evaluated to be a string. The - operator on the other hand has just one purpose, which is to subtract the right operand from the left operand. This is a math operation, and so the JS engine will try to convert both operands to numbers, if they are of any other datatype.
I'm not sure though why typecasting to strings appears to have precedence over typecasting to numbers, but it obviously does.
(It seems to me the most likely that this is a pure specification decision rather than the result of other language mechanics.)
If you want to make sure that the + operator acts as an addition operator, you can explicitly cast values to a number first. Although javascript does not technically distinguish between integers and floats, two functions exist to convert other datatypes to their number equivalents: parseInt() and parseFloat() respectively:
const x = 10;
const result = x + parseInt('1'); // 11
const y = 5;
const result2 = y + parseFloat('1.5'); // 6.5
const result3 = y + parseInt('1.5'); // 6
Edit
As jcaron states in the comment below, the + operator has a third meaning in the form of an unary + operator. If + only has a right operand, it will try to convert its value to a number almost equivalent as how parseFloat does it:
+ '1'; // returns 1
+ '1.5'; // returns 1.5
// In the context of the previous example:
const y = 5;
const result2 = y + +'1.5'; // 6.5
Dhe difference with parseFloat is that parseFloat will create a substring of the source string to the point where that substring would become an invalid numeric, whereas unary + will always take the entire string as its input:
parseFloat('1.5no-longer-valid'); // 1.5
+ '1.5no-longer-valid'; // NaN
That is because + is a concatenation operator. So javascript considers it to be a concatenation operator rather than a mathematical operator.But it is not the case with / ,* ,/ etc.
This happens because + its also used to concatenate strings. Then, JS always will find the better way to make the correct typecasts basing on types. In this case, the x+'1' operation, will be identified as string type + string type.
Otherwise, x-'1', will become int type - int type.
If you want to work with specific types, try to use type cast conversions, link here.
i'm following Beginning Javascript, and learning about data types conversions, more specific float and integers.
in chrome console, when i try subtraction:
parseFloat("2.1" - "0.1");
=>2
but when try the same with addition i get this:
parseFloat("2.1" + "0.1");
=>2.1
Can someone elaborate why during addition it behaves not how I would think it should?
thanks in advance
Have a look at the results of the subtraction and addition before calling parseFloat:
"2.1" - "0.1"
=> 2
In this case, JavaScript has inferred that you want to subtract numbers, so the strings have been parsed for you before the subtraction operation has been applied. Let's have a look at the other case:
"2.1" + "0.1"
=> "2.10.1"
Here the plus operator is ambiguous. Did you want to convert the strings to numbers and add them, or did you want to concatenate the strings? Remember that "AB" + "CDE" === "ABCDE". You then have:
parseFloat("2.10.1")
Now, 2.10.1 is not a valid number, but the parsing does its best and returns 2.10 (or put more simply, 2.1).
I think what you really wanted to do is to parse the two numbers separately before adding or subtracting them. That way you don't rely on JavaScript's automatic conversion from strings to numbers, and any mathematical operation should work as you expect:
parseFloat("2.1") - parseFloat("0.1")
=> 2
parseFloat("2.1") + parseFloat("0.1")
=> 2.2
Why does x = "1"- -"1" work and set the value of x to 2?
Why doesn't x = "1"--"1" works?
This expression...
"1"- -"1"
... is processed as ...
"1"- (-"1")
... that is, substract result of unary minus operation applied to "1" from "1". Now, both unary and binary minus operations make sense to be applied for Numbers only - so JS converts its operands to Numbers first. So that'll essentially becomes:
Number("1") - (-(Number("1"))
... that'll eventually becomes evaluated to 2, as Number("1"), as you probably expect, evaluates to 1.
When trying to understand "1"--"1" expression, JS parser attempts to consume as many characters as possible. That's why this expression "1"-- is processed first.
But it makes no sense, as auto-increment/decrement operations are not defined for literals. Both ++ and -- (both in postfix and prefix forms) should change the value of some assignable ('left-value') expression - variable name, object property etc.
In this case, however, there's nothing to change: "1" literal is always "1". )
Actually, I got a bit different errors (for x = "1"--"1") both in Firefox:
SyntaxError: invalid decrement operand
... and Chrome Canary:
ReferenceError: Invalid left-hand side expression in postfix operation
And I think these messages actually show the reason of that error quite clearly. )
Because -- is an operator in JavaScript.
When you separate the - characters in the first expression, it's unambiguous what you mean. When you put them together, JavaScript interprets them as one operator, and the following "1" as an unexpected string. (Or maybe it's the preceding "1"? I'm honestly not sure.)
"-1" = -1 (unary minus converts it to int)
So. "1" - (-1)
now, "+" is thje concatenation operator. not -. so JS returns the result 2 (instead of string concat).
also, "1"--"1" => here "--" is the decrement operator, for which the syntax is wrong as strings will not get converted automatically in this case.
because -- is an operator for decrement and cannot be applied on constant values.
I'm watching this Google I/O presentation from 2011 https://www.youtube.com/watch?v=M3uWx-fhjUc
At minute 39:31, Michael shows the output of the closure compiler, which looks like the code included below.
My question is what exactly is this code doing (how and why)
// Question #1 - floor & random? 2147483648?
Math.floor(Math.random() * 2147483648).toString(36);
var b = /&/g,
c = /</g,d=/>/g,
e = /\"/g,
f = /[&<>\"]/;
// Question #2 - sanitizing input, I get it...
// but f.test(a) && ([replaces]) ?
function g(a) {
a = String(a);
f.test(a) && (
a.indexOf("&") != -1 && (a = a.replace(b, "&")),
a.indexOf("<") != -1 && (a = a.replace(c, "<")),
a.indexOf(">") != -1 && (a = a.replace(d, ">")),
a.indexOf('"') != -1 && (a = a.replace(e, """))
);
return a;
};
// Question #3 - void 0 ???
var h = document.getElementById("submit-button"),
i,
j = {
label: void 0,
a: void 0
};
i = '<button title="' + g(j.a) + '"><span>' + g(j.label) + "</span></button>";
h.innerHTML = i;
Edit
Thanks for the insightful answers. I'm still really curious about the reason why the compiler threw in that random string generation at the top of the script. Surely there must be a good reason for it. Anyone???
1) This code is pulled from Closure Library. This code in is simply creating random string. In later version it has been replaced by to simply create a large random integer that is then concatenated to a string:
'closure_uid_' + ((Math.random() * 1e9) >>> 0)
This simplified version is easier for the Closure Compiler to remove so you won't see it leftover like it was previously. Specifically, the Compiler assumes "toString" with no arguments does not cause visible state changes. It doesn't make the same assumption about toString calls with parameters, however. You can read more about the compiler assumptions here:
https://code.google.com/p/closure-compiler/wiki/CompilerAssumptions
2) At some point, someone determined it was faster to test for the characters that might need to be replaced before making the "replace" calls on the assumption most strings don't need to be escaped.
3) As others have stated the void operator always returns undefined, and "void 0" is simply a reasonable way to write "undefined". It is pretty useless in normal usage.
1) I have no idea what the point of number 1 is.
2) Looks to make sure that any symbols are properly converted into their corresponding HTML entities , so yes basically sanitizing the input to make sure it is HTML safe
3) void 0 is essentially a REALLY safe way to make sure it returns undefined . Since the actual undefined keyword in javascript is mutable (i.e. can be set to something else), it's not always safe to assume undefined is actually equal to an undefined value you expect.
When in doubt, check other bases.
2147483648 (base 10) = 0x80000000 (base 16). So it's just making a random number which is within the range of a 32-bit signed int. floor is converting it to an actual int, then toString(36) is converting it to a 36-character alphabet, which is 0-9 (10 characters) plus a-z (26 characters).
The end-result of that first line is a string of random numbers and letters. There will be 6 of them (36^6 = 2176782336), but the first one won't be quite as random as the others (won't be late in the alphabet). Edit: Adrian has worked this out properly in his answer; the first letter can be any of the 36 characters, but is slightly less likely to be Z. The other letters have a small bias towards lower values.
For question 2, if you mean this a = String(a); then yes, it is ensuring that a is a string. This is also a hint to the compiler so that it can make better optimisations if it's able to convert it to machine code (I don't know if they can for strings though).
Edit: OK you clarified the question. f.test(a) && (...) is a common trick which uses short-circuit evaluation. It's effectively saying if(f.test(a)){...}. Don't use it like that in real code because it makes it less readable (although in some cases it is more readable). If you're wondering about test, it's to do with regular expressions.
For question 3, it's new to me too! But see here: What does `void 0` mean? (quick google search. Turns out it's interesting, but weird)
There's a number of different questions rolled into one, but considering the question title I'll just focus on the first here:
Math.floor(Math.random() * 2147483648).toString(36);
In actual fact, this doesn't do anything - as the value is discarded rather than assigned. However, the idea of this is to generate a number between 0 and 2 ^ 31 - 1 and return it in base 36.
Math.random() returns a number from 0 (inclusive) to 1 (exclusive). It is then multipled by 2^31 to produce the range mentioned. The .toString(36) then converts it to base 36, represented by 0 to 9 followed by A to Z.
The end result ranges from 0 to (I believe) ZIK0ZI.
As to why it's there in the first place ... well, examine the slide. This line appears right at the top. Although this is pure conjecture, I actually suspect that the code was cropped down to what's visible, and there was something immediately above it that this was assigned to.
In JavaScript, can someone explain the results of the 2 following expressions:
"4" + 4 and 4 + "4"
Thanks!
Both will result in the String:
"44"
This is because the + operator serves 2 purposes -- addition and concatenation. And, if either operand is a String (or is cast to a String by the internal ToPrimitive()) they'll be concatenated.
This is described in the specification as:
7) If Type(lprim) is String or Type(rprim) is String, then
a) Return the String that is the result of concatenating ToString(lprim) followed by ToString(rprim)
8) Return the result of applying the addition operation to ToNumber(lprim) and ToNumber(rprim). See the Note below 11.6.3.
If you want to ensure addition, you can use parseFloat() or the unary + on each:
var a = "4", b = 4;
console.log(parseFloat(a) + parseFloat(b)); // 8;
console.log((+a) + (+b)); // 8, extra parenthesis for clarity
1+'1'+1 = '111'
1+1+'1' = '21'
'1'+(1+1) = '12'
'1'+1+1 = '111'
Javascript performs math until it hits a string and then switches to concatenation, and it also follows regular formula rules run () operations first.
they'll both be '44'. The presence of the '4' as a string casts the whole operation to a string, so the two characters are concatenated.
Cited from: http://javascript.about.com/od/variablesandoperators/a/vop10.htm
One thing that can be confusing to beginners is that JavaScript uses +
with text strings to mean something completely different from what it
means with numbers. While with numbers + means add the numbers
together with text + means concatenate them together. Concatenation
basically means joining one text string onto the end of the first so
that "my"+"book" gives "mybook" as a result. Where beginners tend to
get confused is that while 3+3 gives 6, "3"+"3" gives "33".
You can also use += with text strings to directly add the variable or
text on the right onto the end of the text string variable on the
left.
Mixing Data Types
Additional confusion can arise when you are working with variables
that are of different types. All operations require that the variables
that they are operating on both be of the same type. Before JavaScript
is able to perform any operations that involve two different data
types, it must first convert one of the variables from one type to the
other. You can't add a number to a text string without first either
converting the number to text or the text to a number.
When converting between data types we have two choices. We can allow
JavaScript to do the conversion for us automatically or we can tell
JavaScript which variable that we want to convert.
JavaScript will attempt to convert any text string into the number
equivalent when performing subtraction, multiplication, division, and
taking remainders. Your text string will actually need to contain
something that JavaScript can convert to a number (i.e., a string like
"10") in order for the conversion to work.
If we use + then this could either mean that we want to convert the
string to a number and add then or that we want to convert the number
to a string and concatenate them. JavaScript can only perform one of
these two alternatives. It always converts numbers to strings (since
that will work whether the string contains a number or not).
Here are some examples.
"5" - 3 = 2;
"5" + 3 = "53"
2 + "7" = "27"
5 + 9 + "1" = "141"
Since subtraction only works with numbers 1 converts the text string
into a number before doing the subtraction.
In 2 and 3 the number is converted to a text string before being
concatenated (joined) to the other text string.
In 4 the leftmost addition is done first. Since these are both numbers
they are actually added together and not treated as text. The result
of this first addition leaves us with a similar situation to the third
example and so the result of that addition is converted to text and
concatenated.
To actually force JavaScript to convert a text string to a number we
can use Number("3") or alternatively to force JavaScript to convert a
number to a text string we can use String(5).
Expressions in JS work on two prime principles.
B O D M A(includes concat) S
Left to right order of execution
However, its not straight forward
for + operator
as far as it encounters numbers it will do math addition using left to right execution, However,as soon as it encounters a string, it concatenates the result (that's calculated till encountering a string) with rest of the expression.
//left to right execution
console.log(10+10+"10") //2010, (10+10) of numtype + "10" of stringtype concat(20+"10")
console.log(10+10+"10"+10+10) //20101010,
//(10+10) of number type + "10" stringtype(now since a string is enc.) + (10+10) of number type would act as strings and get concatenated = 20+"10"+"1010"
console.log("10"+[10,10,10]+10) //1010,10,1010
//"10"of stringtype + array of numtypes + 10 of numtype
// "10" concats with first element of array, last number 10 concats with last element of array.
for all other operators such as -,*,/,^...
if all occurrences are numbers/numbers as string, it will do the respective math operation treating "numbers as string" to be numbers.
console.log("10"-10) //0
console.log("10"/10) //1
console.log("10"*10) //100
console.log(10+"10"*10) //110 //BODMAS
console.log(Math.pow(10,"10")) //10000000000
if there are occurrences of non-numeric strings,arrays,objects in the middle of expression that involve (-,*,/,^...)math operations, it will always return NaN
console.log(10-{id:1,name:"hey"}-10) //NaN
console.log(10-10-"hey"-10-10-10) //NaN
console.log("hey"/10) //NaN
console.log("hey"* 3) //NaN
console.log(["hey","hey"]*"3") //NaN
console.log("10"/[10,10,10]/10) //NaN