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

D

Dr J R Stockton

Some may not have noted that Number.toString(16) handles non-integers.
Math.PI.toString(16) -> 3.243f6a8885a3
1e44.toString(16) -> 47bf19673df53000000000000000000000000
Math.random().toString(2) => 0 . /[01]{1,53}/

ECMA 262 (5) 15.7.4.2 Number.prototype.toString ( [ radix ] )
makes no mention of non-integers.

A Hex integer string H can be turned into a Number by
+ ( "0x" + H )
but that does not work for a Hex fraction string. Is there in fact an
easy built-in way of converting non-integer Hex strings to Number?

Otherwise, I suggest that ECMA 6 should specify a Method whereby for ANY
Number X the result of X.toString(B) can be converted to the original X
(disregarding the sign of zero).
 
R

Ry Nohryb

Some may not have noted that Number.toString(16) handles non-integers.
        Math.PI.toString(16) -> 3.243f6a8885a3
        1e44.toString(16) -> 47bf19673df53000000000000000000000000
        Math.random().toString(2) => 0 . /[01]{1,53}/

My dear friend Garrett and I know that at least since:
http://groups.google.com/group/comp.lang.javascript/msg/936a683de6159fd0
ECMA 262 (5) 15.7.4.2 Number.prototype.toString ( [ radix ] )
makes no mention of non-integers.

A Hex integer string H can be turned into a Number by
        + ( "0x" + H )
but that does not work for a Hex fraction string.  Is there in fact an
easy built-in way of converting non-integer Hex strings to Number?

a= 113356057.78731406
--> 113356057.78731406

b= a.toString(16).split('.');
--> [ "6c1ad19", "c98d6a" ]

c= +( '0x'+ b.join('') ) / Math.pow(16, b[1].length)
--> 113356057.78731406
 
E

Evertjan.

Ry Nohryb wrote on 27 mei 2010 in comp.lang.javascript:
a= 113356057.78731406
--> 113356057.78731406

b= a.toString(16).split('.');
--> [ "6c1ad19", "c98d6a" ]

c= +( '0x'+ b.join('') ) / Math.pow(16, b[1].length)
--> 113356057.78731406

or just add the fractional part to the integer part.

===========================================

Try this for any radix [0 .. 36]:

<script type='text/javascript'>

document.write(radixTo10('6c1ad19.c98d6a',16) + '<br>')
// --> 113356057.78731406
document.write(radixTo10('778',8) + '<br>')
// --> 511
document.write(radixTo10('z',36) + '<br>')
// --> 35
document.write(radixTo10('10',36) + '<br>')
// --> 36


function radixTo10int(v,radix) {
var n = 0;
for (i=0;i<v.length;i++) {
temp = v;
temp = (temp>'9')
? 10+temp.charCodeAt(0)-'a'.charCodeAt(0)
: +temp;
//if (temp>radix-1) return 'error';
n *= radix;
n += temp;
};
return n;
};

function radixTo10(v,radix) {
v = v.toLowerCase().split('.');
if (v.length==1)
return radixTo10int(v[0],radix);
return radixTo10int(v[0],radix) +
radixTo10int(v[1],radix) / Math.pow(radix,v[1].length);
};

</script>
 
E

Evertjan.

Evertjan. wrote on 27 mei 2010 in comp.lang.javascript:
Ry Nohryb wrote on 27 mei 2010 in comp.lang.javascript:
a= 113356057.78731406
--> 113356057.78731406

b= a.toString(16).split('.');
--> [ "6c1ad19", "c98d6a" ]

c= +( '0x'+ b.join('') ) / Math.pow(16, b[1].length)
--> 113356057.78731406

or just add the fractional part to the integer part.

===========================================

Try this for any radix [0 .. 36]:

<script type='text/javascript'>

document.write(radixTo10('6c1ad19.c98d6a',16) + '<br>')
// --> 113356057.78731406
document.write(radixTo10('778',8) + '<br>')
// --> 511

sorry:

document.write(radixTo10('777',8) + '<br>')
// --> 511

document.write(radixTo10('z',36) + '<br>')
// --> 35
document.write(radixTo10('10',36) + '<br>')
// --> 36


