Math.PI.toString(16) -> 3.243f6a8885a3 and back

D

Dr J R Stockton

In comp.lang.javascript message <[email protected]>,
I had: (i < 0 ? -1 : 1). But *you* pointed out to me that `i' would be 0
if the absolute value of the represented number would be less than 1.

Indeed you did; and, as you know, your test does not suffice, since it
does not always give the sign of the number 'i'.

I repeat - you need to use the sign of the number 'i'.

You so frequently insist that others search the archives; you should do
so yourself. The matter was referred to sometime in the years 200x.
Careful reading of ECMA 262 3/5 could help you.
 
T

Thomas 'PointedEars' Lahn

Scott said:
Thomas said:
Scott said:
Ry Nohryb wrote:
Scott Sauyet wrote:
[ ... ]
(I should have included this line:)
| var allDigits = "0123456789abcdefghijklmnopqrstuvwxyz",
parseFraction = function(str, base) {
if (!str) return 0;
var digits = str.split(""), total = 0;
for (var i = digits.length; i--;) {
total += allDigits.indexOf(digits);
total /= base;
}
return total;
};
[ ... ]
I like that parseFraction() of yours, it's awesome. Good idea.
Thanks. I'm not sure if it has any practical advantages, but it's at
least fairly clear mathematically.


It is clear(er), but it is unfortunately not mathematically sound because
we are dealing with *floating-point* arithmetics here, where precision is
limited [ ... ]
[ example elided ]


Yes, I understand that we are dealing with an implementation of
IEEE-754. But I don't see how that makes my algorithm mathematically
unsound. It is certainly not as efficient as the one you used, but I
think it might avoid some rounding issues.


You are doing *more* floating-point operations, how can you have *less*
rounding issues?
For instance, if parseFloat1 is the function you posted earlier [1]
and parseFloat2 is the one I posted [2], then in JavaScript 1.8.2

parseFloat1("0.r3j6f0mqo4fr3j6f0m", 36).toString(36)

yields "0.r3j6f0mqo3m", whereas

parseFloat2("0.r3j6f0mqo4fr3j6f0m", 36).toString(36))

yields "0.r3j6f0mqo4f", clearly a better approximation.

No doubt about that, although I think you have the test case backwards. I
have since tested my approach more thoroughly and accepted that {1,198} is
flawed as it sacrifices too much precision.

You need to compare the return value of yours against one of Jorge's that
works (that in
<99e57eb6-f5c7-410e-a40f-07ae48af2a3d@b21g2000vbh.googlegroups.com> returns
NaN with your test case in JavaScript 1.8.2) instead.


PointedEars
 
T

Thomas 'PointedEars' Lahn

Dr said:
Thomas 'PointedEars' Lahn posted:

Indeed you did; and, as you know, your test does not suffice, since it
does not always give the sign of the number 'i'.

I repeat - you need to use the sign of the number 'i'.

Since the value of `i' is the return value of `parseInt(s, base)' here, in
the border case that `s' represents a number which absolute value is less
than 1, `i' has no sign or IOW its sign is always positive. It is therefore
hard to see how its sign could be useful.
You so frequently insist that others search the archives; you should do
so yourself. The matter was referred to sometime in the years 200x.
Careful reading of ECMA 262 3/5 could help you.

Instead of making yet another lame attempt at an ad hominem attack, and
speaking in riddles, you could just have said how you would do it. Suppose,
just suppose, that I do exactly what you just did, then you are no better
than me. This should give you pause.


PointedEars
 
R

Ry Nohryb

(...)
You so frequently insist that others search the archives; you should do
so yourself.  The matter was referred to sometime in the years 200x. (....)

Thanks for such a valuable pointer: we'll dig into 10 years of
threads.
 
R

Ry Nohryb

(...)
It's an interesting result, but I'm not sure how much that really
tells us about accuracy. (...)

How about this one ?

var exp= -1;
var inc= 1e-1;
var i= inc;

while ( inc && (i === parseFloat(i.toString(base), base)) ) {
var iSave= i;
i+= (inc= Math.pow(10, --exp));
}
 
S

Scott Sauyet

