Number.prototype.toFixed and FAQ

A

abozhilov

When i read FAQ. I see implementation of numberToFixed.
http://jibbering.com/faq/#formatNumber

Everything is good but why code use:

s = Math.round(n * Math.pow(10, digits)) + "";

Why when toFixed return string value, in code using mathematical
operation like this? I see potential risk from type overflow. How you
thinking about this code? Maybe existing better way for that?

Thanks.
 
A

abozhilov

My version with avoiding potential risk of type overflow:

function numberToFixed(n, d)
{
var p = Math.pow(10, d),
start = n & n,
end = Math.abs(Math.round((n - start) * p)),
i = 0;
if (end >= p)
{
start++;
i++;
}
end = ('' + end).substring(i);
return start + (d >= 1 ? '.' + padLeft(end, d, '0') : '');
}

padLeft still in FAQ on address:
http://jibbering.com/faq/#formatDate
 
A

abozhilov

And my code have troubles with type overflow.


start = n & n;

Here i have big trouble. Cast ToInt32. Any suggestion?
 
D

Dr J R Stockton

In comp.lang.javascript message <f1b67694-319f-433a-a735-516b000fc0d2@r3
9g2000yqm.googlegroups.com>, Sun, 13 Sep 2009 06:19:37, abozhilov
When i read FAQ. I see implementation of numberToFixed.
http://jibbering.com/faq/#formatNumber

Everything is good but why code use:

s = Math.round(n * Math.pow(10, digits)) + "";

Why when toFixed return string value, in code using mathematical
operation like this?

But, as the FAQ article says, toFixed does not always return the RIGHT
string. Some people care about that. The 0.07 in the FAQ should be
0.007.
I see potential risk from type overflow. How you
thinking about this code? Maybe existing better way for that?

Can you give the exact inputs for a test case which shows what you think
to be a potential problem?

If Math.round does not give an all-digit string, the line containing
/\D/ will detect it and give a substitute string. For finite numbers,
that only happens for more than about 15 digits in the desired result,
which does not often occur.


See <URL:http://www.merlyn.demon.co.uk/js-round.htm> ff., in particular
StrU etc.

And <URL:http://www.merlyn.demon.co.uk/js-round.htm#TRPF> will test the
body of a rounding function against a set of known-helpful test cases,
and against any arguments you provide.

A good conversion routine needs to give appropriate results for all
possible Numbers, including infinities and NaN, and also for anything
else that might be given as the first argument, including null,
undefined, and a function without any argument list. No input should
give an empty string or a machine error; no input should show as a
number, except for Numbers of about that value.
 
A

abozhilov

On Sep 14, 10:03špm, Dr J R Stockton <[email protected]>
wrote:

Hi Dr J R Stockton.
Can you give the exact inputs for a test case which shows what you think
to be a potential problem?

If Math.round does not give an all-digit string, the line containing
/\D/ will detect it and give a substitute string. šFor finite numbers,
that only happens for more than about 15 digits in the desired result,
which does not often occur.

ïf course. Try with number who's value is very close to 10 ^ 21. Like
this one:
var n = 123456789123456789123.1111111111111111111;
When apply n * Math.pow(10, d); We going after 10 ^ 21 and number will
be in exponential notation represent. Number.prototype.toFixed avoid
that.

In custom toFixed we can avoid that with expression like this:

n - parseInt(n);

And after that rounding only digit after decimal point. This will be
prevent "overflow".

Regards.
 
T

Thomas 'PointedEars' Lahn

abozhilov said:
Dr J R Stockton wrote:

Hi Dr J R Stockton.

This is a newsgroup, not private e-mail.
Оf course. Try with number who's value is very close to 10 ^ 21. Like
this one:
var n = 123456789123456789123.1111111111111111111;
When apply n * Math.pow(10, d); We going after 10 ^ 21 and number will
be in exponential notation represent. Number.prototype.toFixed avoid
that.

True, but

123456789123456789123.1111111111111111111.toFixed(19)

will of course not return

"123456789123456789123.1111111111111111111"

but

"123456789123456794624.0000000000000000000"
In custom toFixed we can avoid that with expression like this:

n - parseInt(n);

And after that rounding only digit after decimal point. This will be
prevent "overflow".

At least in JavaScript 1.8.1 this expression results in 0. The reason for
that is essentially that 123456789123456789123.1111111111111111111 % 1 ===
0. IOW: IEEE-754 doubles have not enough precision to store that value.

See also: <http://babbage.cs.qc.edu/IEEE-754/Decimal.html>


PointedEars
 
D

Dr J R Stockton

