/****************************************************************************
* @function    : toFixed(number, [digits])
* @purpose     : Emulate toFixed() for large numbers and
                 large fractional numbers with correct rounding
                 by using the BigInt() built-in Object.
* @version     : 1.01
* @author      : Mohsen Alyafei
* @date        : 03 February 2021
* @param       : {number} [numeric or string] pass large numbers as a string
* @param       : {number} [optional digits]
*              : The number of digits to appear after the decimal point;
*              : this may be a value from 0 and above unlimited.
*              : If this argument is omitted or is negative, it is treated as 0.
*              : Handles large 'e' notation number using the eToNumber() function.digits
*              : See https://stackoverflow.com/a/66072001/11606728
* @returns     : A string representing the given number using fixed-point notation.
****************************************************************************/
function toFixed(num,digits) {
    if (!num && num!==0) return "Cannot read property of null or undefined"; // Can change it to throw Error
    digits<0 && (digits=0);
    digits=digits||0;
    num=eToNumber(num); // Delete this line and function below if larage 'e' notation number are not required
    let wh = (num+="").split((.1).toLocaleString().substr(1,1)), f = wh[1];
    wh = wh[0];
    let fr = (f=f||"0").substr(0,digits), fn = BigInt(fr), w = BigInt(wh), fl = (""+fn).length,
        lZeros = fr.length-fl, minus = wh[0]=="-", inc = (wh<0 || minus) ? BigInt(-1):BigInt(1);
    f[digits]>=5 && (fn+=BigInt(1));
    (fn+="").length > fl && (lZeros-=1);
    lZeros >=0 ? lZeros="0".repeat(lZeros):(fn=fn.substring(1), lZeros="",
               (fn ? w +=inc : ((f[digits]>=5) && (w+=inc))));
    fn = lZeros + fn; L = digits-fn.length;
    L && (fn+="0".repeat(L)); w==0 && minus && (w="-"+w);
    return w+(fn?".":"")+fn;
    }
    //---------------------- Extra Function if needed --------------------------------
    // Delete this function if large 'e' notation number are not required
    // Convert very large 'e' numbers to plain string numbers.
    //--------------------------------------------------------------------------------
    function eToNumber(num) {
        let sign="";
        (num+="").charAt(0)=="-" && (num=num.substring(1),sign ="-");
        let arr = num.split(/[e]/ig); if (arr.length<2) return sign+num;
        let dot=(.1).toLocaleString().substr(1,1), n = arr[0], exp = +arr[1];
        let w = (n=n.replace(/^0+/,'')).replace(dot,''),
          pos = n.split(dot)[1]? n.indexOf(dot)+exp : w.length+exp,
            L = pos-w.length,s=""+BigInt(w);
         w = exp>=0 ? (L>=0 ? s+"0".repeat(L):r()): (pos<=0 ? "0"+dot+"0".repeat(Math.abs(pos))+s:r());
        if (!+w) w=0; return sign+w;
        function r(){return w.replace(new RegExp(`^(.{${pos}})(.)`),`$1${dot}$2`)}}