Thomas said:
Scott said:
Thomas said:
Scott Sauyet wrote:
Ry Nohryb wrote:
Scott Sauyet wrote:
[ ... ]
(I should have included this line:)
|    var allDigits = "0123456789abcdefghijklmnopqrstuvwxyz",
parseFraction = function(str, base) {
  if (!str) return 0;
  var digits = str.split(""), total = 0;
  for (var i = digits.length; i--;) {
    total += allDigits.indexOf(digits);
    total /= base;
  }
  return total;
};
[ ... ]
I like that parseFraction() of yours, it's awesome. Good idea.
Thanks.  I'm not sure if it has any practical advantages, but it's at
least fairly clear mathematically.
It is clear(er), but it is unfortunately not mathematically sound because
we are dealing with *floating-point* arithmetics here, where precision is
limited [ ... ]
[ example elided ]

Yes, I understand that we are dealing with an implementation of
IEEE-754.  But I don't see how that makes my algorithm mathematically
unsound.  It is certainly not as efficient as the one you used, but I
think it might avoid some rounding issues.

You are doing *more* floating-point operations, how can you have *less*
rounding issues?


By being more precise. Your algorithm seems to throw away more
information than mine. At least a few random tests seem to indicate
so.

For instance, if parseFloat1 is the function you posted earlier [1]
and parseFloat2 is the one I posted [2], then in JavaScript 1.8.2
    parseFloat1("0.r3j6f0mqo4fr3j6f0m", 36).toString(36)
yields "0.r3j6f0mqo3m", whereas
    parseFloat2("0.r3j6f0mqo4fr3j6f0m", 36).toString(36))
yields "0.r3j6f0mqo4f", clearly a better approximation.

No doubt about that, although I think you have the test case backwards.  

No, I've posted a test script at

<http://scott.sauyet.com/Javascript/Test/2010-06-03a/>

This case is

<http://scott.sauyet.com/Javascript/Test/2010-06-03a/?
base=36&value=0.r3j6f0mqo4fr3j6f0m>

I have since tested my approach more thoroughly and accepted that {1,198}is
flawed as it sacrifices too much precision.

If you have a new version, I can post a new version of that test page
with it, if you like, or you can modify it as you like. The PHP is
at:

<http://scott.sauyet.com/Javascript/Test/2010-06-03a/index.phps>


You need to compare the return value of yours against one of Jorge's that
works (that in
<99e57eb6-f5c7-410e-a40f-07ae48af2...@b21g2000vbh.googlegroups.com> returns
NaN with your test case in JavaScript 1.8.2) instead.

Jorge's seems to generally agree with yours. Mine will sometimes end
up a closer match to the original string.
 
D

Dr J R Stockton

In comp.lang.javascript message <[email protected]>,
Since the value of `i' is the return value of `parseInt(s, base)' here, in
the border case that `s' represents a number which absolute value is less
than 1, `i' has no sign or IOW its sign is always positive. It is therefore
hard to see how its sign could be useful.


Instead of making yet another lame attempt at an ad hominem attack, and
speaking in riddles, you could just have said how you would do it. Suppose,
just suppose, that I do exactly what you just did, then you are no better
than me. This should give you pause.

Telling you to search the archives has evidently, as intended, annoyed
you; you have found it to be not really helpful. Perhaps, therefore,
you will now abandon the practice of making such a recommendation to
others as frequently as you have been doing. But I doubt it.



You are really being perversely obtuse this week. The answer is on my
Web site, in the obvious place. AND it is also in the article which I
posted to this thread just a week ago today. AND it is fairly obvious
from what ECMA 262 says.

Granted, the additional code in my article to handle the case of strings
starting '-0.' is not easy to see, since it is only two characters; but
there's a whole sentence of clue directly preceding the code.


But, really, you should be aware that, apart from the case of NaN(s),
the IEEE Double format CANNOT represent an unsigned quantity. Of
course.
parseInt(+0.5) == parseInt(-0.5) // -> true
but 1/parseInt(+0.5) == 1/parseInt(-0.5) // -> false

My reading of ECMA 262 is that parseInt is required firstly to remove
whitespace and set /sign/ to 1, then if the string starts '-' set /sign/
to -1. It then processes the rest to get a (non-negative) /number/, and
returns /sign/ times /number/. Now read ECMA 11.5.2. Function parseInt
always returns an appropriately-SIGNED Number.