In comp.lang.javascript message <e5db465c-ab0a-431d-8e93-a7d03e0f50c7@f3
3g2000vbm.googlegroups.com>, Sun, 20 Sep 2009 03:23:47, abozhilov
ïf course. Try with number who's value is very close to 10 ^ 21. Like
this one:
var n = 123456789123456789123.1111111111111111111;

That will, in an IEEE Double, be stored as +123456789123456794624.0
which is 1.673151487196708142590750867384485900402069091796875 * 2^66.
When apply n * Math.pow(10, d); We going after 10 ^ 21 and number will
be in exponential notation represent. Number.prototype.toFixed avoid
that.

It is unreasonable to call for an output format purporting to represent
more than about 15 decimal digits. Native toFixed hides that; the
method in the FAQ always gives a reasonable representation of the value.

To get 123456789123456794624.00 for toFixed(2) of your number requires
In custom toFixed we can avoid that with expression like this:

n - parseInt(n);

And after that rounding only digit after decimal point. This will be
prevent "overflow".

I assume you mean n = parseInt(n) . That will truncate, whereas
toFixed should round (away from zero IIRC). You would perhaps do better
to use n = Math.round(n).

The current FAQ maintainer has altered that section, which now
unfortunately has heavy use of the substring "Fixed". The original
code, with a similar core algorithm, was not written to emulate toFixed;
rather, it was written to fulfil a similar need to toFixed.

A side-effect of his changes is that I can no longer conveniently test
the code, with particular regard to special cases.

See <URL:http://www.merlyn.demon.co.uk/js-round.htm> ff.
 
A

abozhilov

At the moment, my last version.

Number.toFixed = function()
{
var range = Math.pow(10, 21);
return function(n, d)
{
n = +n;
if (isNaN(n))
{
return 'NaN';
}
if (range <= Math.abs(n))
{
return '' + n;
}
var p = Math.pow(10, d),
s = parseInt(n),
e = Math.round(Math.abs(n - s) * p),
i = 0;
if (e >= p)
{
s++;
i++;
}
e = ('' + e).substring(i);
return s + (d >= 1 ? '.' + padLeft(e, d, '0') : '');
}
}();

Close to native toFixed, but of course is much slower from native
toFixed. But for my, this is exactly what i need.
If anybody have suggestion for speed, i will be very happy to read.
 
D

Dr J R Stockton

In comp.lang.javascript message <31a60248-9d29-42e6-a522-6f1ae8dffe25@o1
3g2000vbl.googlegroups.com>, Sun, 20 Sep 2009 13:31:20, abozhilov
At the moment, my last version.

last" "= "latest" !! said:
Number.toFixed = function()
{
var range = Math.pow(10, 21);
return function(n, d)
{
n = +n;
if (isNaN(n))
{
return 'NaN';
}
if (range <= Math.abs(n))
{
return '' + n;
}
var p = Math.pow(10, d),
s = parseInt(n),
e = Math.round(Math.abs(n - s) * p),
i = 0;
if (e >= p)
{
s++;
i++;
}
e = ('' + e).substring(i);
return s + (d >= 1 ? '.' + padLeft(e, d, '0') : '');
}
}();

Close to native toFixed, but of course is much slower from native
toFixed. But for my, this is exactly what i need.
If anybody have suggestion for speed, i will be very happy to read.



Fails if used where the value is undefined or null, I think.

var range = Math.pow(10, 21); //// or var range = 1e21?
and you can use if (1e21 <= Math.abs(n))

There is no need to use
function()
...
}();
which would confuse intended FAQ readers.

It would be better to call it toFixxed; then in testing there is no risk
of getting the result of toFixed instead. A user who wants a new
toFixed can always rename it.

In fact, testing in IE8, 0.006.toFixed(2) gives 0.00
and in FF it gives 0.01 - which suggests that native toFixed is really
being called. The utter failure of toFixxed also suggests that.

But the presence of return function(n, d) and the absence of
"this" suggest that your code is not plug-compatible with toFixed.

You did not use Number.prototype.toFixed = function()


When that sort of code is presented here, test routines and their I/O
data should also be presented - one can then see what may have been
untested.

Try changing string NaN in your code to, say, NaaN, and see if you can
get it as an output. If you cannot, then that statement can serve no
useful purpose --- and it is like the 13th stroke of the clock, which
casts doubt on all else.


Try www.gum.ru (at the turn of the GMT hour).
 
A

abozhilov

In comp.lang.javascript message <31a60248-9d29-42e6-a522-6f1ae8dffe25@o1
3g2000vbl.googlegroups.com>, Sun, 20 Sep 2009 13:31:20, abozhilov


                                        "last" "= "latest" !! <g>

Sorry. Currently my english isn't very well.
Apologize for that.
Fails if used where the value is undefined or null, I think.

