Here's the trivial Java–JS interaction to find a user by name, and it contains a severe problem. Can you spot one?
@RestController
public class SearchController {
@GetMapping("/findUser")
public UserInfo findUser(String name) {
return elasticFacade.findUser(name);
}
}
public class UserInfo {
public long id;
public String name;
}
export const findUser = name =>
fetch(`/findUser?name=${name}`)
.then(r => r.json())
.then(({id, name}) => setUserInfo({id, name}));
When language matters
Debates what language is best will never end. Some people like Java simplicity; others say there's nothing better than JS functions. However, much languages allow writing awesome software for a variety of applications — frontend, backend, desktop, ML, and many more. But... There's something you cannot ignore, and which is quite hard to emulate or workaround: language primitive types, especially numbers.
Java has a variety of primitive numbers to choose from:
 integer

byte
: signed 8bit 
char
: unsigned 16bit, mainly used for UTF16 codes 
short
: signed 16bit 
int
: signed 32bit 
long
: signed 64bit

 floatingpoint

float
: 32bit 
double
: 64bit

JavaScript has only two number primitives:

number
— the “default” type 
bigint
— it's quite new, so JS uses it only if you ask explicitly withn
suffix, like42n
. All traditional APIs and applications like JSON, DOM, CSS use simplenumber
. This also means all numbers passed into JS are coerced tonumber
.
What is number
exactly? This is my favorite question I ask interviewing for fullstack positions. Surprisingly, few candidates know, which is very sad. Do you know the answer? 🙂
The number
is...
A 64bit floating point number, just like double
of Java, C++ and C#. So any other number without n
suffix is converted into this type. Can it hold all numbers which Java and C# can pass, including the largest from long
range? To answer this question we need to understand how these types are stored in memory. That's not that hard, so let's dive in!
long
It's quite simple: higher bit stores the sign (0
= positive 1
= negative), others store the value.
partition  sign  value 
bit  63  62  61  ...  1  0 
When the number is negative, the value is encoded in so called “2s complimentary” code, but let's leave it for really curious folks 😉 That's how the positive long
is interpreted:
The largest long
is when all bits except the sign are ones, and this gives 9,223,372,036,854,775,807.
number
and double
The type is designed to represent numbers of different magnitudes, including very large, like the size of the Universe, and very small, like distances between atoms. These numbers are usually written with so called “scientific notation”:
This notation have two parts: the significand (or “fraction”) and the exponent (1.5319 and 35 respectively for
$x$
). Floatingpoint binary representation mirrors this structure also having these partitions:
partition  sign  exponent  significand 
bit  63  62  ...  52  51  ...  0 
When the exponent is 0, the number is interpreted in this way:
But can it store larger and smaller numbers? That's where the exponent comes into play! When the exponent is $exp$ , it literally says “please multiply the whole significand by $2^{exp}$ ”.
Now, recall our example. We wanted to store a long
which is
$2^{62}$
in the upper bit, so to get the first summand equal to
$2^{62}$
we need multiplying the value by
$2^{63}$
:
That's very similar to long
formula, but... where are summands less than
$2^{10}$
? We need them but there are no more bits and the precision suffers 😥 To get it back we need to decrease
$exp$
to no more than 53:
Now the precision is back but seems like we lost the ability to represent the full long
range 😕 What can we do with it? Just accept it, and always keep in mind.
So, number
allows...
 Either having large but imprecise number
 Or having precise but limited integer. This limit is so important that has its own name: MAX_SAFE_INTEGER.
Feel the precision loss
Just open the console right on this page and try to output the largest long
:
console.log(9223372036854775807)
VM139:1 9223372036854776000
If the argument is for example a physical distance we may assume it was just rounded a bit. Come on, it's 9 quintillion meters, who cares about couple of kilometers error!
But what if it's someone's id? You got the wrong user! If the code like this runs on a backend you compromise the privacy!
What can I do?
Never, never ever pass long
IDs as numbers to a JS code. Convert them to strings!
Thanks for finishing this reading. Have you fixed issues like this? Share your examples! If you find this material helpful please consider leaving some feedback. Thanks!
Discussion