AFAIK, parseInt cannot return an infinity. In the present case, we
expect a finite, and signed, value in 'i' - and the sign can ALWAYS be
determined by comparing a zero (of either sign) with the reciprocal of
'i'.


I chose to use (2*(1/A>0)-1) where you have used (i < 0 ? -1 : 1)
but you could use (1/i < 0 ? -1 : 1). Again, 'i' is a bad choice of
identifier where you do not know in what font it will be read (and I is
not much better).

Only in strings can JavaScript represent an unsigned zero.




IIRC, the case of the nasty format shown by -.12345 can be handled
with a RegExp replace of (\d*)\. by 0$1 .



ASIDE, as .toString(radix) is also about string/number interconversion :
ISTM that looking, for each radix 2 to 36, at the last character
of Math.random().toString(radix), very repeatedly, the set of
characters obtained is not the same in any 2 of the 5 browsers
on this PC - Firefox 3.0.19 alone giving the expected "any non-
zero digit in the base". My js-randm.htm refers.
 
D

Dr J R Stockton

In comp.lang.javascript message <[email protected]
rlyn.invalid>, Fri, 28 May 2010 20:35:46, Dr J R Stockton
ISTM that parseInt("-0", 16) does indeed return -0.

S = "-45.6"
A = parseInt(S, 16)
if (S = S.split(".")[1])
A += (2*(1/A>0)-1) * parseInt(S, 16) / Math.pow(16, S.length)
// -> -69.375

That is undertested. A number string should always have a digit before
its basal separator if any. Have no non-digit terminator.


This accepts zero or more digits before the point, and trailing non-
digits, and ludicrously long input fractions.

function parsFlotB(S, R) {
S = S.replace(/(\d*\.)/, "0$1") // ensure \d+ before point
var A = parseInt(S, R)
if (S = S.split(".")[1]) { var NR = 1, L = 0
S = S.substring(0, 99) // Crude partial fix for excess length
while (1+parseInt(S.charAt(L++), R)) NR *= R // good digits
A += (1/A>0?+1:-1) * parseInt(S, R) / NR }
return A }
 
S

Scott Sauyet

Dr said:
This accepts zero or more digits before the point, and trailing non-
digits, and ludicrously long input fractions.

function parsFlotB(S, R) {
  S = S.replace(/(\d*\.)/, "0$1") // ensure \d+ before point
  var A = parseInt(S, R)
  if (S = S.split(".")[1]) { var NR = 1, L = 0
    S = S.substring(0, 99) // Crude partial fix for excess length
    while (1+parseInt(S.charAt(L++), R)) NR *= R // good digits
    A += (1/A>0?+1:-1) * parseInt(S, R) / NR }
  return A }

This still loses some possible precision. Try the first example at

http://scott.sauyet.com/Javascript/Test/2010-06-16a/

(Doesn't work in IE, and I just can't bother to figure out why right
now.)

My technique is certainly inefficient, but it does seem to gain a
digit or two of precision over the others presented.
 
D

Dr J R Stockton

In comp.lang.javascript message <5583d1fb-9746-4999-9d7b-46a0e831bad5@e5
g2000yqn.googlegroups.com>, Wed, 16 Jun 2010 08:30:51, Scott Sauyet
Dr said:
This accepts zero or more digits before the point, and trailing non-
digits, and ludicrously long input fractions.

function parsFlotB(S, R) {
  S = S.replace(/(\d*\.)/, "0$1") // ensure \d+ before point
  var A = parseInt(S, R)
  if (S = S.split(".")[1]) { var NR = 1, L = 0
    S = S.substring(0, 99) // Crude partial fix for excess length
    while (1+parseInt(S.charAt(L++), R)) NR *= R // good digits
    A += (1/A>0?+1:-1) * parseInt(S, R) / NR }
  return A }

This still loses some possible precision. Try the first example at

http://scott.sauyet.com/Javascript/Test/2010-06-16a/

(Doesn't work in IE, and I just can't bother to figure out why right
now.)

My technique is certainly inefficient, but it does seem to gain a
digit or two of precision over the others presented.

I recommend monospace for input type=text , by CSS.

I suggest that you show the result of the "parseFloat" in addition to
the toString thereof; toString is clearly not reliable cross-browser for
less popular radixes.