function radixTo10int(v,radix) {
var n = 0;
for (i=0;i<v.length;i++) {
temp = v;
temp = (temp>'9')
? 10+temp.charCodeAt(0)-'a'.charCodeAt(0)
: +temp;
//if (temp>radix-1) return 'error';
n *= radix;
n += temp;
};
return n;
};

function radixTo10(v,radix) {
v = v.toLowerCase().split('.');
if (v.length==1)
return radixTo10int(v[0],radix);
return radixTo10int(v[0],radix) +
radixTo10int(v[1],radix) / Math.pow(radix,v[1].length);
};

</script>
 
R

Ry Nohryb

Ry Nohryb wrote on 27 mei 2010 in comp.lang.javascript:
a= 113356057.78731406
--> 113356057.78731406
b= a.toString(16).split('.');
--> [ "6c1ad19", "c98d6a" ]
c= +( '0x'+ b.join('') ) / Math.pow(16, b[1].length)
--> 113356057.78731406

or just add the fractional part to the integer part.

===========================================

Try this for any radix [0 .. 36]:

<script type='text/javascript'>

document.write(radixTo10('6c1ad19.c98d6a',16) + '<br>')
// --> 113356057.78731406
document.write(radixTo10('778',8) + '<br>')
// --> 511
document.write(radixTo10('z',36) + '<br>')
// --> 35
document.write(radixTo10('10',36) + '<br>')
// --> 36

function radixTo10int(v,radix) {
  var n = 0;
  for (i=0;i<v.length;i++) {
    temp = v;
    temp = (temp>'9')
      ? 10+temp.charCodeAt(0)-'a'.charCodeAt(0)
      : +temp;
    //if (temp>radix-1) return 'error';
    n *= radix;
    n += temp;
  };
  return n;

};

function radixTo10(v,radix) {
  v = v.toLowerCase().split('.');
  if (v.length==1)
    return radixTo10int(v[0],radix);
  return radixTo10int(v[0],radix) +
  radixTo10int(v[1],radix) / Math.pow(radix,v[1].length);

};

</script>


Cute. How about this one :) ?

String.prototype.toFP= function (base, digits, r, w, n) {
digits= "0123456789abcdefghijklmnopqrstuvwxyz", r= 0;
w= (n= this.toLowerCase().split('.'))[0].length;
n.join('').split('').forEach(function (s) {
r+= digits.indexOf(s) * Math.pow(base, --w) });
return r;
};



a= Math.PI
--> 3.141592653589793
b= a.toString(16)
--> "3.243f6a8885a3"
a.toFP(16)
--> 3.141592653589793
b= a.toString(36)
--> "3.53i5ab8p5fc5vayqter60f6r"
b.toFP(36)
--> 3.141592653589793
b= a.toString(2)
--> "11.001001000011111101101010100010001000010110100011"
b.toFP(2)
--> 3.141592653589793
 
E

Evertjan.

Ry Nohryb wrote on 27 mei 2010 in comp.lang.javascript:
String.prototype.toFP= function (base, digits, r, w, n) {
digits= "0123456789abcdefghijklmnopqrstuvwxyz", r= 0;
w= (n= this.toLowerCase().split('.'))[0].length;
n.join('').split('').forEach(function (s) {
r+= digits.indexOf(s) * Math.pow(base, --w) });
return r;
};

Nice!

since all non-'digit' chars would count as -1,
I would test for that,
as that makes debugging extreemly difficult.

String.prototype.toFP= function (base, digits, r, w, n) {
if (/[^0-9a-z\.]/i.test(this)) return NaN; // <<<---
digits= "0123456789abcdefghijklmnopqrstuvwxyz", r= 0;
w= (n= this.toLowerCase().split('.'))[0].length;
n.join('').split('').forEach(function (s) {
r+= digits.indexOf(s) * Math.pow(base, --w) });
return r;
};
 
R

Ry Nohryb

Nice!

since all non-'digit' chars would count as -1,
I would test for that,
as that makes debugging extreemly difficult.

String.prototype.toFP= function (base, digits, r, w, n) {
  if (/[^0-9a-z\.]/i.test(this)) return NaN; // <<<---
  digits= "0123456789abcdefghijklmnopqrstuvwxyz", r= 0;
  w= (n= this.toLowerCase().split('.'))[0].length;
  n.join('').split('').forEach(function (s) {
    r+= digits.indexOf(s) * Math.pow(base, --w) });
  return r;
};

