Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Number Base conversions #95

Closed
husayt opened this issue Oct 2, 2013 · 15 comments
Closed

Number Base conversions #95

husayt opened this issue Oct 2, 2013 · 15 comments

Comments

@husayt
Copy link

husayt commented Oct 2, 2013

@josdejong mentioned he wants to implement this.

I thought it will be good to have the card for this anyway.
Also here is the code I use and you can find useful:

 binBase: function (n, to, from) {
                var s, number = parseInt(n.toString(), from || 10);

                s = number.toString(to);

                switch (to) {
                case 10:
                    return number;
                case 16:
                    return "0x" + s.toUpperCase();
                case 2:
                    return "0b" + s;
                default:
                    return '\"' + s + '\"';
                }
            }

then you can define the following functions in math.js

  • function bin(x) = binBase(x, 2)
  • function oct(x) = binBase(x, 8)
  • function dec(x,y) = binBase(x, 10, y)
  • function hex(x) = binBase(x, 16)
  • function base(x,y,z) = binBase(x, y, z)

Above we prefix binary numbers with 0b and hex numbers with 0x. (was using 0 prefix for octal numbers, but that was confusing. Still in search for a better notation for them. )

@robz
Copy link

robz commented Oct 4, 2013

what about using 0c for octal?

@josdejong
Copy link
Owner

@josdejong mentioned he wants to implement this.

Yes, well actually I don't want to do just conversions but I would like to implement special types for different number bases, like Bin, Oct, Hex, and allow calculations with these types. A function bin(x) will return a variable with type Bin, hex(x) will return a Hex, etc. The toString() function of a Bin will return a prefixed string representation of the value, like 0b1011. And the valueOf() will convert the binary value in a regular decimal number (if possible). The expression parser will get support for these prefixed numbers so math.eval('0b1011') will return a Bin.

These types should not store their value as as a regular (floating point) number, but in a BigNumber kind of way, with arbitrary precision. That way we can work with values which do not fit in a regular Number and we prevent weird round-off errors. To work with negative numbers, we will have to add an option to specify the number of bytes used to store the number so we can apply the Two's complement rule.

@owenversteeg
Copy link
Contributor

This is something I'd like to see. Any idea on a timeframe?

@josdejong
Copy link
Owner

I don't think this will be implemented on short term (like months). We could implement this straight away, but I expect this to be much less work if we first implement a solution to create functions in a modular, extensible way. So instead of one huge function add(anything, anything), separate it into functions add(number, number), add(complex, complex), etc. When loading math.js, these functions should be merged automatically by a "function composer". This function composer will have easy ways to define default conversions of data types, like automatically convert booleans to numbers if there is no boolean implementation of the function at hand.

See roadmap for my take on priorities https://github.com/josdejong/mathjs/blob/master/ROADMAP.md, which can be discussed of course.

@josdejong josdejong mentioned this issue Sep 17, 2014
@husayt
Copy link
Author

husayt commented Sep 17, 2014

Thanks Jos, it might be good to review the roadmap, as some of these items are already done and some still left on ver. 1 branch.

@owenversteeg
Copy link
Contributor

Hmm, that sounds like an interesting plan. I like the idea of a function composer.

@josdejong
Copy link
Owner

@husayt I think the roadmap is up to date (most is planned for version 1.x), which ones are already done?

@Frodojj
Copy link

Frodojj commented Oct 15, 2015