Testing '0.fgr' to base 36 on your page, all but yours are perfect in
Firefox 3.0.19.

Try '0.fgr' to base 27 !

Try almost any fraction to a large odd base on Chrome; the "results" are
unreasonably long in all four cases. Method Number.toString, used there
for display, is untrustworthy. That is why the above code includes
  S = S.substring(0, 99) .


Your base-36 test uses "0.r3j6f0mqo4fr3j6f0m" which has 18 radical
places, so the string has more resolution than an IEEE Double can give;
that of course is why results are shorter.

Your parseFraction seems to loop over all of the fraction digits,
repeatedly dividing by base. That perhaps means repeated rounding
errors, unless the JavaScript engine is unreasonably clever. Using
parseInt on the fractional part should be better, since parseInt ought
to be exact up to a result of 2^53.

For such functions, it would be useful to have exact statements of how,
with radix=10, they differ from ECMA 15.1.2.3 parseFloat (string). I
guess all disallow ExponentPart. You and Jorge give NaN for a string
such as "11.001 2*2=4". You accept "++55". Jorge, I think, crashes if
not given a radical point. Mine gives 0 from ".", which is too
tolerant.

For meaningful tests of accuracy, ISTM essential to have a "master"
parseFloat or a "master" "toString" which is absolutely accurate, using
absolutely accurate arithmetic throughout.


I have, via sig line 3, a programmable megadigit integer arithmetic
package for bases 2 to 16, which might be useful here - Pascal/Delphi
LONGCALC. If I were starting again, I could use bases 2 to 256 equally
easily (apart from the representation as strings), but I don't fancy
changing it now.


OTOH, how about the following, which is intended to be evidently good
without regard to speed, and expects proper input only :-

function ExactPF(S, Rdx) { var J, L = 0, R = 0, RN = 1
S = S.split(".")
var Int = S[0].split("")
var Frc = S[1].split("")
var Sgn = Int[0] == "-" ? -1 : +1
if (Sgn == -1) Int.shift(1)
for (J = 0 ; J < Int.length ; J++)
L = L * Rdx + parseInt(Int[J], Rdx)
for (J = 0 ; J < Frc.length ; J++) { RN *= Rdx
R = R * Rdx + parseInt(Frc[J], Rdx) }
return Sgn * ( L + R/RN ) } // Consider case of L or R exceeding 2^53

Note that it uses parseInt only on single characters, which reduces the
chance of error. I've nor found any; but it is practical to test
parseInt with all bases and all single-character strings, but not with
all multi-digit strings.
 
S

Scott Sauyet

Dr said:
In comp.lang.javascript message <5583d1fb-9746-4999-9d7b-46a0e831bad5@e5
g2000yqn.googlegroups.com>, Wed, 16 Jun 2010 08:30:51, Scott Sauyet

I suggest that you show the result of the "parseFloat" in addition to
the toString thereof; toString is clearly not reliable cross-browser for
less popular radixes.

Good idea, it's in the latest version here:

http://scott.sauyet.com/Javascript/Test/2010-06-18a/

as is a try-catch around the call to each function with some minimal
error reporting.

Testing '0.fgr' to base 36 on your page, all but yours are perfect in
Firefox 3.0.19.

Ahh, yes, I can see that mine can lose some precision with smaller
inputs, while it gains some at larger length inputs.
Try '0.fgr' to base 27 !

That doesn't bother me much at least until I figure out how it's
supposed to react to illegitimate input. If you try the same thing to
base 10 you get other surprising results.
Try almost any fraction to a large odd base on Chrome; the "results" are
unreasonably long in all four cases.  Method Number.toString, used there
for display, is untrustworthy.  That is why the above code includes
   S = S.substring(0, 99)   .

Your base-36 test uses "0.r3j6f0mqo4fr3j6f0m" which has 18 radical
places, so the string has more resolution than an IEEE Double can give;
that of course is why results are shorter.

Right, but that does not mean that we can't get more exact results
when we calculate with additional digits. I think maybe it would be
best to combine these techniques. I have some ideas how, but have not
yet tried to implement them as my brain is too fuzzy at the moment.
(Midnight here.) Briefly, the idea would be to figure out and cache
the maximum number of digits for each base that fit in 53 bits, use
those for the most exact results, but then calculate a few digits
further with the technique I used earlier.

