Prev: Firefox cache issue
Next: FAQ Topic - How do I format a Number as a String with exactly 2 decimal places? (2010-05-27)
From: Scott Sauyet on 1 Jun 2010 09:47 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 1 Jun 2010 11:58 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 1 Jun 2010 11:38 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 1 Jun 2010 14:20 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 2 Jun 2010 08:29
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. |