From: Scott Sauyet on
Ry Nohryb <jo...(a)jorgechamorro.com> wrote:
> 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[i]);
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.

--
Scott
From: Thomas 'PointedEars' Lahn on
Dr J R Stockton wrote:

> Thomas 'PointedEars' Lahn posted:
>> Dr J R Stockton wrote:
>>> 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).

>>> That indeed gives -3.141592653589793; but use instead Math.PI/10 and it
>>> gives 0.3141592653589793. Whenever parseInt(s, 16) gives a zero, your
>>> code will give a positive result.
>>
>> 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'.

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
--
Anyone who slaps a 'this page is best viewed with Browser X' label on
a Web page appears to be yearning for the bad old days, before the Web,
when you had very little chance of reading a document written on another
computer, another word processor, or another network. -- Tim Berners-Lee
From: Dr J R Stockton on
In comp.lang.javascript message <Xns9D89A54A13A91eejj99(a)194.109.133.242>
, Mon, 31 May 2010 14:14:52, Evertjan. <exjxw.hannivoort(a)interxnl.net>
posted:

>
>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?

--
(c) John Stockton, nr London, UK. ?@merlyn.demon.co.uk Turnpike v6.05 IE 7.
Web <URL:http://www.merlyn.demon.co.uk/> - FAQish topics, acronyms, & links.
Command-prompt MiniTrue is useful for viewing/searching/altering files. Free,
DOS/Win/UNIX now 2.0.6; see <URL:http://www.merlyn.demon.co.uk/pc-links.htm>.
From: Dr J R Stockton on
In comp.lang.javascript message <9ec0357f-f6f7-4680-829c-bc4403160303(a)z1
5g2000prh.googlegroups.com>, Sun, 30 May 2010 16:49:58, David Mark
<dmark.cinsoft(a)gmail.com> posted:

>On May 30, 12:28�pm, Dr J R Stockton <reply1...(a)merlyn.demon.co.uk>
>wrote:
>> In comp.lang.javascript message <Xns9D87B7820AFF3eej...(a)194.109.133.242>
>> , Sat, 29 May 2010 16:02:20, Evertjan. <exjxw.hannivo...(a)interxnl.net>
>> posted:
>>
>> >Dr J R Stockton wrote on 28 mei 2010 in comp.lang.javascript:
>>
>> >> In comp.lang.javascript message <Xns9D85CE4464FCFeej...(a)194.109.133.242>
>> >> , Thu, 27 May 2010 18:16:34, Evertjan. <exjxw.hannivo...(a)interxnl.net>
>> >> 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.

--
(c) John Stockton, nr London UK. ?@merlyn.demon.co.uk Turnpike v6.05 MIME.
Web <URL:http://www.merlyn.demon.co.uk/> - FAQish topics, acronyms, & links.
Proper <= 4-line sig. separator as above, a line exactly "-- " (RFCs 5536/7)
Do not Mail News to me. Before a reply, quote with ">" or "> " (RFCs 5536/7)
From: Ry Nohryb on
On Jun 1, 3:47 pm, Scott Sauyet <scott.sau...(a)gmail.com> wrote:
> Ry Nohryb <jo...(a)jorgechamorro.com> wrote:
> > 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[i]);
>             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[i]);
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 jorge(a)jorgechamorro.com */

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

> > 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.

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