Here you are incorrect.
n = +n; //ToNumber(n) 9.3 in ECMA3
        var range = Math.pow(10, 21); //// or var range = 1e21?
        and you can use         if (1e21 <= Math.abs(n))

There is no need to use
          function()
        ...
        }();
which would confuse intended FAQ readers.

Good point.
It would be better to call it toFixxed; then in testing there is no risk
of getting the result of toFixed instead.  A user who wants a new
toFixed can always rename it.

In fact, testing in IE8,    0.006.toFixed(2)   gives 0.00
and in FF it gives 0.01 - which suggests that native toFixed is really
being called.  The utter failure of toFixxed also suggests that.

Again good point.
Try changing string NaN in your code to, say, NaaN, and see if you can
get it as an output.  If you cannot, then that statement can serve no
useful purpose --- and it is like the 13th stroke of the clock, which
casts doubt on all else.

                        Trywww.gum.ru(at the turnof the GMT hour).

What are you mean? Here i don't understand exactly what are you mean
with that.

After your post, and my tests. I have latest version.

Number.format = function(num, fraction)
{
num = +num;
if (isNaN(num))
{
return 'NaN';
}
if (1e21 <= Math.abs(num))
{
return '' + num;
}
var end = num % 1,
start = num - end,
f = Math.min(21, fraction),
pow = Math.pow(10, f);
end = Math.round(Math.abs(end) * pow);
if (end == pow)
{
start++;
end = 0;
}
return start + (f > 0 ? '.' + padLeft(end, f, '0') : '');
}

With fraction value between, 0 - 21. If fraction less than 0 will
return results like fraction value equal to 0. If fraction great than
21, results will be like fraction equal 21.

For test cases. And comparing results see:
<URL:http://213.130.84.45/Number/toFixed.html>
 
D

Dr J R Stockton

In comp.lang.javascript message <2430bcc7-e975-41dc-bbe5-258349ea3bb2@l3
4g2000vba.googlegroups.com>, Tue, 22 Sep 2009 05:02:46, abozhilov
Here you are incorrect.
n = +n; //ToNumber(n) 9.3 in ECMA3

Representing null as zero or undefined as NaN is a failure, in my
opinion, in a general-purpose routine.

What are you mean? Here i don't understand exactly what are you mean
with that.

Your browser has mis-quoted that, and so perhaps it had mis-
displayed it. Try <URL:www.gum.ru> - but only for amusement.



After your post, and my tests. I have latest version.

Number.format = function(num, fraction)

That's not "plug-compatible" with .toFixed; and ISTM that it must be
called as a method of a number whose value is not used. I'd expect num
not to be in the argument list, and for
{
num = +num;

to be num = +this ;

It should not be necessary to use "call", IMHO.


if (isNaN(num))
{
return 'NaN';
}
if (1e21 <= Math.abs(num))
{
return '' + num;
}
var end = num % 1,
start = num - end,
f = Math.min(21, fraction),
pow = Math.pow(10, f);
end = Math.round(Math.abs(end) * pow);
if (end == pow)
{
start++;
end = 0;
}
return start + (f > 0 ? '.' + padLeft(end, f, '0') : '');
}

With fraction value between, 0 - 21. If fraction less than 0 will
return results like fraction value equal to 0. If fraction great than
21, results will be like fraction equal 21.

For test cases. And comparing results see:
<URL:http://213.130.84.45/Number/toFixed.html>



I have adjusted it to fit and put it into
<URL:http://www.merlyn.demon.co.uk/js-rndg2.htm>. That way I can
automatically test against many inputs, and manually test too. I've not
uploaded it yet; the previous version of the page is dated 2009-04-29.

With some negative inputs, it gives wrong but plausible answers; so
negative inputs need testing for.

If there are no negative inputs, the Math.abs may not be needed?

However, a calculation that, ideally, would give zero may actually,
because of rounding errors, give a small negative number. So that must
be allowed, and be treated as zero.

When a number to be rounded to N places is entered as a literal with a
value exactly half-way between two adjacent N-place numbers, ISTM that
yours and that in the FAQ may round differently. I don't think that is
important.

However, for inputs that are represented exactly by an IEEE Double, the
Standard specifies rounding away from zero, and not Bankers Rounding.
Yours is OK there.

There is one other algorithm on that page, TwoDP in "Satisfactory
Conversions to String", which uses the % operator; and the operator is
used very differently. So, at least as far as this newsgroup goes, you
seem to have a New Method. On the other hand, looking for ++, your
method resembles that of TMoney.

I think toFixed(0) is wrong; it should give a decimal point. If no
point is wanted, one can just Math.round the number.
 
D

Dr J R Stockton

