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

R

Ry Nohryb

This test is not enough now, meseems:

a = '12345abc.5def'
document.write((a.toFP(10)) // 12345.0005

use:

var re = base>10 ?'a-'+String.fromCharCode(86+base) :'';
re = new RegExp('[^0-9'+re+'\.+-]','i');
if (re.test(this)) return NaN;

and add:

if (/[+-]/.test(this.substr(1))) return NaN; //+- must be in position 0
if (this.replace(/[^\.]+/g,'').length>1) return NaN; // count of . <=1

Well, yes, the inputs ought to be validated. Both the base parameter
and the 'this' string.

Funny thing is that once you add the -ugly- regExp to test the
structure and its contents, it's easy to use it to capture the
validated parts, and then the rest becomes even simpler than before
( ~8 LOC ):

String.prototype.toFP= function (base, n, r, w, div) {
//20100531 by (e-mail address removed)

/* check that base is in range and an integer */
if ((base < 2) || (base > 36) || (base % 1)) return NaN;

/* get the digits that are valid for this base */
validDigits= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);

/* validate structure and contents of the input str : */
/* ^ (optional) whitespace + (optional) a single char [-+] */
/* + (non-optional) 1 or more validDigits + (optional) a point */
/* + (optional) more validDigits + (optional) whitespace $ */
n= "^\\s{0,}([-+]{0,1})(["+ validDigits+ "]{1,})[.]{0,1}(["+
validDigits+ "]{0,})\\s{0,}$";

/* exec n on 'this' now, case-insensitively, and reuse n*/
if (!(n= new RegExp(n, "i").exec(this))) return NaN;

/* got captured : */
/* n[1]= sign, n[2]=integer part, n[3]= fractional part */

if (isFinite(r= parseInt(n[2], base)) && (w= n[3].length)) {
/* trim until div is finite */
while (!isFinite(div= Math.pow(base, w))) w--;
r+= parseInt(n[3].substr(0, w), base)/ div;
}

/* sign is one of "1" or "+1" or "-1" */
return (n[1]+ "1")* r;
};
 
E

Evertjan.

Ry Nohryb wrote on 31 mei 2010 in comp.lang.javascript:
/* get the digits that are valid for this base */
validDigits= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);
nice!

/* validate structure and contents of the input str : */
/* ^ (optional) whitespace + (optional) a single char [-+] */
/* + (non-optional) 1 or more validDigits + (optional) a point */
/* + (optional) more validDigits + (optional) whitespace $ */
n= "^\\s{0,}([-+]{0,1})(["+ validDigits+ "]{1,})[.]{0,1}(["+
validDigits+ "]{0,})\\s{0,}$";

This would fail for 'pointfractions', like ".56ab" and "-.2"

In short:

n='^\\s*([-+]?)(['+validDigits+']+)\\.?(['+validDigits+']?)\\s*$';


However, to allow for such pointfractions would need something like this:

n1='^\\s*([-+]?)(['+validDigits+']+)\\.?(['+validDigits+']?)\\s*$';
n2='^\\s*([-+]?)()\\.(['+validDigits+']?)\\s*$';

n = n1 + '|' + n2

or

n1 = '^\\s*([-+]?)'
n2a = '(['+validDigits+']+)\\.?'
n2b = '()\\.'
n3 = '(['+validDigits+']?)\\s*$';

n = n1 + '(?:' + n2a + '|' + n2b + ')' + n3

not tested.

/* exec n on 'this' now, case-insensitively, and reuse n*/
if (!(n= new RegExp(n, "i").exec(this))) return NaN;
/* got captured : */
/* n[1]= sign, n[2]=integer part, n[3]= fractional part */
 
R

Ry Nohryb

Ry Nohryb wrote on 31 mei 2010 in comp.lang.javascript:
 /* get the digits that are valid for this base */
  validDigits= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);
nice!

  /* validate structure and contents of the input str : */
  /* ^ (optional) whitespace + (optional) a single char [-+] */
  /* + (non-optional) 1 or more validDigits + (optional) a point */
  /* + (optional) more validDigits + (optional) whitespace $ */
  n= "^\\s{0,}([-+]{0,1})(["+ validDigits+ "]{1,})[.]{0,1}(["+
validDigits+ "]{0,})\\s{0,}$";

This would fail for 'pointfractions', like ".56ab" and "-.2"

In short:

 n='^\\s*([-+]?)(['+validDigits+']+)\\.?(['+validDigits+']?)\\s*$';

