Intl formatting of huge floating point numbers - javascript

I'm trying to better understand why large numbers, with potentially large precisions are inconsistently handled, specifically in JavaScript and it's localization facilities (e.g. ECMA-402/Intl). I'm assuming this has to do with the use of floating point numbers, but I'd like to understand where the limits are and/or how to avoid these pitfalls.
For example, using Intl.NumberFormat:
console.log(new Intl.NumberFormat('en-US', { minimumFractionDigits: 3, maximumFractionDigits: 3 }).format(9999999999990.001)); // logs 9,999,999,999,990.000
let test1 = 9999999999990.001
console.log(test1); // logs 9999999999990.002
How would I be able to figure out where these numbers start to get inconsistent? Is there some kind of limit? Does that limit change as I increase decimal precision, e.g. :
let test2 = 9999999999990.0004;
console.log(test2) // logs 9999999999990

Is there some kind of limit? Does that limit change as I increase decimal precision?
Yes, and yes. Floating-point numbers in JavaScript are themselves stored in 64 bits of space, which means they are limited in the precision they can represent. See this answer for more information.
How would I be able to figure out where these numbers start to get inconsistent?
Pass your "numeric literals" to a function in the form of strings, and check to see if that string, when coerced to a number and back, returns the correct literal:
function safeNumber (s) {
if (String(+s) !== s)
throw new Error('Unsafe number!')
return +s
}
let safe = safeNumber('999999999999999')
console.log(safe)
let unsafe = safeNumber('9999999999990.001')
console.log(unsafe)

Related

Using Javascript parseInt returns a different value

I'm not entirely sure why the string "6145390195186705543" is outputting 6145390195186705000, at first reading through the threads it may be the base radix but even tinkering around with that still gives me the same results, can anyone help explain because I do believe this is not a bug, but I'm not entirely sure what's the explanation here.
const digits = [6,1,4,5,3,9,0,1,9,5,1,8,6,7,0,5,5,4,3]
const val1 = digits.join('') // string "6145390195186705543"
const test1 = Number(val1) // outputs 6145390195186705000
const test2 = parseInt(val1) // outputs 6145390195186705000
It's not an issue with parseInt. If you were to create the same number from a number literal, you'd get the same problem.
This happens because JavaScript stores numbers as double prescision floats, which offer approximately 16 significant decimal digits.
The only way to fix this is to not use the Number type. JavaScript has another number type, BigInt, which offers arbitrary precision integers. Here's how you can do it with it:
const digits = [6,1,4,5,3,9,0,1,9,5,1,8,6,7,0,5,5,4,3]
const val1 = digits.join('')
const test3 = BigInt(val1) // 6145390195186705543n - The n at the end means it's a BigInt

Add trailing zeros to match precision dynamically

I am creating a react crypto project and just trying to figure out how to add trailing zeros dynamically without using the toFixed method. The reason why I am avoiding that toFixed method is because though it gets the precision right and tacks on desired trailing zeros, it rounds the number. Which can be misleading. So I am on the last part of getting this thing to operate correctly.
const getInternalAssetBalance = (selectedAsset, pairType, useFreeTotal = false) => {
const exchangeInternalAccount = holdingsByAccount.find(account => account.exchange === 'REGULAR');
const assetObj = exchangeInternalAccount && exchangeInternalAccount.assets.find(item => item.name === selectedAsset);
};
I have it where it limits the digits after the decimal based on precision(meaning it won't go over the precision specified), however, if a number happens to have less decimal places than the desired precision, it won't tack on the extra trailing zeros. For example lets say we have a number 12.353. If the precision was 5, I would want the result to be 12.35300. So that it tacks on the extra two zeros.
Anyone happen to know how I can dynamically tack on zeros if the balance amount happens to have fewer decimal places than the desired precision so that it matches?
A few remarks:
Since your function returns a number data type, you lose any format that wants to have trailing zeroes in the decimal part. A number value does not have any formatting that goes with it. Just like the native .toFixed method, you need to return a string.
You can still make use of the .toFixed method if you first make the truncation that is needed. This you can do by multiplying the number with a power of 10, truncate it to integer, and then divide it again.
function toFixed(n, precision) {
const coeff = 10**precision;
return (Math.floor(n * coeff) / coeff).toFixed(precision);
}
console.log(toFixed(1.234567, 4)); // 1.2345
console.log(toFixed(1.23, 4)); // 1.2300

Why is "_" getting removed from a number in javascript?

I tried entering the below code in the Chrome console:
var a = 16_11;
It's not inside " or '. And the output of a is 1611 instead of 16_11. Why is _ getting removed?
You got a numeric separator which is a proposal and actual shipping in V8 v7.5/Chrome 75.
This feature enables developers to make their numeric literals more readable by creating a visual separation between groups of digits. Large numeric literals are difficult for the human eye to parse quickly, especially when there are long digit repetitions. This impairs both the ability to get the correct value / order of magnitude...
1000000000 // Is this a billion? a hundred millions? Ten millions?
101475938.38 // what scale is this? what power of 10?
...but also fails to convey some use-case information, such as fixed-point arithmetic using integers. For instance, financial computations often work in 4- to 6-digit fixed-point arithmetics, but even storing amounts as cents is not immediately obvious without separators in literals:
const FEE = 12300;
// is this 12,300? Or 123, because it's in cents?
const AMOUNT = 1234500;
// is this 1,234,500? Or cents, hence 12,345? Or financial, 4-fixed 123.45?
Using underscores (_, U+005F) as separators helps improve readability for numeric literals, both integers and floating-point (and in JS, it's all floating-point anyway):
1_000_000_000 // Ah, so a billion
101_475_938.38 // And this is hundreds of millions
let fee = 123_00; // $123 (12300 cents, apparently)
let fee = 12_300; // $12,300 (woah, that fee!)
let amount = 12345_00; // 12,345 (1234500 cents, apparently)
let amount = 123_4500; // 123.45 (4-fixed financial)
let amount = 1_234_500; // 1,234,500
Also, this works on the fractional and exponent parts, too:
0.000_001 // 1 millionth
1e10_000 // 10^10000 -- granted, far less useful / in-range...
Some more sources:
ES proposal: numeric separators
Numeric separators
var a = 1_000;
console.log(a);
Because Chrome implements the experimental numeric separator proposal, which permits optional underscores between any digits in a number literal. Without that, it would be just a syntax error.
What are you going to do with that code snippet? 16_11 is not meaningful number, i think.
So if you want string 16_11, then
var a = "16_11";
will work.
The purpose it is introduced to increase the readability because some number can be large and difficult to read while programming. It just acts as separator here, so you can easily identify how many digits are there.
For example looking at below example you can easily say it is a trillion without putting much effort;
var a = 1_000_000_000_000;
console.log(a);

how to handle more than 20 digit number (Big integer)?

My angular program, I need to pass the number which is more than 20 digit to the API request.
num: any;
this.num = 2019111122001424290521878689;
console.log(this.num); // It displays "2.0191111220014244e+27"
I tried to change string from number as below
console.log(this.num.toString()); // It displays "2.0191111220014244e+27"
My expectation is that I need to pass the original big integer into the API request. If I pass as below, it goes as "2.0191111220014244e+27".
BTW, I tried BigInt(this.num), which gives difference number.
Suggest me
In JavaScript, big integer literals have the letter n as a suffix:
var bigNum = 2019111122001424290521878689n;
console.log(bigNum);
For more information, see
MDN JavaScript Reference - BigInt
If you got a large number (> SAFE_INTEGER) from an API, in JSON format, and you want to get the exact value, as as string, you unfortunately can't use JSON.parse(), as it will use the number type and lose precision.
There are alternative JSON parsers out there like LosslessJSON that might solve your problem.
You can use BigInt.
BigInt is a built-in object that provides a way to represent whole numbers larger than 253 - 1, which is the largest number JavaScript can reliably represent with the Number primitive. BigInt can be used for arbitrarily large integers.
const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n
const hugeString = BigInt("9007199254740991");
// ↪ 9007199254740991n
const hugeHex = BigInt("0x1fffffffffffff");
// ↪ 9007199254740991n
const hugeBin = BigInt("0b11111111111111111111111111111111111111111111111111111");
// ↪ 9007199254740991n
BigInt is similar to Number in some ways, but also differs in a few key matters — it cannot be used with methods in the built-in Math object and cannot be mixed with instances of Number in operations; they must be coerced to the same type. Be careful coercing values back and forth, however, as the precision of a BigInt may be lost when it is coerced to a Number.
Refer to
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
The problem is that the number you have there is not an integer. Javascript can only store integers up to the value given by Number.MAX_SAFE_INTEGER. In chrome, this number is 9007199254740991.
The number you have is actually a floating point number, and converting it between floating point and integer will loose some precision.

Using a float in Javascript in a hash function

I Have a hash function like this.
class Hash {
static rotate (x, b) {
return (x << b) ^ (x >> (32-b));
}
static pcg (a) {
let b = a;
for (let i = 0; i < 3; i++) {
a = Hash.rotate((a^0xcafebabe) + (b^0xfaceb00c), 23);
b = Hash.rotate((a^0xdeadbeef) + (b^0x8badf00d), 5);
}
return a^b;
}
}
// source Adam Smith: https://groups.google.com/forum/#!msg/proceduralcontent/AuvxuA1xqmE/T8t88r2rfUcJ
I use it like this.
console.log(Hash.pcg(116)); // Output: -191955715
As long as I send an integer in, I get an integer out. Now here comes the problem. If I have a floating number as input, rounding will happen. The number Hash.pcg(1.1) and Hash.pcg(1.2) will yield the same. I want different inputs to yield different results. A possible solution could be to multiply the input so the decimal is not rounded down, but is there a more elegant and flexible solution to this?
Is there a way to convert a floating point number to a unique integer? Each floating point number would result in a different integer number.
Performance is important.
This isn't quite an answer, but I was running out of room to make it a comment. :)
You'll hit a problem with integers outside of the 32-bit range as well as with non-integer values.
JavaScript handles all numbers as 64-bit floating point. This gives you exact integers over the range -9007199254740991 to 9007199254740991 (±(2^53 - 1)), but the bit-wise operators used in your hash algorithm (^, <<, >>) only work in a 32-bit range.
Since there are far more non-integer numbers possible than integers, no one-to-one mapping is possible with ordinary numbers. You could work something out with BigInts, but that will likely lead to comparatively much slower performance.
If you're willing to deal with the performance hit, your can use JavaScript buffer functions to get at the actual bits of a floating point number. (I'd say more now about how to do that, but I've got to run!)
Edit... back from dinner...
You can convert JavaScript's standard number type, which is 64-bit floating point, to a BigInt like this:
let dv = new DataView(new ArrayBuffer(8));
dv.setFloat64(0, Math.PI);
console.log(dv.getFloat64(0), dv.getBigInt64(0), dv.getBigInt64(0).toString(16).toUpperCase())
The output from this is:
3.141592653589793 4614256656552045848n "400921FB54442D18"
The first item shows that the number was properly stored as byte array, the second shows the BigInt created from the same bits, and the last is the same BigInt over again, but in hex to better show the floating point data format.
Once you've converted a number like this to a BigInt (which is not the same numeric value, but it is the same string of bits) every possible value of number will be uniquely represented.
The same bit-wise operators you used in your algorithm above will work with BigInts, but without the 32-bit limitation. I'm guessing that for best results you'd want to change the 32 in your code to 64, and use 16-digit (instead of 8-digit) hex constants as hash keys.

Categories

Resources