In comp.lang.javascript message <2430bcc7-e975-41dc-bbe5-258349ea3bb2@l3
4g2000vba.googlegroups.com>, Tue, 22 Sep 2009 05:02:46, abozhilov


Having adjusted your routine to fit with the auto-testing in
<URL:http://www.merlyn.demon.co.uk/js-round.htm#BoF> and
<URL:http://www.merlyn.demon.co.uk/js-rndg2.htm>, I've been able to
attend to some details and have :


function Sign(X) { return X>0 ? "+" : X<0 ? "-" : " " }

function ChrsTo(S, L, C) {
for (var i = String(S).length ; i < L ; i++) S = C + S
return S }


function Abozhilov(X, N) { var Y // to head.tail
if ((X==null) || !(1e21 > (Y=Math.abs(X)))) return '' + X
N = Math.min(21, N)
var tail = Y % 1, head = Y - tail, pow = Math.pow(10, N) ;
tail = Math.round(tail * pow)
if (tail == pow) { head++ ; tail = 0 }
return Sign(X) + head + (N > 0 ? '.' + ChrsTo(tail, N, '0') : '') }


Since comparison of NaN always gives false, I've been able to combine
the NaN and range tests in one, and then to include a null test. I've
added handling of negative numbers. Math.abs is not needed for tail.

Function ChrsTo matches PadLeft.

Function Sign returns "+" or " " or "-", but can easily be changed. I
have a version which discriminates between +0 and -0.


If the numeric test against 1e21 is replaced by a RegExp test that
Y.toString() contains no letters, the null test might not be needed but
the range might be reduced.
 
A

abozhilov

Since comparison of NaN always gives false

I don't thing isNaN, function of `Global Object` compare with
Number.NaN or anything else NaN value. But you are right if compare:

NaN === NaN; //always return false

In ECMA 3, 8.5:
to ECMAScript code, all NaN values are indistinguishable from each
other.

In ECMA3 specification description about isNaN is very short. How to
do it internally isNaN?

After your comment, i wrote simple method called isNaaN, and put in
global execution context:
function isNaaN(input)
{
var num = +input;
return num !== num;
}

Do you see any trouble? See my test cases:
document.write([
"isNaaN(NaN) : " + isNaaN(NaN), //true
"isNaaN(Infinity) : " + isNaaN(Infinity), //false
"isNaaN(-Infinity) : " + isNaaN(-Infinity), //false
"isNaaN(2e+31) : " + isNaaN(2e+31), //false
"isNaaN(1e-8) : " + isNaaN(1e-8), //false
"isNaaN(10) : " + isNaaN('10'), //false
"isNaaN(is not a number) : " + isNaaN('is not a number'), //true
"isNaaN(0xFF) : " + isNaaN(0xFF), //false
"isNaaN(null) : " + isNaaN(null), //false
"isNaaN(false) : " + isNaaN(false), //false
Function Sign returns "+" or " " or "-", but can easily be changed.  I
have a version which discriminates between +0 and -0.

Maybe you talk about:
function Sygn(X) {
return X + 1 / X < 0 ? "-" : "+";
}
<URL:http://www.merlyn.demon.co.uk/js-rndg1.htm#SF>
Looks good, i thing about another method, but at the moment i don't
have. Yours is OK.
 
D

Dr J R Stockton

REPOST:

In comp.lang.javascript message <[email protected]
rlyn.invalid>, Wed, 23 Sep 2009 22:40:47, Dr J R Stockton
function Abozhilov(X, N) { var Y // to head.tail
if ((X==null) || !(1e21 > (Y=Math.abs(X)))) return '' + X
N = Math.min(21, N)
var tail = Y % 1, head = Y - tail, pow = Math.pow(10, N) ;
tail = Math.round(tail * pow)
if (tail == pow) { head++ ; tail = 0 }
return Sign(X) + head + (N > 0 ? '.' + ChrsTo(tail, N, '0') : '') }

Comparing that with native toFixed gives browser-dependent results.

It reminds me that MSIE toFixed has an additional error with large
numbers, not mentioned in the FAQ.

In Firefox, that code exactly agrees with toFixed apart from the sign
character up to around X = 10^16. Above that, they give different
strings of digits; but subtracting the strings always gives zero (I've
not checked whether it is +0 or -0, though). The toFixed result gives
the illusion of superior accuracy; a glance at ISO/IEC 16262 suggest
that it is required to do so.

An exact full-range implementation of ISO/IEC 16262 15.7.4.5 10 in
JavaScript will require multi-length arithmetic. It could be an
interesting challenge and test, but of no advantage in most Web pages.
(IE users apart).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,085
Messages
2,570,597
Members
47,218
Latest member
GracieDebo

Latest Threads

Top