I wrote an extension to support base-n numbers for my app. You can use it for math.j's; I'm donating it to the public domain. It seems to work, but there are still a few issues:

  1. Precision seems to not imported from math instance. ex:

    • 3^(\xa i) = -0.009451260169808602 - 0.9999553358431579(i)
    • dec(3^(\xa i)) = -0.0094512601698086 - 0.99995533584316i

    but

    • (\xF+\xFi)^2 = \x0.000000000007c183c97272714 + \x1c2.00000000001(i)
    • square(\xF+\xFi) = \x1c2(i)

    Can factory functions be used to correct this? How?

  2. Requires preprocessing code before the parser. Ex:

          // from MathProcessor.js
          expr = expr.replace(/\\(x[0-9A-Fa-f]*[.]?[0-9A-Fa-f]*)/g, 'baseN("$1")'); // hexadecimal
          expr = expr.replace(/\\(o[0-7]*[.]?[0-7]*)/g, 'baseN("$1")'); // octal
          expr = expr.replace(/\\(b[0-1]*[.]?[0-1]*)/g, 'baseN("$1")'); // binary
          return math.eval(expr, scope);
  3. This isn't a bug, because the offending code was commented out. However, I don't know why it still works (I can't find any errors in the output), and I don't know by the offending code was causing an infinite loop in the first place. I must be misunderstanding something about math.js or my own code. Here is the commented-out code:

          /* Following causes infinite loop with 3^(\xa i) why???
          {
              from: "BaseNumber",
              to: "number",
              convert: function (aa) {
                  return aa.valueOf();
              }
          },
          */

Example of expected usage: \xFc.F - \b110.0101 * \o176.32 i I'll include the extension in the next post. I had trouble including it in this one.

@Frodojj
Copy link

Frodojj commented Oct 15, 2015

/*global math */
/*
    issues
        - Precision not imported from math instance. ex: 3^(\xa i), (\xF+\xFi)^2. Use factory function? How?
        - Conversion object from "BaseNumber" to "number" causes infinite loop. Why? Still works without it. Why?
        - Must enclose dec() function to use with function-plot.js
    preprocessing code
        // from MathProcessor.js
        expr = expr.replace(/\\(x[0-9A-Fa-f]*[.]?[0-9A-Fa-f]*)/g, 'baseN("$1")'); // hexadecimal
        expr = expr.replace(/\\(o[0-7]*[.]?[0-7]*)/g, 'baseN("$1")'); // octal
        expr = expr.replace(/\\(b[0-1]*[.]?[0-1]*)/g, 'baseN("$1")'); // binary
        return math.eval(expr, scope);
    @license
        Permission is hereby granted, free of charge, to any person obtaining
        a copy of this software and associated documentation files (the
        "Software"), to deal in the Software without restriction, including
        without limitation the rights to use, copy, modify, merge, publish,
        distribute, sublicense, and/or sell copies of the Software, and to
        permit persons to whom the Software is furnished to do so.

        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
        OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
        MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
        IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
        CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
        TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
        SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
(function () {
    "use strict";

    // Object Type for Base-N numbers (like hex, oct, bin, etc)
    function BaseNumber(vv, bb) {
        if (typeof vv === "string" || vv instanceof String) {
            return this.parseString(vv, bb);
        }
        if (vv.isBaseNumber) {
            this.value = vv.value;
            this.base = bb || vv.base;
        } else {
            this.value = vv;
            this.base = bb;
        }
    }
    BaseNumber.prototype = {
        BINID: "\\b",
        OCTID: "\\o",
        HEXID: "\\x",
        base: 10,
        value: 0,
        isBaseNumber: true,
        create: function (vv, bb) {
            return new BaseNumber(vv, bb);
        },
        valueOf: function () {
            return this.value;
        },
        toString: function (bb, vv) {
            var txt = "", prefix = "", base = bb || this.base, value = vv || this.value;

            if (value instanceof math.type.Complex) {
                if (value.re) {
                    txt = this.toString(base, value.re);
                }
                if (value.re && value.im) {
                    if (value.im < 0) {
                        txt = txt + " - " + this.toString(base, math.abs(value.im)) + "(i)";
                    } else {
                        txt = txt + " + " + this.toString(base, value.im) + "(i)";
                    }
                } else if (value.im) {
                    txt = this.toString(base, value.im) + "(i)";
                }
            } else {
                switch (base) {
                case 2:
                    prefix = this.BINID;
                    break;
                case 8:
                    prefix = this.OCTID;
                    break;
                case 16:
                    prefix = this.HEXID;
                    break;
                }

                txt = Number((value).valueOf()).toString(base);

                if (txt.charAt(0) === "-") {
                    txt = "-" + prefix + txt.substr(1);
                } else {
                    txt = prefix + txt;
                }
            }
            return txt;
        },
        parseString: function (vv, bb) {
            var txt = String(vv).trim(),
                sign = 1,
                base = 10,
                value = 0,
                pre = "";

            // Determine sign of x, remove it if so.
            if (txt.charAt(0) === "-") {
                txt = txt.substr(1);
                sign = -1;
            }

            // Remove a "\" character if it is in front of the string.
            if (txt.charAt(0) === "\\") {
                txt = txt.substr(1);
            }

            // Determine if base 2,8,10,16 based on the character in front
            // Remove identifying character if so.
            if (txt.length > 1) {
                pre = txt.charAt(0);
                if (pre === "b") {
                    base = 2;
                    txt = txt.substr(1);
                } else if (pre === "o") {
                    base = 8;
                    txt = txt.substr(1);
                } else if (pre === "x") {
                    base = 16;
                    txt = txt.substr(1);
                }
            }

            /*
                Convert remaining text to decimal.
                If it has a fractional part, convert that too.
                d(i) = digit in place i of string. p = length of string (with no ".").
                before the decimal place:
                    whole part = d(1)*b^(p-1) + d(2)*b^(p-2) + ... d(p-1)*b^1 + d(p)*b^0
                    whole part = parseInt
                after the decimal place:
                    fractional part =  d(1)/b^1 + d(2)/b^2 + ... d(p-1)/b^(p-1) + d(p)/b^p
                    fractional part = (d(1)/b^1 + d(2)/b^2 + ... d(p-1)/b^(p-1) + d(p)/b^p)*b^p/b^p
                    fractional part = (d(1)*b^p/b^1 + d(2)*b^p/b^2 + ... d(p-1)*b^p/b^(p-1) + d(p)*b^p/b^p)/b^p
                    fractional part = (d(1)*b^(p-1) + d(2)*b^(p-2) + ... d(p-1)*b^1 + dp*b^0)/b^p
                    fractional part = parseInt / b^p
                value = before the decimal place + after the decimal place
                value = parseInt(txt before decimal) + parseInt(txt after decimal) / (base ^ length after decimal)
            */
            txt = txt.split(".");
            if (txt[0].length > 0) {
                value = sign * parseInt(txt[0], base);
            }
            if (txt.length > 1 && txt[1].length > 0) {
                value += sign * parseInt(txt[1], base) / Math.pow(base, txt[1].length);
            }

            return new BaseNumber(value, bb || base);
        }
    };

    // Register BaseNumber with math.js
    math.typed.addType({
        name: "BaseNumber",
        test: function (aa) {
            return aa && aa.isBaseNumber;
        }
    });

    // BaseNumber conversions for math.js
    math.typed.conversions.push(
        {
            from: "number",
            to: "BaseNumber",
            convert: function (num) {
                return new BaseNumber(num, 10);
            }
        },
        /* Following causes infinite loop with 3^(\xa i) why???
        {
            from: "BaseNumber",
            to: "number",
            convert: function (aa) {
                return aa.valueOf();
            }
        },
        */
        {
            from: "BaseNumber",
            to: "Complex",
            convert: function (aa) {
                if (aa.valueOf() instanceof math.type.Complex) {
                    return aa.valueOf();
                }
                return new math.type.Complex(aa.valueOf(), 0);
            }
        },
        {
            from: "Complex",
            to: "BaseNumber",
            convert: function (aa) {
                return new BaseNumber(aa, 10);
            }
        },
        {
            from: "string",
            to: "BaseNumber",
            convert: function (txt) {
                return new BaseNumber(txt);
            }
        },
        {
            from: "BaseNumber",
            to: "string",
            convert: function (aa) {
                return aa.toString();
            }
        },
        {
            from: "boolean",
            to: "BaseNumber",
            convert: function (bool) {
                return new BaseNumber(bool, 2);
            }
        }
    );

    // BaseNumber additions to math.js library
    math.import({
        baseN: function (xx, yy) {
            // Makes BaseNumber objects
            return new BaseNumber(xx, yy);
        },
        hex: function (xx) {
            // Makes Hexidecimal BaseNumber objects
            return new BaseNumber(xx, 16);
        },
        oct: function (xx) {
            // Makes Octadecimal BaseNumber objects
            return new BaseNumber(xx, 8);
        },
        bin: function (xx) {
            // Makes Binary BaseNumber objects
            return new BaseNumber(xx, 2);
        },
        dec: function (xx) {
            // Converts to a base10 decimal representation
            if (typeof xx === "string" || xx instanceof String) {
                return (new BaseNumber(xx)).valueOf();
            }
            if (xx.isBaseNumber) {
                return xx.valueOf();
            }
            return xx;
        },
        add: math.typed("add", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.add(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.add(aa.valueOf(), bb), aa.base);
            }
        }),
        subtract: math.typed("subtract", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.subtract(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.subtract(aa.valueOf(), bb), aa.base);
            }
        }),
        multiply: math.typed("multiply", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.multiply(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.multiply(aa.valueOf(), bb), aa.base);
            }
        }),
        divide: math.typed("divide", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.divide(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.divide(aa.valueOf(), bb), aa.base);
            }
        }),
        pow: math.typed("pow", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.pow(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.pow(aa.valueOf(), bb), aa.base);
            }
        }),
        mod: math.typed("mod", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.mod(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.mod(aa.valueOf(), bb), aa.base);
            }
        }),
        abs: math.typed("abs", {
            BaseNumber: function (aa) {
                return new BaseNumber(math.abs(aa.valueOf()), aa.base);
            }
        }),
        sqrt: math.typed("sqrt", {
            BaseNumber: function (aa) {
                return new BaseNumber(math.sqrt(aa.valueOf()), aa.base);
            }
        }),
        square: math.typed("square", {
            BaseNumber: function (aa) {
                return new BaseNumber(math.square(aa.valueOf()), aa.base);
            }
        }),
        cube: math.typed("cube", {
            BaseNumber: function (aa) {
                return new BaseNumber(math.cube(aa.valueOf()), aa.base);
            }
        }),
        sign: math.typed("sign", {
            BaseNumber: function (aa) {
                return new BaseNumber(math.sign(aa.valueOf()), aa.base);
            }
        }),
        unaryMinus: math.typed("unaryMinus", {
            BaseNumber: function (aa) {
                return new BaseNumber(math.unaryMinus(aa.valueOf()), aa.base);
            }
        }),
        unaryPlus: math.typed("unaryPlus", {
            BaseNumber: function (aa) {
                return new BaseNumber(math.unaryPlus(aa.valueOf()), aa.base);
            }
        }),
        bitNot: math.typed("bitNot", {
            BaseNumber: function (aa) {
                return new BaseNumber(math.bitNot(aa.valueOf()), aa.base);
            }
        }),
        bitAnd: math.typed("bitAnd", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.bitAnd(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.bitAnd(aa.valueOf(), bb), aa.base);
            }
        }),
        bitOr: math.typed("bitOr", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.bitOr(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.bitOr(aa.valueOf(), bb), aa.base);
            }
        }),
        bitXor: math.typed("bitXor", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.bitXor(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.bitXor(aa.valueOf(), bb), aa.base);
            }
        }),
        leftShift: math.typed("leftShift", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.leftShift(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.leftShift(aa.valueOf(), bb), aa.base);
            }
        }),
        rightArithShift: math.typed("rightArithShift", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.rightArithShift(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.rightArithShift(aa.valueOf(), bb), aa.base);
            }
        }),
        rightLogShift: math.typed("rightLogShift", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.rightLogShift(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.rightLogShift(aa.valueOf(), bb), aa.base);
            }
        }),
        not: math.typed("not", {
            BaseNumber: function (aa) {
                return new BaseNumber(math.not(aa.valueOf()), aa.base);
            }
        }),
        and: math.typed("and", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.and(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.and(aa.valueOf(), bb), aa.base);
            }
        }),
        or: math.typed("or", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.or(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.or(aa.valueOf(), bb), aa.base);
            }
        }),
        xor: math.typed("xor", {
            "BaseNumber, BaseNumber | number": function (aa, bb) {
                return new BaseNumber(math.xor(aa.valueOf(), bb.valueOf()), aa.base);
            },
            "BaseNumber, Complex": function (aa, bb) {
                return new BaseNumber(math.xor(aa.valueOf(), bb), aa.base);
            }
        })
    });

    // Add documentation for base-N numbers to Math.js
    math.expression.docs.hex = {
        name: "hex",
        category: "Base-N Numbers",
        description: "Creates a real base-16 hexadecimal number from a string or another number",
        syntax: ["hex(string)", "hex(number)"],
        examples: ['hex("xAF.F")', "hex(500)"],
        seealso: ["bin", "oct", "dec", "baseN"]
    };
    math.expression.docs.oct = {
        name: "oct",
        category: "Base-N Numbers",
        description: "Creates a real base-8 number from a string or another number",
        syntax: ["oct(string)", "oct(number)"],
        examples: ['oct("o76.05")', "oct(\\b1001)"],
        seealso: ["bin", "oct", "dec", "baseN"]
    };
    math.expression.docs.bin = {
        name: "bin",
        category: "Base-N Numbers",
        description: "Creates a real base-2 binary number from a string or another number",
        syntax: ["bin(string)", "bin(number)"],
        examples: ['bin("b1001.11")', "bin(500)"],
        seealso: ["bin", "oct", "dec", "baseN"]
    };
    math.expression.docs.dec = {
        name: "dec",
        category: "Base-N Numbers",
        description: "Converts a base-N number to a base-10 number",
        syntax: ["dec(string)", "dec(number)"],
        examples: ['dec("12345.06789")', "dec(500)"],
        seealso: ["bin", "oct", "dec", "baseN"]
    };
    math.expression.docs.baseN = {
        name: "baseN",
        category: "Base-N Numbers",
        description: "Creates a real base-N number from a string or another number",
        syntax: ["baseN(string)", "baseN(number, base)"],
        examples: ['baseN("xAF.F")', "baseN(500,3)"],
        seealso: ["bin", "oct", "dec", "baseN"]
    };
}());

@josdejong
Copy link
Owner

Thanks for sharing your solution @Frodojj

@q2apro
Copy link

q2apro commented Sep 14, 2018

Has this been implemented in mathjs?

I would like to use math.eval() to do the conversion between binary, hexadecimal and decimal.

Is that possible? Thanks.

@josdejong
Copy link
Owner

@q2apro this hasn't been implemented yet.

@contentfree
Copy link

Checking on the relative priority of this? I see that some folks have implemented this on their own (notably @Frodojj above). Any plans to implement this? (@Frodojj do you think you could create a Pull Request with your solution above?)

@josdejong
Copy link
Owner

See #1968, @clnhlzmn has created a PR for this.

@josdejong
Copy link
Owner

Support for hex, bin, and oct numbers in the expression parser is now available in v7.3.0 🎉 .

We have plans to extend this further, but I'll close this issue now since there is a working version now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants