Citing the ECMAScript spec Section 5.2:
The notation “x modulo y” (y must be finite and nonzero) computes a
value k of the same sign as y (or zero) such that abs(k) < abs(y) and
x−k = q × y for some integer q.
so if y is positive, the result k of 'x modulo y' is positive regardless of the sign of x.
and if my understanding is right, ToInt32(-1) equals ToInt32(1)?
The notation x modulo y is used internally within the spec to describe the result of certain operations. So yes, the result k of x modulo y is (by definition) of the same sign as y. It is not claimed that the % operator is equivalent to modulo.
If you're interested, the actual spec for % can be found under section 11.5.3. Interestingly, it makes no use of modulo.
Copy pasting from my previous answer here:
Take a % b
1. When both +ve, Modulo & Remainder are one and the same
2. When a is -ve, they are not the same
For example;
a = -10, b = 3
Remainder of -10 % 3 = -1
for Modulo, add a greater multiple of 3 to your 'a' and calculate the remainder.
-10 + 12 = 2
2 % 3 = 2 is your answer
The modulo operation is defined as the mathematical modulo operation:
Mathematical operations such as addition, subtraction, negation,
multiplication, division, and the mathematical functions defined later
in this clause should always be understood as computing exact
mathematical results on mathematical real numbers, which do not
include infinities and do not include a negative zero that is
distinguished from positive zero.
Your question:
ToInt32(-1) equals ToInt32(1)
Well, no:
Let posInt be sign(number) * floor(abs(number)).
posInt = sign(-1) * floor(abs(-1)) = -1;
Let int32bit be posInt modulo 232; that is, a finite integer value k
of Number type with positive sign and less than 232 in magnitude such
that the mathematical difference of posInt and k is mathematically an
integer multiple of 232.
int32bit = posInt mod 4294967296 = -1 mod 4294967296 = 4294967295
(wolfram alpha link for mathematical result)
If int32bit is greater than or equal to 231, return int32bit − 232,
otherwise return int32bit.
Because 4294967295 >= 2147483648, we return 4294967295 - 4294967296, I.E. -1.
If we run the same steps for ToInt32(1), we get 1. So they don't have the same result.
It's important to note that I'm not looking for a rounding function. I am looking for a function that returns the number of decimal places in an arbitrary number's simplified decimal representation. That is, we have the following:
decimalPlaces(5555.0); //=> 0
decimalPlaces(5555); //=> 0
decimalPlaces(555.5); //=> 1
decimalPlaces(555.50); //=> 1
decimalPlaces(0.0000005); //=> 7
decimalPlaces(5e-7); //=> 7
decimalPlaces(0.00000055); //=> 8
decimalPlaces(5.5e-7); //=> 8
My first instinct was to use the string representations: split on '.', then on 'e-', and do the math, like so (the example is verbose):
function decimalPlaces(number) {
var parts = number.toString().split('.', 2),
integerPart = parts[0],
decimalPart = parts[1],
exponentPart;
if (integerPart.charAt(0) === '-') {
integerPart = integerPart.substring(1);
}
if (decimalPart !== undefined) {
parts = decimalPart.split('e-', 2);
decimalPart = parts[0];
}
else {
parts = integerPart.split('e-', 2);
integerPart = parts[0];
}
exponentPart = parts[1];
if (exponentPart !== undefined) {
return integerPart.length +
(decimalPart !== undefined ? decimalPart.length : 0) - 1 +
parseInt(exponentPart);
}
else {
return decimalPart !== undefined ? decimalPart.length : 0;
}
}
For my examples above, this function works. However, I'm not satisfied until I've tested every possible value, so I busted out Number.MIN_VALUE.
Number.MIN_VALUE; //=> 5e-324
decimalPlaces(Number.MIN_VALUE); //=> 324
Number.MIN_VALUE * 100; //=> 4.94e-322
decimalPlaces(Number.MIN_VALUE * 100); //=> 324
This looked reasonable at first, but then on a double take I realized that 5e-324 * 10 should be 5e-323! And then it hit me: I'm dealing with the effects of quantization of very small numbers. Not only are numbers being quantized before storage; additionally, some numbers stored in binary have unreasonably long decimal representations, so their decimal representations are being truncated. This is unfortunate for me, because it means that I can't get at their true decimal precision using their string representations.
So I come to you, StackOverflow community. Does anyone among you know a reliable way to get at a number's true post-decimal-point precision?
The purpose of this function, should anyone ask, is for use in another function that converts a float into a simplified fraction (that is, it returns the relatively coprime integer numerator and nonzero natural denominator). The only missing piece in this outer function is a reliable way to determine the number of decimal places in the float so I can multiply it by the appropriate power of 10. Hopefully I'm overthinking it.
Historical note: the comment thread below may refer to first and second implementations. I swapped the order in September 2017 since leading with a buggy implementation caused confusion.
If you want something that maps "0.1e-100" to 101, then you can try something like
function decimalPlaces(n) {
// Make sure it is a number and use the builtin number -> string.
var s = "" + (+n);
// Pull out the fraction and the exponent.
var match = /(?:\.(\d+))?(?:[eE]([+\-]?\d+))?$/.exec(s);
// NaN or Infinity or integer.
// We arbitrarily decide that Infinity is integral.
if (!match) { return 0; }
// Count the number of digits in the fraction and subtract the
// exponent to simulate moving the decimal point left by exponent places.
// 1.234e+2 has 1 fraction digit and '234'.length - 2 == 1
// 1.234e-2 has 5 fraction digit and '234'.length - -2 == 5
return Math.max(
0, // lower limit.
(match[1] == '0' ? 0 : (match[1] || '').length) // fraction length
- (match[2] || 0)); // exponent
}
According to the spec, any solution based on the builtin number->string conversion can only be accurate to 21 places beyond the exponent.
9.8.1 ToString Applied to the Number Type
Otherwise, let n, k, and s be integers such that k ≥ 1, 10k−1 ≤ s < 10k, the Number value for s × 10n−k is m, and k is as small as possible. Note that k is the number of digits in the decimal representation of s, that s is not divisible by 10, and that the least significant digit of s is not necessarily uniquely determined by these criteria.
If k ≤ n ≤ 21, return the String consisting of the k digits of the decimal representation of s (in order, with no leading zeroes), followed by n−k occurrences of the character ‘0’.
If 0 < n ≤ 21, return the String consisting of the most significant n digits of the decimal representation of s, followed by a decimal point ‘.’, followed by the remaining k−n digits of the decimal representation of s.
If −6 < n ≤ 0, return the String consisting of the character ‘0’, followed by a decimal point ‘.’, followed by −n occurrences of the character ‘0’, followed by the k digits of the decimal representation of s.
Historical note: The implementation below is problematic. I leave it here as context for the comment thread.
Based on the definition of Number.prototype.toFixed, it seems like the following should work but due to the IEEE-754 representation of double values, certain numbers will produce false results. For example, decimalPlaces(0.123) will return 20.
function decimalPlaces(number) {
// toFixed produces a fixed representation accurate to 20 decimal places
// without an exponent.
// The ^-?\d*\. strips off any sign, integer portion, and decimal point
// leaving only the decimal fraction.
// The 0+$ strips off any trailing zeroes.
return ((+number).toFixed(20)).replace(/^-?\d*\.?|0+$/g, '').length;
}
// The OP's examples:
console.log(decimalPlaces(5555.0)); // 0
console.log(decimalPlaces(5555)); // 0
console.log(decimalPlaces(555.5)); // 1
console.log(decimalPlaces(555.50)); // 1
console.log(decimalPlaces(0.0000005)); // 7
console.log(decimalPlaces(5e-7)); // 7
console.log(decimalPlaces(0.00000055)); // 8
console.log(decimalPlaces(5e-8)); // 8
console.log(decimalPlaces(0.123)); // 20 (!)
Well, I use a solution based on the fact that if you multiply a floating-point number by the right power of 10, you get an integer.
For instance, if you multiply 3.14 * 10 ^ 2, you get 314 (an integer). The exponent represents then the number of decimals the floating-point number has.
So, I thought that if I gradually multiply a floating-point by increasing powers of 10, you eventually arrive to the solution.
let decimalPlaces = function () {
function isInt(n) {
return typeof n === 'number' &&
parseFloat(n) == parseInt(n, 10) && !isNaN(n);
}
return function (n) {
const a = Math.abs(n);
let c = a, count = 1;
while (!isInt(c) && isFinite(c)) {
c = a * Math.pow(10, count++);
}
return count - 1;
};
}();
for (const x of [
0.0028, 0.0029, 0.0408,
0, 1.0, 1.00, 0.123, 1e-3,
3.14, 2.e-3, 2.e-14, -3.14e-21,
5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8,
0.000006, 0.0000007,
0.123, 0.121, 0.1215
]) console.log(x, '->', decimalPlaces(x));
2017 Update
Here's a simplified version based on Edwin's answer. It has a test suite and returns the correct number of decimals for corner cases including NaN, Infinity, exponent notations, and numbers with problematic representations of their successive fractions, such as 0.0029 or 0.0408. This covers the vast majority of financial applications, where 0.0408 having 4 decimals (not 6) is more important than 3.14e-21 having 23.
function decimalPlaces(n) {
function hasFraction(n) {
return Math.abs(Math.round(n) - n) > 1e-10;
}
let count = 0;
// multiply by increasing powers of 10 until the fractional part is ~ 0
while (hasFraction(n * (10 ** count)) && isFinite(10 ** count))
count++;
return count;
}
for (const x of [
0.0028, 0.0029, 0.0408, 0.1584, 4.3573, // corner cases against Edwin's answer
11.6894,
0, 1.0, 1.00, 0.123, 1e-3, -1e2, -1e-2, -0.1,
NaN, 1E500, Infinity, Math.PI, 1/3,
3.14, 2.e-3, 2.e-14,
1e-9, // 9
1e-10, // should be 10, but is below the precision limit
-3.14e-13, // 15
3.e-13, // 13
3.e-14, // should be 14, but is below the precision limit
123.12345678901234567890, // 14, the precision limit
5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8,
0.000006, 0.0000007,
0.123, 0.121, 0.1215
]) console.log(x, '->', decimalPlaces(x));
The tradeoff is that the method is limited to maximum 10 guaranteed decimals. It may return more decimals correctly, but don't rely on that. Numbers smaller than 1e-10 may be considered zero, and the function will return 0. That particular value was chosen to solve correctly the 11.6894 corner case, for which the simple method of multiplying by powers of 10 fails (it returns 5 instead of 4).
However, this is the 5th corner case I've discovered, after 0.0029, 0.0408, 0.1584 and 4.3573. After each, I had to reduce the precision by one decimal. I don't know if there are other numbers with less than 10 decimals for which this function may return an incorrect number of decimals. To be on the safe side, look for an arbitrary precision library.
Note that converting to string and splitting by . is only a solution for up to 7 decimals. String(0.0000007) === "7e-7". Or maybe even less? Floating point representation isn't intuitive.
Simple "One-Liner":
If what you're doing requires more than 16 digit precision, then this is not for you.
This 'one-liner' will work fine for the other 99.99999999999999% of the time. (Yes, even that number.)😜
function numDec(n){return n%1==0?0:(""+n).length-(""+n).lastIndexOf(".")-1}
Demo in the snippet:
function numDec(n){return n%1==0?0:(""+n).length-(""+n).lastIndexOf(".")-1}
setInterval(function(){
n=Math.random()*10000000000;
document.body.innerHTML=n+' ← '+numDec(n)+' decimal places';
},777);
body{font-size:123.4567890%; font-family:'fira code';}
More info:
mozilla.com : .lastIndexOf()
mozilla.com : .length
this works for numbers smaller than e-17 :
function decimalPlaces(n){
var a;
return (a=(n.toString().charAt(0)=='-'?n-1:n+1).toString().replace(/^-?[0-9]+\.?([0-9]+)$/,'$1').length)>=1?a:0;
}
This works for me
const decimalPlaces = value.substring(value.indexOf('.') + 1).length;
This method expects the value to be a standard number.
Not only are numbers being quantized before storage; additionally, some numbers stored in binary have unreasonably long decimal representations, so their decimal representations are being truncated.
JavaScript represents numbers using IEEE-754 double-precision (64 bit) format. As I understand it this gives you 53 bits precision, or fifteen to sixteen decimal digits.
So for any number with more digits you just get an approximation. There are some libraries around to handle large numbers with more precision, including those mentioned in this thread.
2021 Update
An optimized version of Mike Samuel handling scientific and non-scientific representation.
// Helper function to extract the number of decimal assuming the
// input is a number (either as a number of a stringified number)
// Note: if a stringified number has an exponent, it will always be
// '<x>e+123' or '<x>e-123' or '<x.dd...d>e+123' or '<x.dd...d>e-123'.
// No need to check for '<x>e123', '<x>E+123', '<x>E-123' etc.
const _numDecimals = v => {
const [i, p, d, e, n] = v.toString().split(/(\.|e[\-+])/g);
const f = e === 'e-';
return ((p === '.' && (!e || f) && d.length) + (f && parseInt(n)))
|| (p === 'e-' && parseInt(d))
|| 0;
}
// But if you want to be extra safe...you can replace _numDecimals
// with this:
const _numSafeDecimals = v => {
let [i, p, d, e, n] = v.toString().split(/(\.|[eE][\-+])/g);
e = e.toLowerCase();
const f = e === 'e-';
return ((p === '.' && (!e || f) && d.length) + (f && parseInt(n)))
|| (p.toLowerCase() === 'e-' && parseInt(d))
|| 0;
}
// Augmenting Number proto.
Number.prototype.numDecimals = function () {
return (this % 1 !== 0 && _numDecimals(this)) || 0;
}
// Independent function.
const numDecimals = num => (
(!isNaN(num) && num % 1 !== 0 && _numDecimals(num)) || 0
);
// Tests:
const test = n => (
console.log('Number of decimals of', n, '=', n.numDecimals())
);
test(1.234e+2); // --> 1
test(0.123); // ---> 3
test(123.123); // ---> 3
test(0.000123); // ---> 6
test(1e-20); // --> 20
test(1.2e-20); // --> 21
test(1.23E-20); // --> 22
test(1.23456789E-20); // --> 28
test(10); // --> 0
test(1.2e20); // --> 0
test(1.2e+20); // --> 0
test(1.2E100); // --> 0
test(Infinity); // --> 0
test(-1.234e+2); // --> 1
test(-0.123); // ---> 3
test(-123.123); // ---> 3
test(-0.000123); // ---> 6
test(-1e-20); // --> 20
test(-1.2e-20); // --> 21
test(-1.23E-20); // --> 22
test(-1.23456789E-20); // --> 28
test(-10); // --> 0
test(-1.2e20); // --> 0
test(-1.2e+20); // --> 0
test(-1.2E100); // --> 0
test(-Infinity); // --> 0
I use this...
45555.54545456?.toString().split(".")[1]?.length
value null check before converting it to string, then convert it to string, split it, get the decimal part, add nullcheck, and get length, if there is number with decimals, you get the amount of those decimals, else you get undefined.
//console.log("should give error:", null.toString().split(".")[1]?.length);
console.log("should give undefined:", null?.toString().split(".")[1]?.length);
//console.log("should give error:", 45555.toString().split(".")[1]?.length);
console.log("should give undefined:", 45555?.toString().split(".")[1]?.length);
//console.log("should give error:", 45555?.toString().split(".")[1].length);
console.log("should give amount of decimals:", 45555.54545456?.toString().split(".")[1]?.length);
console.log("should return without decimals when undefined:", 45555.54545456.toFixed(undefined));
An optimized version of nick answer.
The function requires that n is a string. This function gets the decimal even if there all 0, like 1.00 -> 2 decimals.
function getDecimalPlaces(n) {
var i = n.indexOf($DecimalSeparator)
return i > 0 ? n.length - i - 1 : 0
}
console.log(getDecimalPlaces("5555.0")); // 1
console.log(getDecimalPlaces("5555")); // 0
console.log(getDecimalPlaces("555.5")); // 1
console.log(getDecimalPlaces("555.50")); // 2
console.log(getDecimalPlaces("0.0000005")); // 7
console.log(getDecimalPlaces("0.00000055")); // 8
console.log(getDecimalPlaces("0.00005500")); // 8
If you have very small values, use the below code:
Number.prototype.countDecimals = function () {
if (Math.floor(this.valueOf()) === this.valueOf()) return 0;
var str = this.toString();
if (str.indexOf(".") !== -1 && str.indexOf("-") !== -1) {
return parseInt(str.split("-")[1])+str.split("-")[0].split(".")[1].length-1
} else if (str.indexOf(".") !== -1) {
return str.split(".")[1].length || 0;
}
return str.split("-")[1] || 0;
}
var num = 10
console.log(num.countDecimals()) //0
num = 1.23
console.log(num.countDecimals()) //2
num = 1.454689451
console.log(num.countDecimals()) //9
num = 1.234212344244323e-7
console.log(num.countDecimals()) //22
Based on gion_13 answer I came up with this:
function decimalPlaces(n){
let result= /^-?[0-9]+\.([0-9]+)$/.exec(n);
return result === null ? 0 : result[1].length;
}
for (const x of [
0, 1.0, 1.00, 0.123, 1e-3, 3.14, 2.e-3, -3.14e-21,
5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8,
0.000006, 0.0000007,
0.123, 0.121, 0.1215
]) console.log(x, '->', decimalPlaces(x));
It fixes the returning 1 when there are no decimal places. As far as I can tell this works without errors.