Your parseFraction seems to loop over all of the fraction digits,
repeatedly dividing by base.  That perhaps means repeated rounding
errors, unless the JavaScript engine is unreasonably clever.  Using
parseInt on the fractional part should be better, since parseInt ought
to be exact up to a result of 2^53.

Yes, but a IEEE754 number can sometimes still have a bit more
precision than the result of that integer divided by the relevant
power of the radix.
For such functions, it would be useful to have exact statements of how,
with radix=10, they differ from ECMA 15.1.2.3 parseFloat (string).  I
guess all disallow ExponentPart.  You and Jorge give NaN for a string
such as "11.001 2*2=4".  You accept "++55".  Jorge, I think, crashes if
not given a radical point.  Mine gives 0 from ".", which is too
tolerant.

I've added this test too.

For meaningful tests of accuracy, ISTM essential to have a "master"
parseFloat or a "master" "toString" which is absolutely accurate, using
absolutely accurate arithmetic throughout.

I have, via sig line 3, a programmable megadigit integer arithmetic
package for bases 2 to 16, which might be useful here - Pascal/Delphi
LONGCALC.  If I were starting again, I could use bases 2 to 256 equally
easily (apart from the representation as strings), but I don't fancy
changing it now.

OTOH, how about the following, which is intended to be evidently good
without regard to speed, and expects proper input only :-

function ExactPF(S, Rdx) { var J, L = 0, R = 0, RN = 1
  S = S.split(".")
  var Int = S[0].split("")
  var Frc = S[1].split("")
  var Sgn = Int[0] == "-" ? -1 : +1
  if (Sgn == -1) Int.shift(1)
  for (J = 0 ; J < Int.length ; J++)
    L = L * Rdx + parseInt(Int[J], Rdx)
  for (J = 0 ; J < Frc.length ; J++) { RN *= Rdx
    R = R * Rdx + parseInt(Frc[J], Rdx) }
  return Sgn * ( L + R/RN ) } // Consider case of L or R exceeding 2^53

Note that it uses parseInt only on single characters, which reduces the
chance of error.  I've nor found any; but it is practical to test
parseInt with all bases and all single-character strings, but not with
all multi-digit strings.

I've included it on the page without considering it thoroughly
enough. I'll try to have another look over the weekend.

A very interesting discussion! Thank you,
 
R

Ry Nohryb


Would you mind to update in your page my code to the latest version ?
It is:

String.prototype.toFP= (function (regExpCache) {
/* 20100531, by (e-mail address removed) */
return function (base, n, r, w, div) {
if ((base < 2) || (base > 36) || (base % 1)) return NaN;
if (!(n= regExpCache[base])) {
n= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);
n= "^\\s{0,}([-+]{0,1})(["+n+"]{0,})[.]{0,1}(["+n+"]{0,})\
\s{0,}$";
regExpCache[base]= n= new RegExp(n, "i");
}
if (!(n= n.exec(this))) return NaN;
if (isFinite(r= parseInt(n[2] || "0", base)) && (w= n[3].length)) {
while (!isFinite(div= Math.pow(base, w))) w--;
r+= parseInt(n[3].substr(0, w), base)/ div;
}
return (n[1]+ "1")* r;
};
})([]);

TIA,
 
D

Dr J R Stockton

In comp.lang.javascript message <[email protected]
rlyn.invalid>, Tue, 15 Jun 2010 23:41:23, Dr J R Stockton
function parsFlotB(S, R) {
S = S.replace(/(\d*\.)/, "0$1") // ensure \d+ before point
var A = parseInt(S, R)
if (S = S.split(".")[1]) { var NR = 1, L = 0
S = S.substring(0, 99) // Crude partial fix for excess length
while (1+parseInt(S.charAt(L++), R)) NR *= R // good digits
A += (1/A>0?+1:-1) * parseInt(S, R) / NR }
return A }

REPLACE \d with \w. Also, parsFlotC is in hand.
 
D

Dr J R Stockton

