Parse Float Comparison

Test Values

Results

Function Output toString()
Results will go here

Explanation

A discussion in a comp.lang.javascript thread is attempting to find a reasonable way to convert non-integer strings to numbers using bases other than 10. The functions listed below are the major ones discussed so far. Enter a string representing a number and the base to parse it in and submit the form. The output from each of these algorithms is displayed in the results table. Note that it is converted back into the original format using value.toString(base).

This does not attempt to compare performance, and by inspection it's clear that there are likely to be significant differences in performance.

Examples

Functions

  1. Thomas 'PointedEars' Lahn
  2. Jorge Chamorro 1
  3. Jorge Chamorro 2
  4. Scott Sauyet 1
  5. John Stockton 1 - updated
  6. John Stockton 2 - updated
  7. Benjamin 'BeRo' Rosseaux
  8. Scott Sauyet 2
  9. John Stockton 3 - updated

Thomas 'PointedEars' Lahn

(cljs post)

var parseFloat_tl = (function () {
  var origPF = parseFloat;

  return function (s, iBase) {
    if (!iBase || iBase == 10)
    {
      return origPF(s);
    }

    var
      i = (s.indexOf(".") != 0 ? parseInt(s, iBase) : 0),
      chars = (iBase < 10
        ? "0-" + String.fromCharCode(47 + iBase)
        : "\\d"
          + (iBase > 10
            ? "a"
              + (iBase > 11
                ? "-" + String.fromCharCode(86 + iBase)
                : "")
            : "")),
      f = (s.match(new RegExp("\\.([" + chars + "]+)", "i")) || [, "0"])[1];

    return i + (i < 0 ? -1 : 1) * parseInt(f, iBase) / Math.pow(iBase, f.length);
  };

}()); 

Jorge Chamorro 1

(cljs post)

String.prototype.toFP_old= function (base, n, r, w, div) {
  if (/[^0-9a-z\.+-]/i.test(this)) return NaN;
  n= this.split('.');
  if (isFinite(r= parseInt(n[0], base))) {
    if (w= (n= n[1]).length) {
      /*trim until it's finite*/
      while (!isFinite(div= Math.pow(base, w))) w--;
      r+= (r<0 ? -1:1)* parseInt(n.substr(0, w), base)/ div;
    }
  }
  return r;
}; 

Jorge Chamorro 2

(cljs post)

String.prototype.toFP= (function (regExpCache) {
 /* 20100531, by jo...@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;
 };

})([]);

Scott Sauyet 1

(cljs post)

var parseFloat_ss = (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) * (
             (match[2] == "" ? 0 : parseInt(match[2], base)) + parseFraction(match[3], base)
        );
    };
}()); 

John Stockton 1 - updated

(website)

function parsFixdB(S, R) {
  S = S.replace(/(\w*\.)/i, "0$1") // ensure digit 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 }

John Stockton 2 - updated

(website)

function pInt(Ch, Rdx) {
  var T = pInt.Digits.indexOf(Ch.toLowerCase())
  return (T < 0 || T >= Rdx) ? NaN : T }

pInt.Digits = "0123456789abcdefghijklmnopqrstuvwxyz"

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

Benjamin 'BeRo' Rosseaux

(cljs post)

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);
}

Scott Sauyet 2

(cljs post)

var parseFloat_ss2 = (function() {
    var origPF = parseFloat,
        DEPTH = 2,
        maxInt = Math.pow(2, 53),
        allDigits = "0123456789abcdefghijklmnopqrstuvwxyz",
        regexes = [null, null],
        powers = [null, null],
        parseFraction = function(str, base) {
            if (!str) return 0;
            var basePowers = powers[base], 
                max = basePowers[basePowers.length - 1];
                total = 0, 
                denominator = 1,
                count = 0;
            while (str.length && count < DEPTH) {
                var index = Math.min(str.length, basePowers.length - 1);
                total += parseInt(str.substring(0, index), base) / (denominator * basePowers[index]);
                str = str.substring(index);
                denominator *= max;
                count++;
            }
            return total;
        };
    (function() {
        for (var base = 2; base <=36; base++) {
            var digits = allDigits.substring(0, base);
            regexes[base]= new RegExp("(^[\\-\\+]?)([" + digits +
                               "]*)(?:\\.([" + digits + "]*))?$", "i");
            powers[base] = [];
            var power = 1, i = 0;
            while (power <= maxInt) {
                powers[base][i] = power;
                power *= base;
                i++;
            }
        }
    }());

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

John Stockton 3 - updated

The intent of this version is to be obviously and exactly correct without any care for speed.

(website)

function refParseFixed(IN, Rdx) { var Tmp, Sgn = +1, J = 0, Scale
  // May be slow; is intended to be completely accurate.
  var Digits = "0123456789abcdefghijklmnopqrstuvwxyz"
  Tmp = IN.charAt(0) // possible sign
  if (Tmp == "-" || Tmp == "+") { J = 1
    if (Tmp == "-") Sgn = -1 }

  var Num = Int = [], Frc = [], K = IN.length, Cy = true, Bin = []
  while (J < K) { Tmp = IN.charAt(J++) // read char from string
    if (Tmp == "." && Num == Int) { Num = Frc ; continue }
    Tmp = Digits.indexOf(Tmp.toLowerCase()) // char to Number
    if (Tmp < 0 || Tmp >= Rdx) break
    if (Tmp > 0) Cy = false	// so not all zero
    Num.push(Tmp) } // arrays now hold digit Numbers
  if (Cy) return Sgn*0 // Zero (otherwise loops forever)

  // Process integer part :
  while (J = Int.length) { // not ==
    for (K = 0, Cy = 0 ; K < J ; K++) { // halving loop
      Tmp = Cy * Rdx + Int[K] ; Cy = Tmp % 2 ; Int[K] = (Tmp-Cy) / 2 }
    Bin.push(Cy)
    while (Int[0] == 0) Int.shift() }
  Bin.reverse()

  while (Bin.length && Bin[0]==0) Bin.shift() // Omit any ldg zeroes
  J = Bin.length - 54 ; Scale = 0.5 ; while (--J >= 0) Scale /= 2

  // Process fractional part :
  while (Bin.length < 54) { Cy = 0 ; K = Frc.length
    while (K--) { // doubling loop
      Tmp = Frc[K] * 2 + Cy ; Cy = +(Tmp>=Rdx) ; Frc[K] = Tmp % Rdx }
    if (Bin.length || Cy == 1) Bin.push(Cy)
    Scale *= 2 }

  Bin[52] += Bin[53] // Rounding: now use Bin[0..52]

  // Evaluate Bin and scale it :
  for (J = 0, Num = 0 ; J < 53 ; J++) { Num *= 2 ; Num += Bin[J] }
  return Sgn * Num / Scale } // end refParseFixed