Yes, good idea. And instead of n.join('').split('').forEach(f) an
[].forEach.call(n.join(''), f)... might be faster:

String.prototype.toFP= function (base, digits, w, n, r) {
if (/[^0-9a-z\.]/i.test(this)) return NaN;
digits= "0123456789abcdefghijklmnopqrstuvwxyz";
w= (n= this.toLowerCase().split('.'))[r= 0].length;
n.forEach.call(n.join(''), function (s) {
r+= digits.indexOf(s) * Math.pow(base, --w) });
return r;
};

What do we do with the sign ?
 
E

Evertjan.

Ry Nohryb wrote on 27 mei 2010 in comp.lang.javascript:
Nice!

since all non-'digit' chars would count as -1,
I would test for that,
as that makes debugging extreemly difficult.

String.prototype.toFP= function (base, digits, r, w, n) {
  if (/[^0-9a-z\.]/i.test(this)) return NaN; // <<<---
  digits= "0123456789abcdefghijklmnopqrstuvwxyz", r= 0;
  w= (n= this.toLowerCase().split('.'))[0].length;
  n.join('').split('').forEach(function (s) {
    r+= digits.indexOf(s) * Math.pow(base, --w) });
  return r;
};

Yes, good idea. And instead of n.join('').split('').forEach(f) an
[].forEach.call(n.join(''), f)... might be faster:

String.prototype.toFP= function (base, digits, w, n, r) {
if (/[^0-9a-z\.]/i.test(this)) return NaN;
digits= "0123456789abcdefghijklmnopqrstuvwxyz";
w= (n= this.toLowerCase().split('.'))[r= 0].length;
n.forEach.call(n.join(''), function (s) {
r+= digits.indexOf(s) * Math.pow(base, --w) });
return r;
};

What do we do with the sign ?


this2 = this;
minus = 1;
if (this2.substr(0,1)=='-') {
this2 = this2.substr(1); // or .slice()?
minus = -1;
};
..........this2.........
return minus*r;
 
T

Thomas 'PointedEars' Lahn

Dr said:
Some may not have noted that Number.toString(16) handles non-integers.
Math.PI.toString(16) -> 3.243f6a8885a3
1e44.toString(16) -> 47bf19673df53000000000000000000000000
Math.random().toString(2) => 0 . /[01]{1,53}/

ECMA 262 (5) 15.7.4.2 Number.prototype.toString ( [ radix ] )
makes no mention of non-integers.

It does not have to mention non-integers as all Number values are internally
represented as IEEE-754 double-precision floating-point values, which by
definition includes non-integers.

| If radix not present or is undefined the Number 10 is used as the value of
| radix. If ToInteger(radix) is the Number 10 then this Number value is
| given as an argument to the ToString abstract operation; the resulting
| String value is returned.
|
| [...]
| If ToInteger(radix) is an integer from 2 to 36, but not 10, the result is
| a String representation of this Number value using the specified radix.
| Letters a-z are used for digits with values 10 through 35. The precise
| algorithm is implementation-dependent if the radix is not 10, however the
| algorithm should be a generalization of that specified in 9.8.1.
A Hex integer string H can be turned into a Number by
+ ( "0x" + H )

Or simply parseInt(H, 16).
but that does not work for a Hex fraction string. Is there in fact an
easy built-in way of converting non-integer Hex strings to Number?

No, I don't think so. Obviously you could do this:

var s = "f.0c";
var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];

/* 15.046875 = 15 + 12 * Math.pow(16, -2) */
var n = parseInt(s, 16) + parseInt(f, 16) / Math.pow(16, f.length);
Otherwise, I suggest that ECMA 6 should specify a Method whereby for ANY
Number X the result of X.toString(B) can be converted to the original X
(disregarding the sign of zero).

It would suffice if parseFloat() could take a second argument to specify the
base, as does parseInt(). But while it would be good to have it specified
in _ECMAScript Ed._ 6 ("Harmony"), I do not think we really need ES6 (and
probably to wait another 10 years) for that. It could, for example, be
implemented in JavaScript 1.9 as can be expected from Firefox 4.0, and then,
through competition, copied by other implementations like so many other
features before.


PointedEars
 
R

Ry Nohryb

