## DEV Community is a community of 553,164 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

# Passing IDs as numbers? You are under the risk!

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) {
}
}

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 8-bit
• char: unsigned 16-bit, mainly used for UTF-16 codes
• short: signed 16-bit
• int: signed 32-bit
• long: signed 64-bit
• floating-point
• float: 32-bit
• double: 64-bit

JavaScript has only two number primitives:

• number — the “default” type
• bigint — it's quite new, so JS uses it only if you ask explicitly with n suffix, like 42n. All traditional APIs and applications like JSON, DOM, CSS use simple number. This also means all numbers passed into JS are coerced to number.

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 64-bit 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:

$value = 2^{62} ⋅ bit_{62} + 2^{61} ⋅ bit_{61} + ... + 2 ⋅ bit_{1} + 1 ⋅ bit_{0}$

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”:

\begin{aligned} x &= 1.5319 ⋅ 10^{35} \\ y &= 8.14038 ⋅ 10^{-21} \end{aligned}

This notation have two parts: the significand (or “fraction”) and the exponent (1.5319 and 35 respectively for $x$ ). Floating-point 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:

$value = {1 \over 2} + {1 \over 2^2} ⋅ bit_{51} + {1 \over 2^3} ⋅ bit_{50} + ... {1 \over 2^{53}} ⋅ bit_{0}$

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}$ :

\begin{aligned} exp &= 63 \\ value &= 2^{62} + 2^{61} ⋅ bit_{51} + 2^{60} ⋅ bit_{50} + ... + 2^{10} ⋅ bit_{0} \end{aligned}

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:

\begin{aligned} exp &= 53 \\ value &= 2^{52} + 2^{51} ⋅ bit_{51} + 2^{50} ⋅ bit_{50} + ... + 1 ⋅ bit_{0} \end{aligned}

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!