I read some other's code, there is some piece of code below. I am wondering What does the method do with num?
formatNumber: function (num, digit) {
var pow = Math.pow(10, digit || 5);
return Math.round(num * pow) / pow;
}
BTW when I running
formatNum(11.267898, 5), it gave me 11.2679, is this OK?
Essentially, the function returns the number with certain precision. The precision is digit, which is 5 if not provided.
The return part essentially brings that many values (equal to digit) decimal right to left and then discard the rest and finally divides again to get the original value reduces to precision of digit.
Regarding BTW edit -
The value obtained is correct. See details below
When you call formatNum(11.267898, 5), you're asking the number to round to 5 digit precision and your number has 6 digit precision - precision is digits after the dot.
Now when you call num * pow the number becomes 1126789.8 and when you round this number, it rounds to closest integer which is 11.26790. Finally when you divide it by pow (100000), the number becomes 11.2679, discarding last 0 as trailing Zero in precision is pointless.
That is a really poor piece of code.
First, the naming conventions don't match what the code does at all.
The function name formatNumber() suggests that it formats a number. In other words, it ought to produce a string representation of a number, formatted in some way. But the function doesn't do this, it returns another number. That makes no sense. Numbers don't have a format, they are just numbers.
The parameter name digit sounds like it would contain a single digit. But it doesn't. It contains a count of digits that you want to round the number to. When you name things, singular and plural matter!
It gets worse.
As you found, the function doesn't even work. In your example, formatNum(11.267898,5) returns the number 11.2679. Why did it give you four digits when you asked for five? The result you were expecting was 11.26790, wasn't it? Well, of course that is identical to 11.2679, if we're talking about numbers. But what good does that do you when you wanted five digits?
Or to take a ridiculously simple example: formatNumber(1,2). You might expect that to produce 1.00, but it produces 1. Of course that is really the same value, but not formatted the way you want.
Now we go from the ridiculous to the sublime.
JavaScript has always had a built-in function that does exactly what we would expect formatNumber() to do: number.toFixed(digits). This does proper rounding and always returns the number of digits after the decimal point that you ask for. And of course, to be able to do that, it returns a string, not a number.
If we try these examples using .toFixed() they work as expected:
(11.267898).toFixed(5) returns the string "11.26790".
(1).toFixed(2) returns "1.00".
And so on, for just about anything you can throw at it. (It gives up on numbers with magnitude too large and uses exponential notation instead.)
Note that the parentheses around the first number in those examples are just needed to avoid a syntax error; in most cases you'd be using a variable and they would not be required, e.g.
myNumber.toFixed(2)
To summarize, not only does formatNumber() not do what it says and not anything useful, it was never needed in the first place!
Related
The question sprang up from reading the answers to this recent post.
The OP simply wants to know how to cut off a string at the second decimal point: e.g. '2.346' => 2.34
One of the users provided this answer:
function tofixed(str){
return parseInt(str * 1000 / 10) /100
}
I tested it with a very large number and got this result:
console.log(tofixed('53219247129812312132.453'))
//Result: 0.05
I got curios and started digging. The quirk seems to lie with parseInt, because I can easily run this:
console.log(53219247129812312132 * 1000 / 10)
OR
console.log("53219247129812312132" * 1000 / 10)
And get the proper result. But why do I get 5 when I run:
console.log(parseInt("53219247129812312132" * 1000))
It seems to always return the first character of the string. I thought perhaps the number is too large for parseInt to parse, but why, then can I parse this without any issues:
console.log(parseInt("5321924712981231213212323232323"))
That multiplication seems to throw parseInt for a loop when combined with a large number. Can someone explain this behavior to me?
What parseInt does is:
The parseInt() function parses a string argument and returns an integer of the specified radix (the base in mathematical numeral systems).
If the argument is not a string to begin with, it gets converted into a string.
When a large number is converted into a string, it starts with the most significant digit, followed by a . and eventually e:
console.log(String(53219247129812312132000));
console.log(parseInt(53219247129812312132000))
Since 5 is the starting "integer" part of the string before the dot, that's the result. (The engine stops trying to parse the string once it encounters the dot, since it considers characters past that to not be part of the integer part of the number.)
I am having an issue with the way Javascript is rounding numbers when hitting 0.5.
I am writing levies calculators, and am noticing a 0.1c discrepancy in the results.
The problem is that the result for them is 21480.705 which my application translates into 21480.71, whereas the tariff says 21480.70.
This is what I am seeing with Javascript:
(21480.105).toFixed(2)
"21480.10"
(21480.205).toFixed(2)
"21480.21"
(21480.305).toFixed(2)
"21480.31"
(21480.405).toFixed(2)
"21480.40"
(21480.505).toFixed(2)
"21480.51"
(21480.605).toFixed(2)
"21480.60"
(21480.705).toFixed(2)
"21480.71"
(21480.805).toFixed(2)
"21480.81"
(21480.905).toFixed(2)
"21480.90"
Questions:
What the hell is going on with this erratic rouding?
What's the quickest easiest way to get a "rounded up" result (when hitting 0.5)?
So as some of the others already explained the reason for the 'erratic' rounding is a floating point precision problem. You can investigate this by using the toExponential() method of a JavaScript number.
(21480.905).toExponential(20)
#>"2.14809049999999988358e+4"
(21480.805).toExponential(20)
#>"2.14808050000000002910e+4"
As you can see here 21480.905, gets a double representation that is slightly smaller than 21480.905, while 21480.805 gets a double representation slightly larger than the original value. Since the toFixed() method works with the double representation and has no idea of your original intended value, it does all it can and should do with the information it has.
One way to work around this, is to shift the decimal point to the number of decimals you require by multiplication, then use the standard Math.round(), then shift the decimal point back again, either by division or multiplication by the inverse. Then finally we call toFixed() method to make sure the output value gets correctly zero-padded.
var x1 = 21480.905;
var x2 = -21480.705;
function round_up(x,nd)
{
var rup=Math.pow(10,nd);
var rdwn=Math.pow(10,-nd); // Or you can just use 1/rup
return (Math.round(x*rup)*rdwn).toFixed(nd)
}
function round_down(x,nd)
{
var rup=Math.pow(10,nd);
var rdwn=Math.pow(10,-nd);
return (Math.round(x*-rup)*-rdwn).toFixed(nd)
}
function round_tozero(x,nd)
{
return x>0?round_down(x,nd):round_up(x,nd)
}
console.log(x1,'up',round_up(x1,2));
console.log(x1,'down',round_down(x1,2));
console.log(x1,'to0',round_tozero(x1,2));
console.log(x2,'up',round_up(x2,2));
console.log(x2,'down',round_down(x2,2));
console.log(x2,'to0',round_tozero(x2,2));
Finally:
Encountering a problem like this is usually a good time to sit down and have a long think about wether you are actually using the correct data type for your problem. Since floating point errors can accumulate with iterative calculation, and since people are sometimes strangely sensitive with regards to money magically disappearing/appearing in the CPU, maybe you would be better off keeping monetary counters in integer 'cents' (or some other well thought out structure) rather than floating point 'dollar'.
The why -
You may have heard that in some languages, such as JavaScript, numbers with a fractional part are calling floating-point numbers, and floating-point numbers are about dealing with approximations of numeric operations. Not exact calculations, approximations. Because how exactly would you expect to compute and store 1/3 or square root of 2, with exact calculations?
If you had not, then now you've heard of it.
That means that when you type in the number literal 21480.105, the actual value that ends up stored in computer memory is not actually 21480.105, but an approximation of it. The value closest to 21480.105 that can be represented as a floating-point number.
And since this value is not exactly 21480.105, that means it is either slightly more than that, or slightly less than that. More will be rounded up, and less will be rounded down, as expected.
The solution -
Your problem comes from approximations, that it seems you cannot afford. The solution is to work with exact numbers, not approximate.
Use whole numbers. Those are exact. Add in a fractional dot when you convert your numbers to string.
This works in most cases. (See note below.)
The rounding problem can be avoided by using numbers represented in
exponential notation:
function round(value, decimals) {
return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}
console.log(round(21480.105, 2).toFixed(2));
Found at http://www.jacklmoore.com/notes/rounding-in-javascript/
NOTE: As pointed out by Mark Dickinson, this is not a general solution because it returns NaN in certain cases, such as round(0.0000001, 2) and with large inputs.
Edits to make this more robust are welcome.
You could round to an Integer, then shift in a comma while displaying:
function round(n, digits = 2) {
// rounding to an integer is accurate in more cases, shift left by "digits" to get the number of digits behind the comma
const str = "" + Math.round(n * 10 ** digits);
return str
.padStart(digits + 1, "0") // ensure there are enough digits, 0 -> 000 -> 0.00
.slice(0, -digits) + "." + str.slice(-digits); // add a comma at "digits" counted from the end
}
What the hell is going on with this erratic rouding?
Please reference the cautionary Mozilla Doc, which identifies the cause for these discrepancies. "Floating point numbers cannot represent all decimals precisely in binary which can lead to unexpected results..."
Also, please reference Is floating point math broken? (Thank you Robby Cornelissen for the reference)
What's the quickest easiest way to get a "rounded up" result (when hitting 0.5)?
Use a JS library like accounting.js to round, format, and present currency.
For example...
function roundToNearestCent(rawValue) {
return accounting.toFixed(rawValue, 2);
}
const roundedValue = roundToNearestCent(21480.105);
console.log(roundedValue);
<script src="https://combinatronics.com/openexchangerates/accounting.js/master/accounting.js"></script>
Also, consider checking out BigDecimal in JavaScript.
Hope that helps!
I've made a program to convert bases of numbers. I thought that, in the event that there are no characters A-F in the output, it would be convenient to return the number as a Number instead of a String (even though I'd be returning base 2 numbers as regular decimal numbers, whatever).
For some reason, when I converted A3B232 to Binary (which should be 10100001101011001002 [Wolfram|Alpha source]), I got 1010000110101100200. There's a two in there, that isn't binary! The final line of code which returns the value is:
return (toBase <= 10) ? Number(result) : result;
This worked properly for basically all tests, until I decided to use that base-32 number above. I thought my program was doing the division/remainder step incorrect, so I had it print out the result just before it returns the value. It was correctly giving out 1010000110101100100. So, I tested the Number() method directly in Chrome's console. This is what happened:
> Number("1010000110101100100")
> 1010000110101100200
Am I misunderstanding what the Number() method does? Why is it converting a string made up of all Ones and Zeroes to a number with Ones, Zeroes, and Twos?
What you're doing when calling Number("1010000110101100100") is passing a string, which it is trying to turn it into a number, but that number is higher than JavaScript can count, which is 9,007,199,254,740,991 (also known as Number.MAX_SAFE_INTEGER)
Basically, any number over 9,007,199,254,740,991 can't reliably be used because JavaScript uses double-precision floating-point format numbers as specified in IEEE 754
Here's some examples, the following input numbers "should" be the same as the output, but aren't because they've gone over the MAX_SAFE_INTEGER. Some are, but it's not reliable as demonstrated in the Bonus Fun With LARGE Numbers section below.
Number("9007199254740992") // 9007199254740992
Number("9007199254740993") // 9007199254740992
Number("9007199254740994") // 9007199254740994
Number("9007199254740995") // 9007199254740996
Number("9007199254740996") // 9007199254740996
Number("9007199254740997") // 9007199254740996
Number("9007199254740998") // 9007199254740998
Number("9007199254740999") // 9007199254741000
If you still want to convert hex to binary, you need to use parseInt and Number.prototype.toString and keep it as a string.
const hex = "A3B2";
const binary = parseInt(hex, 16).toString(2);
console.log(binary);
Bonus Fun With LARGE Numbers
The following is true!
console.log(9007199254740992 === 9007199254740993);
console.log(1010000110101100200 === 1010000110101100100)
Documentation
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString
#AnonymusSB said is right. i want add few details to it.
So when you Number(value) it internally calls ToNumber to calculate the value. when you supply a string. it have a two step process to change it number.
First it calculate the Mathematical value (MV) of the string.
In second process it rounds up the calculated value.(when you exceed the limit of Math.MAX_SAFE_INTEGER it round of the number).
console.log(Number("9007199254740996"))
console.log(Number("9007199254740997"), "rounded down")
console.log(Number("9007199254740998"))
console.log(Number("9007199254740999") ,"rounded up")
EcmaScript
The problem: JavaScript returns 20160107190308484 for parseInt("20160107190308485")
Not the question: I do not need a workaround; I got one.
Not the question II: I do not need an explanation why this can happen, and how numbers are stored and so on.
How are numbers as input for parseInt defined? Why do I get a wrong result, not a NaN?
I only find in documentation how parseInt handles literals and so on, that only the first numbers are interpreted and ...
As W3C or MDN
Note: Only the first number in the string is returned!
Note: Leading and trailing spaces are allowed.
Note: If the first character cannot be converted to a number, parseInt() returns NaN.
I can't find anything that says "123456789202232334234212312311" is not a "number". And nothing like "a number must be in the range of -BIGINT .. +BIGINT or so. And I could not find a hint for errors thrown.
I just get 20160107190308484 for "20160107190308485" (two browsers tested) and other pairs like:
20160107155044520, 20160107155044522
20160107002720970, 20160107002720967
20160107000953860, 20160107000953859
For me this feels like a hole in ECMA.
Is there a number defined that is allowed as input for parseInt?
For those who are interested in "how came?":
I stumbled over that problem with a small logic, that converts classes and ids into an object holding the information like
class="book240 lang-en" id="book240page2212"
converting to an object like:
{
book: 240
page: 2212
lang: "en"
}
Notice, that INTs are numbers, not strings.
Therefore I have a loop that makes that generic:
for n in some_regexp_check_names
# m hold a Regexp result arg[1] is the interest:
nr = parseInt(m[1]) # Make it a number
#--------- Here the funny fault
if nr > 0 # Check if number & > 0 all my ids are > 0
out_obj[n]=nr # Take the number
else
out_obj[n]=m[1] # Take the string as is
All this is nice and is working with "real strings" and "real (int) ids", and then I had a situation where dynamically semi uuids out of datetime where created: "temp-2016-01-07-19-03-08.485" - and still all fine, but I had the idea to remove all constant chars from this (i.e. temp-.) and BANG, because the string "20160107190308485" gives 20160107190308484 as parseInt.
And it took me only ~3 hours to find that error.
How are numbers as input for parseInt defined?
I think it's pretty clearly defined in the spec (§18.2.5):
Let mathInt be the mathematical integer value that is represented by Z
in radix-R notation, using the letters A-Z and a-z for digits with
values 10 through 35. (However, if R is 10 and Z contains more than 20
significant digits, every significant digit after the 20th may be
replaced by a 0 digit, at the option of the implementation; and if R
is not 2, 4, 8, 10, 16, or 32, then mathInt may be an
implementation-dependent approximation to the mathematical integer
value that is represented by Z in radix-R notation.)
Let number be the Number value for mathInt.
For that last step, the section for the Number type (§6.1.6) specifies:
In this specification, the phrase “the Number value for x” where x
represents an exact nonzero real mathematical quantity (which might
even be an irrational number such as π) means a Number value chosen in
the following manner. Consider the set of all finite values of the
Number type, with −0 removed and with two additional values added to
it that are not representable in the Number type, namely 21024 (which
is +1 × 253 × 2971) and −21024 (which is −1 × 253 × 2971). Choose the
member of this set that is closest in value to x. If two values of the
set are equally close, then the one with an even significand is
chosen; for this purpose, the two extra values 21024 and −21024 are
considered to have even significands. Finally, if 21024 was chosen,
replace it with +∞; if −21024 was chosen, replace it with −∞; if +0
was chosen, replace it with −0 if and only if x is less than zero; any
other chosen value is used unchanged. The result is the Number value
for x. (This procedure corresponds exactly to the behaviour of the
IEEE 754-2008 “round to nearest, ties to even” mode.)
Why do I get a wrong result not a NaN?
You get a rounded result (with the maximum precision available in that range) - and there's nothing wrong with that, your input is a valid number, not NaN.
Although Bergis answer is correct, I want to tell what my real mistake was:
Don't think of parseInt of a function, that parses into an INTeger like known from C as 16, 32 or 64 bit value. It parse the number as long as there are valid digits, so if the number is to big (does not fit exactly into eg 64 bit), you dont get an error, you just get a rounded value like working with float. If you want to be on the safe side: check the result against Number.MAX_SAFE_INTEGER.
The maximum integer value in JavaScript is 2^53 == 9 007 199 254 740 992. This is because Numbers are stored as floating point is a 52 bit mantissa.
Read more: http://web.archive.org/web/20161117214618/http://bjola.ca/coding/largest-integer-in-javascript/
I am having a little problem with rounding numbers which are brought in from html.
For example a value extracted from <input id="salesValue"> using var salesValue = $("salesValue").val() would give me a text value.
So if I did something like var doubleSalesValue = salesValue + salesValue; , it would return the number as a concatenation instead of summation of the two values.
I could use var doubleSalesValue = salesValue * 2.0; which does return the value which is to multiple decimal places. However, if I did want to use the other method, how can I approach the situation.
What methods do you use? I have created a function which I run on each number where I want to restrict the decimal places along with converting the type to number
function round(number, figure){
return Number(Number(number).toFixed(figure));
}
I have to run Number initially to make sure that the value is converted to type number and has the method toFixed, otherwise it would throw an error here. Then I have to round the number again to the number of decimal places as required by the function, and somehow after running the toFixed method the number would sometimes turn to a string.
So, I decided to run the Number function Number(number).toFixed(figure)
Is there anything else or any different paradigm that you follow?
EDIT: I want to know if what I am doing here is conventional or are there better methods for this in general?
If you want to round it to 2 decimals you can simply do this:
var roundedNum = Math.round(parseFloat(originalNum) * 100) / 100;
Regarding your question:
and somehow after running the toFixed method the number would sometimes turn to a string.
I suggest next time read the dox a bit better https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed which says:
Returns
A string representation of number that does not use exponential
notation and has exactly digits digits after the decimal place. The
number is rounded if necessary, and the fractional part is padded with
zeros if necessary so that it has the specified length. If number is
greater than 1e+21, this method simply calls
Number.prototype.toString() and returns a string in exponential
notation.