However, to allow for such pointfractions would need something like this:

 n1='^\\s*([-+]?)(['+validDigits+']+)\\.?(['+validDigits+']?)\\s*$';
 n2='^\\s*([-+]?)()\\.(['+validDigits+']?)\\s*$';

 n = n1 + '|' + n2

or

 n1 = '^\\s*([-+]?)'
 n2a = '(['+validDigits+']+)\\.?'
 n2b = '()\\.'
 n3 = '(['+validDigits+']?)\\s*$';

 n = n1 + '(?:' + n2a + '|' + n2b + ')' + n3

not tested.

Hmmm, I think I just have to make that part optional in the regexp
({0,} instead of {1,}), and guard n[2] with || "0" in the call to
parseInt() :

String.prototype.toFP= function (base, n, r, w, div) {
//20100531 by (e-mail address removed)

/* check that base is in range and an integer */
if ((base < 2) || (base > 36) || (base % 1)) return NaN;

/* get the digits that are valid for this base */
validDigits= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);

/* validate structure and contents of the input str : */
/* ^ (optional) whitespace + (optional) a single char [-+] */
/* + (non-optional) 1 or more validDigits + (optional) a point */
/* + (optional) more validDigits + (optional) whitespace $ */
n= "^\\s{0,}([-+]{0,1})(["+ validDigits+ "]{0,})[.]{0,1}(["+
validDigits+ "]{0,})\\s{0,}$";

/* exec n on 'this' now, case-insensitively, and reuse n*/
if (!(n= new RegExp(n, "i").exec(this))) return NaN;

/* got captured : */
/* n[1]= sign, n[2]=integer part, n[3]= fractional part */

if (isFinite(r= parseInt(n[2] || "0", base)) && (w= n[3].length)) {
/* trim until div is finite */
while (!isFinite(div= Math.pow(base, w))) w--;
r+= parseInt(n[3].substr(0, w), base)/ div;
}

/* sign is one of "1" or "+1" or "-1" */
return (n[1]+ "1")* r;
};

"-.8".toFP(10)
--> -0.8
 
R

Ry Nohryb

Since you like short code:

  {0,} is the same as *
  {1,} is the same as +

:)

Yes, but, don't you find their -the regExps'- syntax sufficiently
confusing and unintelligible enough without the shortcuts ? Whose sick
mind invented such an ugly thing ? :)
 
R

Ry Nohryb

(...)
  /* get the digits that are valid for this base */
  validDigits= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);
(...)

Oops, forgot to declare "validDigits". What if I just reuse "n"
again ?

String.prototype.toFP= function (base, n, r, w, div) {
//20100531 by (e-mail address removed)

/* check that base is in range and an integer */
if ((base < 2) || (base > 36) || (base % 1)) return NaN;

/* get the digits that are valid for this base */
n= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);

/* validate structure and contents of the input str : */
/* ^ (optional) whitespace + (optional) a single char [-+] */
/* + (optional) 0 or more validDigits + (optional) a point */
/* + (optional) more validDigits + (optional) whitespace $ */
n= "^\\s{0,}([-+]{0,1})(["+ n+ "]{0,})[.]{0,1}(["+ n+ "]{0,})\
\s{0,}$";

/* exec n on 'this' now, case-insensitively, and reuse n*/
if (!(n= new RegExp(n, "i").exec(this))) return NaN;

/* by now we've got captured, cleaned-up and validated : */
/* n[1]= sign, n[2]=integer part, n[3]= fractional part */

if (isFinite(r= parseInt(n[2] || "0", base)) && (w= n[3].length)) {
/* trim until div is finite */
while (!isFinite(div= Math.pow(base, w))) w--;
r+= parseInt(n[3].substr(0, w), base)/ div;
}

/* sign is one of "1" or "+1" or "-1" */
return (n[1]+ "1")* r };

"\t -.z\r".toFP(36).toString(36)
--> "-0.z"
 
D

Dr J R Stockton

In comp.lang.javascript message <[email protected]>,
Sun, 30 May 2010 13:30:58, Thomas 'PointedEars' Lahn
Dr said:
Thomas 'PointedEars' Lahn posted:
Yes, good catch; we need to consider the sign with addition, e.g.:

var s = (-Math.PI).toString(16);
var i = parseInt(s, 16);
var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];
var n = i + (i < 0 ? -1 : 1) * parseInt(f, 16) / Math.pow(16, f.length);

You need to consider it more effectively, and to test adequately.

Do you know what "quick hack" means?