In comp.lang.javascript message <1ba54467-b1ec-423a-9989-4eb3b3288fec@a3
0g2000yqn.googlegroups.com>, Fri, 18 Jun 2010 21:01:35, Scott Sauyet
Dr J R Stockton wrote:
function ExactPF(S, Rdx) { var J, L = 0, R = 0, RN = 1
  S = S.split(".")
  var Int = S[0].split("")
  var Frc = S[1].split("")
  var Sgn = Int[0] == "-" ? -1 : +1
  if (Sgn == -1) Int.shift(1)
  for (J = 0 ; J < Int.length ; J++)
    L = L * Rdx + parseInt(Int[J], Rdx)
  for (J = 0 ; J < Frc.length ; J++) { RN *= Rdx
    R = R * Rdx + parseInt(Frc[J], Rdx) }
  return Sgn * ( L + R/RN ) } // Consider case of L or R exceeding 2^53

Note that it uses parseInt only on single characters, which reduces the
chance of error.  I've nor found any; but it is practical to test
parseInt with all bases and all single-character strings, but not with
all multi-digit strings.

I've included it on the page without considering it thoroughly
enough. I'll try to have another look over the weekend.

ExactPF now does not use parseInt. And it has been demoted (in
<URL:http://www.merlyn.demon.co.uk/js-maths.htm#pF>) to be called
BetterPF.

Routines should be tested, base 10, with 0.999999999999999999999999999
for a varying number of nines and similar on other bases. A result of
over 1.0 is bad.

Consider putting the test button somewhere that the
"value" does not cover when showing a list, and putting
the results below, rather than beside, the input.

For production code, ISTM OK to use the default parseFloat for base 10.
On the other hand, that assumes it to be right. But for test ing
conversion code, it is well to have base 10 done in the same way as
other bases, enabling a meaningful comparison with parseFloat.



Function parseFloat should not be used for currency input, since it
inevitably gives inexact Numbers; instead, one should check the format
with a RegExp match and construct the exact number of pennies (does not
apply to much Government work).

Otherwise, the nature of the following calculation is probably such that
an LSB or two of error does not matter; routines such as we have seen
are OK (but one might add handling of exponents such as toString gives).

But, for test purposes, something perfectly accurate at all times is
needed (with rounding into the LSB defined for the half-way case).
 
B

Benjamin 'BeRo' Rosseaux

Here my naive but secure variant, which works at least in V8 and in my
own ECMAScript engine BESEN:

String.prototype.toNumber=function(radix){
radix=(radix!==undefined)?(+radix):10;
if((radix<1)||(radix>36))throw new RangeError("Bad radix");
var s=(""+this).toLowerCase(),whitespaces=" \t\r\n",signs="-+";
var
n="0123456789abcdefghijklmnopqrstuvwxyz".substr(0,radix),intValue=0,fracValue=0,signValue=1,fracFactor=1,i,j=s.length,c,v;
for(i=0;(i<j)&&(whitespaces.indexOf(c=s.charAt(i))>=0);i++);

for(;(i<j)&&(signs.indexOf(c=s.charAt(i))>=0);i++)signValue*=(c=="-")?(-1):1;
for(;(i<j)&&(whitespaces.indexOf(c=s.charAt(i))>=0);i++);

for(;(i<j)&&((v=n.indexOf(c=s.charAt(i)))>=0);intValue=(intValue*radix)+v,i++);

for(i+=(c==".")?1:(j+1);(i<j)&&((v=n.indexOf(c=s.charAt(i)))>=0);fracValue+=v*(fracFactor/=radix),i++);

return(((intValue+fracValue)*(((c=="e")||(c=="p"))?Math.pow(radix,parseInt(s.substr(++i))):1.0))*signValue);
}

var a=Math.PI.toString(36);
alert(a.toNumber(36));
 
B

Benjamin 'BeRo' Rosseaux

Am 21.06.2010 21:55, schrieb Scott Sauyet:
Is it intentional that this accepts such input as "---+-+-1.234"?

Yes, it accepts even "1.0e4" "1.0e-3" "1.0e+4" (base 10), "1f.1fp4"
"1f.1fp+2" "1f.1fp-4" (base 16) and so on as exponent stuff, because
Number.prototype.toString "can" produce so such results, at least in
BESEN and even some other engines.
 

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

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,052
Latest member
LucyCarper

Latest Threads

Top