//================================================
//             Test Cases
//================================================
let r=0; // test tracker
r |= test(35.855,2,"35.86");
r |= test(12.00000015,2,"12.00");
r |= test(35.855,5,"35.85500");
r |= test(35.855,4,"35.8550");
r |= test(1.135,2,"1.14");
r |= test(1.135,3,"1.135");
r |= test(1.135,4,"1.1350");
r |= test(1.135,8,"1.13500000");
r |= test(0.1545,3,"0.155");
r |= test(89.684449,2,"89.68");
r |= test("0.0000001",2,"0.00");
r |= test("0.9993360575508052",3,"0.999");
r |= test("0.999336057550805244545454545",29,"0.99933605755080524454545454500");
r |= test("1.0020739645577939",3,"1.002");
r |= test(0.999,0,"1");
r |= test(0.999,1,"1.0");
r |= test(0.999,2,"1.00");
r |= test(0.975,0,"1");
r |= test(0.975,1,"1.0");
r |= test(0.975,2,"0.98");
r |= test(2.145,2,"2.15");
r |= test(2.135,2,"2.14");
r |= test(2.34,1,"2.3");
r |= test(2.35,1,"2.4");
r |= test("0.0000001",2,"0.00");
r |= test("0.0000001",7,"0.0000001");
r |= test("0.0000001",8,"0.00000010");
r |= test("0.00000015",2,"0.00");
if (r==0) console.log("Tests 01. Standard fractional numbers passed");
//================================================
r=0; // test tracker
r |= test("1234567890123456789444.99",5,"1234567890123456789444.99000");
r |= test("1234567890123456789444.1445",3,"1234567890123456789444.145");
r |= test("1234567890123456789444.14451445144514451745",19,"1234567890123456789444.1445144514451445175");
if (r==0) console.log("Tests 02. Large fractional numbers passed");
//================================================
r=0; // test tracker
r |= test(100,2,"100.00");
r |= test(0,5,"0.00000");
if (r==0) console.log("Tests 03. Non-fractional numbers passed");
//================================================
r=0; // test tracker
r |= test(12345.6789,null,"12346");
r |= test(2.1234,null,"2");
r |= test(12345.6789,undefined,"12346");
r |= test(2.1234,undefined,"2");
r |= test(12345.6789,"","12346");
r |= test(0.1234,"","0");
r |= test(2.1234,"","2");
if (r==0) console.log("Tests 04. Undefined, Null, and Empty Digits passed");
//================================================
r=0; // test tracker
r |= test(1.1155,2,"1.12");
r |= test(1.255,2,"1.26");
r |= test(1.265,2,"1.27");
r |= test(1.275,2,"1.28");
r |= test(1.285,2,"1.29");
r |= test(1.295,2,"1.30");
r |= test(2.05,1,"2.1");
r |= test(2.15,1,"2.2");
r |= test(2.55,1,"2.6");
r |= test(2.65,1,"2.7");
r |= test(2.215,2,"2.22");
r |= test(2.315,2,"2.32");
r |= test(2.715,2,"2.72");
r |= test(2.815,2,"2.82");
r |= test(2.005,2,"2.01");
r |= test(2.105,2,"2.11");
r |= test(2.405,2,"2.41");
r |= test(2.505,2,"2.51");
r |= test(2.605,2,"2.61");
r |= test(2.905,2,"2.91");
r |= test(0.00155,4,"0.0016");
r |= test(2.55,1,"2.6");
r |= test(-2.35,1,"-2.4");
if (r==0) console.log("Tests 05. Correct rounding passed");
//================================================
r=0; // test tracker
r |= test(-1.125,2,"-1.13");
r |= test(-1.15,1,"-1.2");
r |= test(-1.15,1,"-1.2");
r |= test(-1.45,1,"-1.5");
r |= test(-1.65,1,"-1.7");
r |= test(-1.95,1,"-2.0");
r |= test(-2.34,1,"-2.3");
r |= test("-0.024641163062896567",3,"-0.025");
r |= test("-0.024641163062896567",16,"-0.0246411630628966");
r |= test("0.024641163062896567",16, "0.0246411630628966");
r |= test("-0.0246411630628965",16,"-0.0246411630628965");
if (r==0) console.log("Tests 06. Negative numbers rounding passed");
//================================================
r=0; // test tracker
r |= test(.135,2,"0.14");    // without whole part
r |= test(-.135,2,"-0.14");
r |= test("+35.855",2,"35.86");
r |= test("0.0",2,"0.00");
r |= test("-0",2,"-0.00");       //minus 0
r |= test("-0.0",5,"-0.00000");  // minus 0
r |= test("",5,"Cannot read property of null or undefined");        // empty string
r |= test(null,5,"Cannot read property of null or undefined");      //null
r |= test(undefined,5,"Cannot read property of null or undefined"); // undefined
if (r==0) console.log("Tests 07. Special test cases passed");
//================================================
r=0; // test tracker
r |= test(1.1234e1,2,"11.23");      //11.234
r |= test(1.12e2,2,"112.00");       //112
r |= test(-1.1234e2,2,"-112.34");   // -112.34
r |= test(-1.1234e2,4,"-112.3400"); // -112.34
r |= test(-1.1235e2,2,"-112.35");   // -112.35
r |= test(-1.1235e2,1,"-112.4");    // -112.4
if (r==0) console.log("Tests 08. Standard e notation numbers passed");
//================================================
r=0; // test tracker
r |= test("123456789123456789.111122223333444455556666777788889999e+10",16,"1234567891234567891111222233.3344445555666678");
r |= test("1.1235678944556677e2",20,"112.35678944556677000000");
r |= test("99.1235678944556677e2",20,"9912.35678944556677000000");
if (r==0) console.log("Tests 09. Large e notation numbers passed");
//================================================
if (r==0) console.log(`${"-".repeat(22)}\nAll Test Cases Passed.\n${"-".repeat(22)}`);
//================================================
//             Test function
//================================================
function test(n1,n2,should) {
let result = toFixed(n1,n2);
if (result !== should) {console.log(`Output   : ${result}\nShould be: ${should}`);return 1;}
}