(...)
  var s = "f.0c";
  var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];

  /* 15.046875 = 15 + 12 * Math.pow(16, -2) */
  var n = parseInt(s, 16) + parseInt(f, 16) / Math.pow(16, f.length);
(...)

We're almost there, but not yet:

function pointyParseFloat (s, base) {
var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];
return parseInt(s, base) + parseInt(f, base) / Math.pow(base,
f.length);
}

pointyParseFloat(Math.PI.toString(16), 16)
--> 3.141592653589793

pointyParseFloat(Math.PI.toString(33), 33)
--> 3.121212121212121

pointyParseFloat((-Math.PI).toString(16), 16)
--> -2.858407346410207

String.prototype.toFP= function (base, digits, w, n, r) {
if (/[^0-9a-z\.]/i.test(this)) return NaN;
digits= "0123456789abcdefghijklmnopqrstuvwxyz";
r= parseInt((n= this.toLowerCase().split('.'))[w= 0], base);
n.forEach.call(n[1], function (s) {
r+= digits.indexOf(s) * Math.pow(base, --w) });
return r;
};

(Math.PI).toString(16).toFP(16)
--> 3.141592653589793

(Math.PI).toString(33).toFP(33)
--> 3.141592653589793

(-Math.PI).toString(33).toFP(33)
--> NaN
 
R

Ry Nohryb

  (...)
  var s = "f.0c";
  var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];
  /* 15.046875 = 15 + 12 * Math.pow(16, -2) */
  var n = parseInt(s, 16) + parseInt(f, 16) / Math.pow(16, f.length);
  (...)

We're almost there, but not yet:
(...)

How about this one ?

String.prototype.toFP= function (base, d, w, n, r, s) {
if (/[^0-9a-z\.+-]/i.test(this)) return NaN;
d= "0123456789abcdefghijklmnopqrstuvwxyz";
s= (r= parseInt((n= this.split('.'))[w= 0], base)) < 0 ? -1 : 1;
n= n[1].toLowerCase().split('');
while (n.length) r+= s* d.indexOf(n.shift())* Math.pow(base, --w);
return r;
};

(-Math.PI).toString(33).toFP(33)
--> -3.141592653589793

Math.PI.toString(33).toFP(33)
--> 3.141592653589793

Math.PI.toString(16).toFP(16)
--> 3.141592653589793

(-Math.PI).toString(2).toFP(2)
--> -3.141592653589793

"-dead.bee".toFP(16).toString(16)
"-dead.bee"

"+bad.c0ffee".toFP(16).toString(16)
"bad.c0ffee"
 
R

Ry Nohryb

How about this one ?

String.prototype.toFP= function (base, d, w, n, r, s) {
  if (/[^0-9a-z\.+-]/i.test(this)) return NaN;
  d= "0123456789abcdefghijklmnopqrstuvwxyz";
  s= (r= parseInt((n= this.split('.'))[w= 0], base)) < 0 ? -1 :1;
  n= n[1].toLowerCase().split('');
  while (n.length) r+= s* d.indexOf(n.shift())* Math.pow(base, --w);
  return r;

};

D'oh, not yet... :)

"Pointy.isUgly".toFP(36).toString(36)
--> "pointy.isugkchaor"
 
T

Thomas 'PointedEars' Lahn

Ry said:
Thomas said:
var s = "f.0c";
var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];

/* 15.046875 = 15 + 12 * Math.pow(16, -2) */
var n = parseInt(s, 16) + parseInt(f, 16) / Math.pow(16, f.length);
(...)

We're almost there, but not yet:

function pointyParseFloat (s, base) {
var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];
return parseInt(s, base) + parseInt(f, base) / Math.pow(base,
f.length);
}

[...]
pointyParseFloat((-Math.PI).toString(16), 16)
--> -2.858407346410207

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);
String.prototype.toFP= function (base, digits, w, n, r) {
if (/[^0-9a-z\.]/i.test(this)) return NaN;
digits= "0123456789abcdefghijklmnopqrstuvwxyz";
r= parseInt((n= this.toLowerCase().split('.'))[w= 0], base);
n.forEach.call(n[1], function (s) {
r+= digits.indexOf(s) * Math.pow(base, --w) });
return r;
};

