/****************************************************************************
* @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;}
}