Generally slovenly working, with a tendency to miss the obvious.
ACK, thanks. ISTM that checking whether the first character of the
representation is a `-' solves this particular problem. Again, largely
untested:

Entirely unnecessary. Just use the sign of the number 'i'.

Unless you are working with dates, it is better to use j, rather than i,
for a short-term identifier in News. The former is likely, in an
unknown font, to be better distinguishable from other characters; and it
does not excite the attention of a spelling-checker.
 
R

Ry Nohryb


W/o the comments it's really just only 11 LOCs :

String.prototype.toFP= function (base, n, r, w, div) {

if ((base < 2) || (base > 36) || (base % 1)) return NaN;

n= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);
n= "^\\s{0,}([-+]{0,1})(["+n+"]{0,})[.]{0,1}(["+n+"]{0,})\\s{0,}$";
if (!(n= new RegExp(n, "i").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;
};

What other bugs are there left into it ?
Would it be a good idea to memoize the regExps ?
Hey, Pointy, what's its Jorgtropy level ?
 
S

Scott Sauyet

Ry Nohryb said:
W/o the comments it's really just only 11 LOCs :

Quite unreadable LOC, I'm afraid.
String.prototype.toFP= function (base, n, r, w, div) {

  if ((base < 2) || (base > 36) || (base % 1)) return NaN;

  n= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);
  n= "^\\s{0,}([-+]{0,1})(["+n+"]{0,})[.]{0,1}(["+n+"]{0,})\\s{0,}$";
  if (!(n= new RegExp(n, "i").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;

};

What other bugs are there left into it ?

If I find some time this evening, I'll look into the accuracy. I'm
curious as to whether something like this would be more precise,
although it would clearly not perform as well:

var parseFloat = (function() {
var origPF = parseFloat,
allDigits = "0123456789abcdefghijklmnopqrstuvwxyz",
regexes = {},
getRegex = function(base) {
if (!regexes[base]) {
var digits = allDigits.substring(0, base);
regexes[base] = new RegExp("(^[\\-\\+]?)([" + digits +
"]*)(?:\.([" + digits + "]*))?$");
}
return regexes[base];
},
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;
};

return function (str, base) {
if (!base || base == 10) {
return origPF(str);
}
if ((base < 2) || (base > 36) || (base % 1)) return NaN;
str = str.toString().toLowerCase();
var regex = getRegex(base),
match = regex.exec(str);
if (!match) return NaN;
return ((match[1] == "-") ? -1 : 1) * (
parseInt(match[2], base) + parseFraction(match[3], base)
);
};
}());

Would it be a good idea to memoize the regExps ?

If you're concerned about performance, then yes. They depend only on
the (35 possible) bases used.
 
D

Dr J R Stockton

In comp.lang.javascript message <[email protected]>
This would fail for 'pointfractions', like ".56ab" and "-.2"

Those should not be written - see IUPAP-25 / SUNAMCO 87-1, section
1.3.2.

Query : what does one call the dot in 123.456 if the radix is unknown?
 
T

Thomas 'PointedEars' Lahn

Dr said:
Thomas 'PointedEars' Lahn posted:
Dr said:
Thomas 'PointedEars' Lahn posted:
Yes, good catch; we need to consider the sign with addition, e.g.:

var s = (-Math.PI).toString(16);
var i = parseInt(s, 16);
var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];
var n = i + (i < 0 ? -1 : 1) * parseInt(f, 16) / Math.pow(16,
f.length);

You need to consider it more effectively, and to test adequately.
Do you know what "quick hack" means?

Generally slovenly working, with a tendency to miss the obvious.

No. Quick hack refers to code that is largely untested, if that. It is
therefore unreasonable to insist that it should have been (properly) tested
(when it fails to accomplish the intended task).
Entirely unnecessary. Just use the sign of the number 'i'.

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.


PointedEars
 
D

Dr J R Stockton

In comp.lang.javascript message <9ec0357f-f6f7-4680-829c-bc4403160303@z1
5g2000prh.googlegroups.com>, Sun, 30 May 2010 16:49:58, David Mark
In comp.lang.javascript message <[email protected]>
Dr J R Stockton wrote on 28 mei 2010 in comp.lang.javascript:
In comp.lang.javascript message <[email protected]>
, Thu, 27 May 2010 18:16:34, Evertjan. <[email protected]>
posted:
However we were [or at least I was] just contemplating a general function
for conversion of any root[1..36] floating point string to number value.

Remember to include isNaN & !isFinite testing.
Such functions are much safer explicit than embedded,

Sometimes.  The advantage of an embedded bug is that someone else is
more likely to find it first, and get it fixed.

Opera 10.10, but not Opera 10.53 : Number.toString(radix) ignores
radix.

Not from what I've seen (just tested it). I just happened to be
working on some code that relies on toString to work with a radix and
your comment was a real spit-take moment.

Which Opera 10.10 are you testing?

Version 10.10
Build 1893
Platform Win32
System Windows XP
Java Sun Java Runtime Environment version 1.6
XHTML+Voice Plug-in not loaded
Browser identification
Opera/9.80 (Windows NT 5.1; U; en-GB) Presto/2.2.15 Version/10.10

Example :
Math.random().toString(2)
always returns a string between 0.0 and 0.999999 ...
in which, after the "0.", on average only about 10% of the characters
are "0" and only about another 10% are "1" - about 80% are in "2"-"9".






The same test in my Opera 10.53 gives strings consisting entirely of "0"
& "1" after the "0.", except that the last character is quite often a
"2".

Similar (last digit = radix) - has been seen throughout radix 2 to 7,
and 9, and 11 (get ":"). Radix 36 has been seen to give ":" and "{".

The correctness of base 8, and the difference seen at base 32, could
have been due to imperfection in Math.random. So I've tried
Math.sqrt(Math.random()) with clearer results : all below radix 10 are
similarly defective, and all above radix 10 are similar with the
addition of ":".

It should be noted that, if my laptop and its Opera are not corrupt,
this test funds something imperfect about Opera 10.53 Math.random, since
Math.sqrt should have made no difference AFAICS.

The code lists, for each radix R, all the final digits observed in 1e6
tests of Math.sqrt(Math.random()).toString(R). Base 10 is correct; all
others have an extra digit; all above 10 have a colon.


B = []
for (R=2 ; R<=36 ; R++) { A = []
for (J=0 ; J<1e6 ; J++) {
S = Math.sqrt(Math.random()).toString(R)
L = S.length
T = S[L-1]
A[T.charCodeAt(0)] = T }
B.push(R + "\t" + A.join("")) }

document.write("<pre>" + B.join("\n") + "<\/pre>")


2 12
3 123
4 1234
5 12345
6 123456
7 1234567
8 12345678
9 123456789
10 123456789
11 123456789:ab
12 123456789:abc
13 123456789:abcd
14 123456789:abcde
15 123456789:abcdef
16 123456789:abcdefg
17 123456789:abcdefgh
18 123456789:abcdefghi
19 123456789:abcdefghij
20 123456789:abcdefghijk
21 123456789:abcdefghijkl
22 123456789:abcdefghijklm
23 123456789:abcdefghijklmn
24 123456789:abcdefghijklmno
25 123456789:abcdefghijklmnop
26 123456789:abcdefghijklmnopq
27 123456789:abcdefghijklmnopqr
28 123456789:abcdefghijklmnopqrs
29 123456789:abcdefghijklmnopqrst
30 123456789:abcdefghijklmnopqrstu
31 123456789:abcdefghijklmnopqrstuv
32 123456789:abcdefghijklmnopqrstuvw
33 123456789:abcdefghijklmnopqrstuvwx
34 123456789:abcdefghijklmnopqrstuvwxy
35 123456789:abcdefghijklmnopqrstuvwxyz
36 123456789:abcdefghijklmnopqrstuvwxyz{

A run takes at most a few minutes on a recent PC.
 
R

Ry Nohryb

Ry Nohryb said:
W/o the comments it's really just only 11 LOCs :

Quite unreadable LOC, I'm afraid.




String.prototype.toFP= function (base, n, r, w, div) {
  if ((base < 2) || (base > 36) || (base % 1)) return NaN;
  n= "0123456789abcdefghijklmnopqrstuvwxyz".substr(0, base);
  n= "^\\s{0,}([-+]{0,1})(["+n+"]{0,})[.]{0,1}(["+n+"]{0,})\\s{0,}$";
  if (!(n= new RegExp(n, "i").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;

What other bugs are there left into it ?

If I find some time this evening, I'll look into the accuracy.  I'm
curious as to whether something like this would be more precise,
although it would clearly not perform as well:

  var parseFloat = (function() {
    var origPF = parseFloat,
        allDigits = "0123456789abcdefghijklmnopqrstuvwxyz",
        regexes = {},
        getRegex = function(base) {
          if (!regexes[base]) {
            var digits = allDigits.substring(0, base);
            regexes[base] = new RegExp("(^[\\-\\+]?)([" + digits +
                                "]*)(?:\.([" + digits + "]*))?$");
          }
          return regexes[base];
        },
        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;
        };

    return function (str, base) {
      if (!base || base == 10) {
        return origPF(str);
      }
      if ((base < 2) || (base > 36) || (base % 1)) return NaN;
      str = str.toString().toLowerCase();
      var regex = getRegex(base),
          match = regex.exec(str);
      if (!match) return NaN;
      return ((match[1] == "-") ? -1 : 1) * (
        parseInt(match[2], base) + parseFraction(match[3], base)
      );
    };
  }());


I like that parseFraction() of yours, it's awesome. Good idea. In
order to test it agains the .toFP algorithm, I've written this: it
loops through all the bases, and converts an increasingly smaller
number i until i !=== [ parseFloat || toFP ](i.toString(base)). The
win is given to the algorithm that fails with a smaller i. The funny
thing is that different browsers give different results (due, I guess,
to differences in .toString(base)), but, in general, your algorithm
WINS (in all but FF):

(Tested on a Mac)

Safari(*) r60462: WINS: toFP(): 5, ParseFloat(): 6
FF3.6.4: WINS: toFP(): 15, ParseFloat(): 9
Chrome 5.0.375.38: WINS: toFP(): 11, ParseFloat(): 16
Opera10.53: WINS: toFP(): 2, ParseFloat(): 4

(*) .toString(base) is broken in Safari.

The test code follows: just copy-paste it, it's a bookmarklet:

javascript:
var parseFloat = (function() {
var origPF = parseFloat,
allDigits = "0123456789abcdefghijklmnopqrstuvwxyz",
regexes = {},
getRegex = function(base) {
if (!regexes[base]) {
var digits = allDigits.substring(0, base);
regexes[base] = new RegExp("(^[\\-\\+]?)([" + digits +
"]*)(?:\.([" + digits + "]*))?$");
}
return regexes[base];
},
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;
};
return function (str, base) {
if (!base || base == 10) {
return origPF(str);
}
if ((base < 2) || (base > 36) || (base % 1)) return NaN;
str = str.toString().toLowerCase();
var regex = getRegex(base),
match = regex.exec(str);
if (!match) return NaN;
return ((match[1] == "-") ? -1 : 1) * (
parseInt(match[2], base) + parseFraction(match[3], base)
);
};
}());

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;
};
})([]);

(function test () {
var parseFloatScore= 0;
var toFPScore= 0;
console.log("[ Base, toFP(base), parseFloat(base), winner ]");
for (var base= 2; base < 37; base ++) {
var i= 1e-1;
var r= [base];
while ( i && (i === i.toString(base).toFP(base)) ) {
var iSave= i;
i*= 1e-1;
}
r.push(iSave);
i= 1e-1;
while ( i && (i === parseFloat(i.toString(base), base)) ) {
var iSave= i;
i*= 1e-1;
}
r.push(iSave);
if (r[1] === r[2]) r.push("===");
else r.push("WINNER: "+ ( r[1] > r[2] ?
(parseFloatScore++, "ParseFloat()") :
(toFPScore++, "toFP()") ));
console.log(r);
}
console.log("WINS: toFP(): "+ toFPScore+ ", ParseFloat(): "+
parseFloatScore);
})();


************ Here's the sample output in Chrome:

[ Base, toFP(base), parseFloat(base), winner ]
[2, 1.0000000000000164e-292, 1e-323, "WINNER: ParseFloat()"]
[3, 1e-323, 1e-323, "==="]
[4, 1.0000000000000163e-291, 1e-323, "WINNER: ParseFloat()"]
[5, 1e-323, 0.0010000000000000002, "WINNER: toFP()"]
[6, 0.010000000000000002, 0.010000000000000002, "==="]
[7, 0.010000000000000002, 0.1, "WINNER: toFP()"]
[8, 1.0000000000000164e-292, 1e-323, "WINNER: ParseFloat()"]
[9, 1e-323, 1e-323, "==="]
[10, 0.0010000000000000002, 1e-323, "WINNER: ParseFloat()"]
[11, 1e-323, 0.010000000000000002, "WINNER: toFP()"]
[12, 0.010000000000000002, 0.010000000000000002, "==="]
[13, 0.010000000000000002, 0.00010000000000000003, "WINNER:
ParseFloat()"]
[14, 0.00010000000000000003, 0.1, "WINNER: toFP()"]
[15, 0.1, 1.0000000000000005e-9, "WINNER: ParseFloat()"]
[16, 1.0000000000000163e-291, 1e-323, "WINNER: ParseFloat()"]
[17, 1e-323, 0.0010000000000000002, "WINNER: toFP()"]
[18, 0.010000000000000002, 0.00010000000000000003, "WINNER:
ParseFloat()"]
[19, 0.00010000000000000003, 0.00010000000000000003, "==="]
[20, 0.1, 0.0010000000000000002, "WINNER: ParseFloat()"]
[21, 0.010000000000000002, 0.0010000000000000002, "WINNER:
ParseFloat()"]
[22, 0.0010000000000000002, 0.010000000000000002, "WINNER: toFP()"]
[23, 0.010000000000000002, 0.000010000000000000004, "WINNER:
ParseFloat()"]
[24, 0.000010000000000000004, 0.000010000000000000004, "==="]
[25, 0.000010000000000000004, 1.0000000000000005e-7, "WINNER:
ParseFloat()"]
[26, 1.0000000000000005e-7, 0.00010000000000000003, "WINNER: toFP()"]
[27, 0.00010000000000000003, 0.0010000000000000002, "WINNER: toFP()"]
[28, 0.1, 0.1, "==="]
[29, 0.1, 0.1, "==="]
[30, 0.010000000000000002, 1.0000000000000006e-12, "WINNER:
ParseFloat()"]
[31, 1.0000000000000006e-12, 0.0000010000000000000004, "WINNER:
toFP()"]
[32, 1.0000000000000163e-291, 1e-323, "WINNER: ParseFloat()"]
[33, 1e-323, 1.0000000000000005e-9, "WINNER: toFP()"]
[34, 1.0000000000000005e-9, 0.0000010000000000000004, "WINNER:
toFP()"]
[35, 0.0000010000000000000004, 1.0000000000000006e-12, "WINNER:
ParseFloat()"]
[36, 0.1, 0.00010000000000000003, "WINNER: ParseFloat()"]
WINS: toFP():11, ParseFloat(): 16
If you're concerned about performance, then yes.  They depend only on
the (35 possible) bases used.

Well done. toFP() now memoizes them too :)
 
E

Evertjan.

Dr J R Stockton wrote on 01 jun 2010 in comp.lang.javascript:
Query : what does one call the dot in 123.456 if the radix is unknown?

A dot?

[In your above example, rhe radix is only partly unknown,
as it must be between 7 and 36 inclusive.]

A fraction delimiter?

[this is better,
as there is no unanimity about what character should be used.]
 
S

Scott Sauyet

Ry said:
Scott said:
Ry said:
If I find some time this evening, I'll look into the accuracy.

I'm glad you had a chance to investigate, because I will not have the
time before the weekend.
[ ... ]
        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.
In
order to test it agains the .toFP algorithm, I've written this: it
loops through all the bases, and converts an increasingly smaller
number i until i !=== [ parseFloat || toFP ](i.toString(base)). The
win is given to the algorithm that fails with a smaller i. The funny
thing is that different browsers give different results (due, I guess,
to differences in .toString(base)), but, in general, your algorithm
WINS (in all but FF):

It's an interesting result, but I'm not sure how much that really
tells us about accuracy. It's also not clear to me if

floating point --> string --> floating point

is as good a test as

string --> floating point --> string

It's right now just a gut feeling, and I'm not sure why, but I think
we'd learn more from the latter.


Well done. toFP() now memoizes them too :)

For your function, you might want to cache the "w" values as well.
 
T

Thomas 'PointedEars' Lahn

Scott said:
Ry said:
Scott said:
[ ... ]
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 (as I indicated in one of first replies to Jorge):

var
b = 3,
e = 4;
x = Math.pow(b, e),
y = Math.pow(b, e + 1);

/*
* JavaScript 1.8.2:
* 0.004115226337448559 0.00411522633744856 false
*/
console.log(1/x/b, 1/y, 1/x/b == 1/y);


PointedEars
 
S

Scott Sauyet

Thomas said:
Scott said:
Ry said:
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 becausewe
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.

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.

This approach, though sacrifices the speed of your algorithm. I
haven't done any performance tests, but if you were to alter your
function to cache the results of the regular expressions, I would
guess that your code would run significantly faster than mine -- maybe
not a full order of magnitude, but several times faster, I would
imagine.


[1] <[2] <[email protected]>
 

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
474,077
Messages
2,570,566
Members
47,202
Latest member
misc.

Latest Threads

Top