I prefer using regular expressions where possible but only where necessary,
and to avoid callbacks. So my current quick hack looks as follows:

/**
* Parses a string of characters into a Number value. It replaces the
* built-in function in that it supports fractional parts on non-decimal
* representations, and uses the built-in for decimal representations.
*
* @param s : String
* String representation to be parsed
* @param iBase : Number
* Numeric base of the representation, from 2 to 36 inclusive.
* @return number
*/
var parseFloat = jsx.string.parseFloat = (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);
};
}());

As for your misusing arguments as local variables and the resulting
unreadable, unmaintainable, and insecure code, that has been discussed ad
nauseam. Will you ever learn?
(Math.PI).toString(16).toFP(16)
--> 3.141592653589793

(Math.PI).toString(33).toFP(33)
--> 3.141592653589793

(-Math.PI).toString(33).toFP(33)
--> NaN

So you are essentially saying that you managed to produce code garbage,
as usual?

And stop calling me Pointy, Georgina.


PointedEars
 
R

Ry Nohryb

Ry said:
Thomas said:
var s = "f.0c";
var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];
/* 15.046875 = 15 + 12 * Math.pow(16, -2) */
var n = parseInt(s, 16) + parseInt(f, 16) / Math.pow(16, f.length);
  (...)
We're almost there, but not yet:
function pointyParseFloat (s, base) {
  var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];
  return parseInt(s, base) + parseInt(f, base) / Math.pow(base,
f.length);
}
[...]
pointyParseFloat((-Math.PI).toString(16), 16)
--> -2.858407346410207

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);
(...)

Better, but still not there:

function pointyParseFloat (s, base) {
var i = parseInt(s, base);
var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1];
return i + (i < 0 ? -1 : 1) * parseInt(f, base) / Math.pow(base,
f.length);
}

pointyParseFloat((-Math.PI).toString(33), 33)
--> -3.121212121212121
 
R

Ry Nohryb

(...)
I prefer using regular expressions where possible but only where necessary,
and to avoid callbacks.  So my current quick hack looks as follows:

/**
 * Parses a string of characters into a Number value.  It replaces the
 * built-in function in that it supports fractional parts on non-decimal
 * representations, and uses the built-in for decimal representations.
 *
 * @param s : String
 *   String representation to be parsed
 * @param iBase : Number
 *   Numeric base of the representation, from 2 to 36 inclusive.
 * @return number
 */
var parseFloat /*= jsx.string.parseFloat*/ = (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);
  };

}());


parseFloat(Math.PI.toString(33), 33)
--> NaN

Cool !
 
R

Ry Nohryb

(...)
So you are essentially saying that you managed to produce code garbage,
as usual?
LOL^2

And stop calling me Pointy, Georgina.

LOL, Georgina ? No, leave that or I'll call you MissPointy.
 
T

Thomas 'PointedEars' Lahn

Ry said:
Thomas said:
Ry said:
[...]
pointyParseFloat((-Math.PI).toString(16), 16)
--> -2.858407346410207

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);
(...)

Better, but still not there:

function pointyParseFloat (s, base) {
var i = parseInt(s, base);
var f = (s.match(/\.([\da-f]+)/i) || [, "0"])[1]; ^^^^^
return i + (i < 0 ? -1 : 1) * parseInt(f, base) / Math.pow(base,
f.length);
}

pointyParseFloat((-Math.PI).toString(33), 33) ^^ ^^
--> -3.121212121212121

You have not considered that this example was suited to *hexadecimal*
representations. parseFloat((-Math.PI).toString(33), 33) and
jsx.string.parseFloat((-Math.PI).toString(33), 33) as posted return
-3.141592653589793 (approx. -Math.PI), as expected.


PointedEars
 
T

Thomas 'PointedEars' Lahn

Ry said:
Thomas said:
var parseFloat /*= jsx.string.parseFloat*/ = (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);
};

}());


parseFloat(Math.PI.toString(33), 33)
--> NaN

I cannot confirm this. Where have you tested it?


PointedEars
 
T

Thomas 'PointedEars' Lahn

Ry said:
You may find it so, not me.


you opinion

Your code style is being frowned upon by several knowledgable people here.
each and every parameter is properly initialized before use.

Unless you happen to forget doing that, which is likely with that code
style.


PointedEars
 

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,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top