@entestat/formula
Version:
fast excel formula parser
777 lines (678 loc) • 27.6 kB
JavaScript
const FormulaError = require('../error');
const TextFunctions = require('./text');
const {FormulaHelpers, Types} = require('../helpers');
const H = FormulaHelpers;
const bessel = require("bessel");
const jStat = require("jstat");
const MAX_OCT = 536870911, // OCT2DEC(3777777777)
MIN_OCT = -536870912, // OCT2DEC4000000000)
MAX_HEX = 549755813887,
MIN_HEX = -549755813888,
MAX_BIN = 511, // BIN2DEC(111111111)
MIN_BIN = -512; // BIN2DEC(1000000000)
const numberRegex = /^\s?[+-]?\s?[0-9]+[.]?[0-9]*([eE][+\-][0-9]+)?\s?$/;
const IMWithoutRealRegex = /^\s?([+-]?\s?([0-9]+[.]?[0-9]*([eE][+\-][0-9]+)?)?)\s?[ij]\s?$/;
const IMRegex = /^\s?([+-]?\s?[0-9]+[.]?[0-9]*([eE][+\-][0-9]+)?)\s?([+-]?\s?([0-9]+[.]?[0-9]*([eE][+\-][0-9]+)?)?)\s?[ij]\s?$/;
function parseIM(textOrNumber) {
textOrNumber = H.accept(textOrNumber);
let real = 0, im = 0, unit = 'i';
if (typeof textOrNumber === "number")
return {real: textOrNumber, im, unit};
if (typeof textOrNumber === "boolean")
throw FormulaError.VALUE;
let match = textOrNumber.match(numberRegex);
if (match) {
real = Number(match[0]);
return {real, im, unit};
}
match = textOrNumber.match(IMWithoutRealRegex);
if (match) {
im = Number(/^\s?[+-]?\s?$/.test(match[1]) ? match[1] + '1' : match[1]);
unit = match[0].slice(-1);
return {real, im, unit};
}
match = textOrNumber.match(IMRegex);
if (match) {
real = Number(match[1]);
im = Number(/^\s?[+-]?\s?$/.test(match[3]) ? match[3] + '1' : match[3]);
unit = match[0].slice(-1);
return {real, im, unit};
}
throw FormulaError.NUM;
}
const EngineeringFunctions = {
BESSELI: (x, n) => {
x = H.accept(x, Types.NUMBER_NO_BOOLEAN);
n = H.accept(n, Types.NUMBER_NO_BOOLEAN);
// if n is not an integer, it is truncated.
n = Math.trunc(n);
if (n < 0) {
throw FormulaError.NUM;
}
return bessel.besseli(x, n);
},
BESSELJ: (x, n) => {
x = H.accept(x, Types.NUMBER_NO_BOOLEAN);
n = H.accept(n, Types.NUMBER_NO_BOOLEAN);
// if n is not an integer, it is truncated.
n = Math.trunc(n);
if (n < 0) {
throw FormulaError.NUM;
}
return bessel.besselj(x, n);
},
BESSELK: (x, n) => {
x = H.accept(x, Types.NUMBER_NO_BOOLEAN);
n = H.accept(n, Types.NUMBER_NO_BOOLEAN);
// if n is not an integer, it is truncated.
n = Math.trunc(n);
if (n < 0) {
throw FormulaError.NUM;
}
return bessel.besselk(x, n);
},
BESSELY: (x, n) => {
x = H.accept(x, Types.NUMBER_NO_BOOLEAN);
n = H.accept(n, Types.NUMBER_NO_BOOLEAN);
// if n is not an integer, it is truncated.
n = Math.trunc(n);
if (n < 0) {
throw FormulaError.NUM;
}
return bessel.bessely(x, n);
},
BIN2DEC: (number) => {
number = H.accept(number, Types.NUMBER_NO_BOOLEAN);
let numberStr = number.toString();
if (numberStr.length > 10) {
throw FormulaError.NUM;
}
if (numberStr.length === 10 && numberStr.substring(0, 1) === '1') {
return parseInt(numberStr.substring(1), 2) + MIN_BIN;
} else {
return parseInt(numberStr, 2);
}
},
BIN2HEX: (number, places) => {
number = H.accept(number, Types.NUMBER_NO_BOOLEAN);
places = H.accept(places, Types.NUMBER_NO_BOOLEAN, null);
const numberStr = number.toString();
if (numberStr.length > 10) {
throw FormulaError.NUM;
}
if (numberStr.length === 10 && numberStr.substring(0, 1) === '1') {
return (parseInt(numberStr.substring(1), 2) + 1099511627264).toString(16).toUpperCase();
}
// convert BIN to HEX
const result = parseInt(number, 2).toString(16);
if (places == null) {
return result.toUpperCase();
} else {
if (places < 0) {
throw FormulaError.NUM;
}
// truncate places in case it is not an integer
places = Math.trunc(places);
if (places >= result.length) {
return (TextFunctions.REPT('0', places - result.length) + result).toUpperCase();
} else {
throw FormulaError.NUM;
}
}
},
BIN2OCT: (number, places) => {
number = H.accept(number, Types.NUMBER_NO_BOOLEAN);
places = H.accept(places, Types.NUMBER, null);
let numberStr = number.toString();
if (numberStr.length > 10) {
throw FormulaError.NUM;
}
if (numberStr.length === 10 && numberStr.substr(0, 1) === "1") {
return (parseInt(numberStr.substr(1), 2) + 1073741312).toString(8);
}
let result = parseInt(number, 2).toString(8);
if (places == null) {
return result.toUpperCase();
} else {
if (places < 0) {
throw FormulaError.NUM;
}
// truncate places in case it is not an integer
places = Math.trunc(places);
if (places >= result.length) {
return (TextFunctions.REPT('0', places - result.length) + result);
} else {
throw FormulaError.NUM;
}
}
},
BITAND: (number1, number2) => {
number1 = H.accept(number1, Types.NUMBER);
number2 = H.accept(number2, Types.NUMBER);
if (number1 < 0 || number2 < 0) {
throw FormulaError.NUM;
}
// check if they are non-integer, if yes, return error
if (Math.floor(number1) !== number1 || Math.floor(number2) !== number2) {
throw FormulaError.NUM;
}
if (number1 > 281474976710655 || number2 > 281474976710655) {
throw FormulaError.NUM;
}
return number1 & number2;
},
BITLSHIFT: (number, shiftAmount) => {
number = H.accept(number, Types.NUMBER);
shiftAmount = H.accept(shiftAmount, Types.NUMBER);
shiftAmount = Math.trunc(shiftAmount);
if (Math.abs(shiftAmount) > 53) {
throw FormulaError.NUM;
}
if (number < 0 || Math.floor(number) !== number || number > 281474976710655) {
throw FormulaError.NUM;
}
const result = (shiftAmount >= 0) ? number * 2 ** shiftAmount : Math.trunc(number / 2 ** -shiftAmount);
if (result > 281474976710655)
throw FormulaError.NUM;
else
return result;
},
BITOR: (number1, number2) => {
number1 = H.accept(number1, Types.NUMBER);
number2 = H.accept(number2, Types.NUMBER);
if (number1 < 0 || number2 < 0) {
throw FormulaError.NUM;
}
// check if they are non-integer, if yes, return error
if (Math.floor(number1) !== number1 || Math.floor(number2) !== number2) {
throw FormulaError.NUM;
}
if (number1 > 281474976710655 || number2 > 281474976710655) {
throw FormulaError.NUM;
}
return number1 | number2;
},
BITRSHIFT: (number, shiftAmount) => {
number = H.accept(number, Types.NUMBER);
shiftAmount = H.accept(shiftAmount, Types.NUMBER);
return EngineeringFunctions.BITLSHIFT(number, -shiftAmount);
},
BITXOR: (number1, number2) => {
number1 = H.accept(number1, Types.NUMBER);
number2 = H.accept(number2, Types.NUMBER);
if (number1 < 0 || number1 > 281474976710655 || Math.floor(number1) !== number1) {
throw FormulaError.NUM;
}
if (number2 < 0 || number2 > 281474976710655 || Math.floor(number2) !== number2) {
throw FormulaError.NUM;
}
// // to check if the number is a non-integer
// if (Math.abs(number1) !== number1 || Math.abs(number2) !== number2) {
// throw FormulaError.NUM;
// }
return number1 ^ number2;
},
COMPLEX: (realNum, iNum, suffix) => {
realNum = H.accept(realNum, Types.NUMBER_NO_BOOLEAN);
iNum = H.accept(iNum, Types.NUMBER_NO_BOOLEAN);
suffix = H.accept(suffix, Types.STRING, "i");
if (suffix !== "i" && suffix !== "j") {
throw FormulaError.VALUE;
}
if (realNum === 0 && iNum === 0) {
return 0;
} else if (realNum === 0) {
if (iNum === 1) {
return suffix;
} else if (iNum === -1) {
return "-" + suffix;
} else {
return iNum.toString() + suffix;
}
} else if (iNum === 0) {
return realNum.toString()
} else {
let sign = (iNum > 0) ? "+" : "";
if (iNum === 1) {
return realNum.toString() + sign + suffix;
} else if (iNum === -1) {
return realNum.toString() + sign + "-" + suffix;
} else {
return realNum.toString() + sign + iNum.toString() + suffix;
}
}
},
DEC2BIN: (number, places) => {
number = H.accept(number, Types.NUMBER);
places = H.accept(places, Types.NUMBER, null);
if (number < MIN_BIN || number > MAX_BIN) {
throw FormulaError.NUM;
}
// if the number is negative, valid place values are ignored and it returns a 10-character binary number.
if (number < 0) {
return "1" + TextFunctions.REPT("0", 9 - (512 + number).toString(2).length) + (512 + number).toString(2);
}
let result = parseInt(number, 10).toString(2);
if (places == null) {
return result;
} else {
// if places is not an integer, it is truncated
places = Math.trunc(places);
if (places <= 0) {
throw FormulaError.NUM;
}
if (places < result.length)
throw FormulaError.NUM;
return TextFunctions.REPT("0", places - result.length) + result;
}
},
DEC2HEX: (number, places) => {
number = H.accept(number, Types.NUMBER);
places = H.accept(places, Types.NUMBER, null);
if (number < -549755813888 || number > 549755813888) {
throw FormulaError.NUM;
}
// if the number is negative, valid place values are ignored and it returns a 10-character binary number.
if (number < 0) {
return (1099511627776 + number).toString(16).toUpperCase();
}
let result = parseInt(number, 10).toString(16);
if (places == null) {
return result.toUpperCase();
} else {
// if places is not an integer, it is truncated
places = Math.trunc(places);
if (places <= 0) {
throw FormulaError.NUM;
}
if (places < result.length)
throw FormulaError.NUM;
return TextFunctions.REPT("0", places - result.length) + result.toUpperCase();
}
},
DEC2OCT: (number, places) => {
number = H.accept(number, Types.NUMBER);
places = H.accept(places, Types.NUMBER, null);
if (number < -536870912 || number > 536870912) {
throw FormulaError.NUM;
}
// if the number is negative, valid place values are ignored and it returns a 10-character binary number.
if (number < 0) {
return (number + 1073741824).toString(8);
}
let result = parseInt(number, 10).toString(8);
if (places == null) {
return result.toUpperCase();
} else {
// if places is not an integer, it is truncated
places = Math.trunc(places);
if (places <= 0) {
throw FormulaError.NUM;
}
if (places < result.length)
throw FormulaError.NUM;
return TextFunctions.REPT("0", places - result.length) + result;
}
},
DELTA: (number1, number2) => {
number1 = H.accept(number1, Types.NUMBER_NO_BOOLEAN);
number2 = H.accept(number2, Types.NUMBER_NO_BOOLEAN, 0);
return number1 === number2 ? 1 : 0;
},
ERF: (lowerLimit, upperLimit) => {
lowerLimit = H.accept(lowerLimit, Types.NUMBER_NO_BOOLEAN);
upperLimit = H.accept(upperLimit, Types.NUMBER_NO_BOOLEAN, 0);
return jStat.erf(lowerLimit);
},
ERFC: (x) => {
x = H.accept(x, Types.NUMBER_NO_BOOLEAN);
return jStat.erfc(x);
},
GESTEP: (number, step) => {
number = H.accept(number, Types.NUMBER_NO_BOOLEAN);
step = H.accept(step, Types.NUMBER_NO_BOOLEAN, 0);
return number >= step ? 1 : 0;
},
HEX2BIN: (number, places) => {
number = H.accept(number, Types.STRING);
places = H.accept(places, Types.NUMBER, null);
if (number.length > 10 || !/^[0-9a-fA-F]*$/.test(number)) {
throw FormulaError.NUM;
}
// to check if the number is negative
let ifNegative = (number.length === 10 && number.substr(0, 1).toLowerCase() === "f");
// convert HEX to DEC
let toDecimal = ifNegative ? parseInt(number, 16) - 1099511627776 : parseInt(number, 16);
// if number is lower than -512 or grater than 511, return error
if (toDecimal < MIN_BIN || toDecimal > MAX_BIN) {
throw FormulaError.NUM;
}
// if the number is negative, valid place values are ignored and it returns a 10-character binary number.
if (ifNegative) {
return "1" + TextFunctions.REPT('0', 9 - (toDecimal + 512).toString(2).length) + (toDecimal + 512).toString(2)
}
// convert decimal to binary
let toBinary = toDecimal.toString(2);
if (places == null) {
return toBinary;
} else {
// if places is not an integer, it is truncated
places = Math.trunc(places);
if (places <= 0 || places < toBinary.length) {
throw FormulaError.NUM;
}
return TextFunctions.REPT("0", places - toBinary.length) + toBinary;
}
},
HEX2DEC: (number) => {
number = H.accept(number, Types.STRING);
if (number.length > 10 || !/^[0-9a-fA-F]*$/.test(number)) {
throw FormulaError.NUM;
}
let result = parseInt(number, 16);
//david: validate
// If the places is larger than 10, or number is larger than, return #NUM!
// If number is not a valid Hex number, returns the #NUM! error value.
return (result >= 549755813888) ? result - 1099511627776 : result;
},
HEX2OCT: (number, places) => {
number = H.accept(number, Types.STRING);
if (number.length > 10 || !/^[0-9a-fA-F]*$/.test(number)) {
throw FormulaError.NUM;
}
// convert HEX to DEC
let toDecimal = EngineeringFunctions.HEX2DEC(number);
if (toDecimal > MAX_OCT || toDecimal < MIN_OCT) {
throw FormulaError.NUM;
}
return EngineeringFunctions.DEC2OCT(toDecimal, places);
},
IMABS: (iNumber) => {
const {real, im} = parseIM(iNumber);
return Math.sqrt(Math.pow(real, 2) + Math.pow(im, 2));
},
IMAGINARY: (iNumber) => {
return parseIM(iNumber).im;
},
IMARGUMENT: (iNumber) => {
const {real, im} = parseIM(iNumber);
// x + yi => x cannot be 0, since theta = tan-1(y / x)
if (real === 0 && im === 0) {
throw FormulaError.DIV0;
}
// return PI/2 if x is equal to 0 and y is positive
if (real === 0 && im > 0) {
return Math.PI / 2;
}
// while return -PI/2 if x is equal to 0 and y is negative
if (real === 0 && im < 0) {
return -Math.PI / 2;
}
// return -PI if x is negative and y is equal to 0
if (real < 0 && im === 0) {
return Math.PI
}
// return 0 if x is positive and y is equal to 0
if (real > 0 && im === 0) {
return 0;
}
// return argument of iNumber
if (real > 0) {
return Math.atan(im / real);
} else if (real < 0 && im > 0) {
return Math.atan(im / real) + Math.PI;
} else {
return Math.atan(im / real) - Math.PI;
}
},
IMCONJUGATE: (iNumber) => {
const {real, im, unit} = parseIM(iNumber);
return (im !== 0) ? EngineeringFunctions.COMPLEX(real, -im, unit) : '' + real;
},
IMCOS: (iNumber) => {
const {real, im, unit} = parseIM(iNumber);
let realInput = Math.cos(real) * (Math.exp(im) + Math.exp(-im)) / 2;
let imaginaryInput = -Math.sin(real) * (Math.exp(im) - Math.exp(-im)) / 2;
return EngineeringFunctions.COMPLEX(realInput, imaginaryInput, unit);
},
IMCOSH: (iNumber) => {
const {real, im, unit} = parseIM(iNumber);
let realInput = Math.cos(im) * (Math.exp(real) + Math.exp(-real)) / 2;
let imaginaryInput = -Math.sin(im) * (Math.exp(real) - Math.exp(-real)) / 2;
return EngineeringFunctions.COMPLEX(realInput, -imaginaryInput, unit);
},
IMCOT: (iNumber) => {
iNumber = H.accept(iNumber);
let real = EngineeringFunctions.IMCOS(iNumber);
let imaginary = EngineeringFunctions.IMSIN(iNumber);
return EngineeringFunctions.IMDIV(real, imaginary);
},
IMCSC: (iNumber) => {
iNumber = H.accept(iNumber);
return EngineeringFunctions.IMDIV('1', EngineeringFunctions.IMSIN(iNumber));
},
IMCSCH: (iNumber) => {
iNumber = H.accept(iNumber);
return EngineeringFunctions.IMDIV('1', EngineeringFunctions.IMSINH(iNumber));
},
IMDIV: (iNumber1, iNumber2) => {
const res1 = parseIM(iNumber1);
const a = res1.real, b = res1.im, unit1 = res1.unit;
const res2 = parseIM(iNumber2);
const c = res2.real, d = res2.im, unit2 = res2.unit;
if (c === 0 && d === 0 || unit1 !== unit2) {
throw FormulaError.NUM;
}
let unit = unit1;
let denominator = Math.pow(c, 2) + Math.pow(d, 2);
return EngineeringFunctions.COMPLEX((a * c + b * d) / denominator, (b * c - a * d) / denominator, unit);
},
IMEXP: (iNumber) => {
const {real, im, unit} = parseIM(iNumber);
// return exponential of complex number
let e = Math.exp(real);
return EngineeringFunctions.COMPLEX(e * Math.cos(im), e * Math.sin(im), unit)
},
IMLN: (iNumber) => {
const {real, im, unit} = parseIM(iNumber);
return EngineeringFunctions.COMPLEX(Math.log(Math.sqrt(Math.pow(real, 2) + Math.pow(im, 2))),
Math.atan(im / real), unit);
},
IMLOG10: (iNumber) => {
const {real, im, unit} = parseIM(iNumber);
let realInput = Math.log(Math.sqrt(Math.pow(real, 2) + Math.pow(im, 2))) / Math.log(10);
let imaginaryInput = Math.atan(im / real) / Math.log(10);
return EngineeringFunctions.COMPLEX(realInput, imaginaryInput, unit);
},
IMLOG2: (iNumber) => {
const {real, im, unit} = parseIM(iNumber);
let realInput = Math.log(Math.sqrt(Math.pow(real, 2) + Math.pow(im, 2))) / Math.log(2);
let imaginaryInput = Math.atan(im / real) / Math.log(2);
return EngineeringFunctions.COMPLEX(realInput, imaginaryInput, unit);
},
IMPOWER: (iNumber, number) => {
let {unit} = parseIM(iNumber);
number = H.accept(number, Types.NUMBER_NO_BOOLEAN);
// calculate power of modules
let p = Math.pow(EngineeringFunctions.IMABS(iNumber), number);
// calculate argument
let t = EngineeringFunctions.IMARGUMENT(iNumber);
let real = p * Math.cos(number * t);
let imaginary = p * Math.sin(number * t);
return EngineeringFunctions.COMPLEX(real, imaginary, unit);
},
IMPRODUCT: (...params) => {
let result;
let i = 0;
H.flattenParams(params, null, false, (item) => {
if (i === 0) {
result = H.accept(item);
parseIM(result);
} else {
const res1 = parseIM(result);
const a = res1.real, b = res1.im, unit1 = res1.unit;
const res2 = parseIM(item);
const c = res2.real, d = res2.im, unit2 = res2.unit;
if (unit1 !== unit2)
throw FormulaError.VALUE;
result = EngineeringFunctions.COMPLEX(a * c - b * d, a * d + b * c);
}
i++;
}, 1);
return result;
},
IMREAL: (iNumber) => {
return parseIM(iNumber).real;
},
IMSEC: (iNumber) => {
return EngineeringFunctions.IMDIV('1', EngineeringFunctions.IMCOS(iNumber));
},
IMSECH: (iNumber) => {
return EngineeringFunctions.IMDIV('1', EngineeringFunctions.IMCOSH(iNumber));
},
IMSIN: (iNumber) => {
const {real, im, unit} = parseIM(iNumber);
let realInput = Math.sin(real) * (Math.exp(im) + Math.exp(-im)) / 2;
let imaginaryInput = Math.cos(real) * (Math.exp(im) - Math.exp(-im)) / 2;
return EngineeringFunctions.COMPLEX(realInput, imaginaryInput, unit);
},
IMSINH: (iNumber) => {
const {real, im, unit} = parseIM(iNumber);
let realInput = Math.cos(im) * (Math.exp(real) - Math.exp(-real)) / 2;
let imaginaryInput = Math.sin(im) * (Math.exp(real) + Math.exp(-real)) / 2;
return EngineeringFunctions.COMPLEX(realInput, imaginaryInput, unit);
},
IMSQRT: (iNumber) => {
const {unit} = parseIM(iNumber);
// calculate the power of modulus
let power = Math.sqrt(EngineeringFunctions.IMABS(iNumber));
// calculate argument
let argument = EngineeringFunctions.IMARGUMENT(iNumber);
return EngineeringFunctions.COMPLEX(power * Math.cos(argument / 2), power * Math.sin(argument / 2), unit);
},
IMSUB: (iNumber1, iNumber2) => {
const res1 = parseIM(iNumber1);
const a = res1.real, b = res1.im, unit1 = res1.unit;
const res2 = parseIM(iNumber2);
const c = res2.real, d = res2.im, unit2 = res2.unit;
if (unit1 !== unit2) {
throw FormulaError.VALUE;
}
return EngineeringFunctions.COMPLEX(a - c, b - d, unit1);
},
IMSUM: (...params) => {
let realSum = 0, imSum = 0, prevUnit;
H.flattenParams(params, null, false, (item) => {
const {real, im, unit} = parseIM(item);
if (!prevUnit) prevUnit = unit;
if (prevUnit !== unit)
throw FormulaError.VALUE;
realSum += real;
imSum += im;
});
return EngineeringFunctions.COMPLEX(realSum, imSum, prevUnit);
},
IMTAN: (iNumber) => {
const {unit} = parseIM(iNumber);
return EngineeringFunctions.IMDIV(EngineeringFunctions.IMSIN(iNumber), EngineeringFunctions.IMCOS(iNumber), unit);
},
// FIXME: need to check the test cases
OCT2BIN: (number, places) => {
// office: If number is not a valid octal number, OCT2BIN returns the #NUM! error value.
// office: If places is nonnumeric, OCT2BIN returns the #VALUE! error value.
number = H.accept(number, Types.STRING);
places = H.accept(places, Types.NUMBER, null);
// 1. If number's length larger than 10, returns #NUM!
if (number.length > 10) {
throw FormulaError.NUM
}
// In microsoft Excel, if places is larger than 10, it will return #NUM!
if (places > 10) {
throw FormulaError.NUM;
}
// 2. office: If places is negative, OCT2BIN returns the #NUM! error value.
if (places !== null && places < 0) {
throw FormulaError.NUM;
}
// if places is not an integer, it is truncated
// office: If places is not an integer, it is truncated.
places = Math.trunc(places);
// to check if the Oct number is negative
let isNegative = (number.length === 10 && number.substring(0, 1) === '7');
// convert OCT to DEC
let toDecimal = EngineeringFunctions.OCT2DEC(number);
// 2.
// office: If number is negative, it cannot be less than 7777777000, and if number is positive, it cannot be greater than 777.
// MiN_BIN = -512, MAX_BIN = 511
if (toDecimal < MIN_BIN || toDecimal > MAX_BIN) {
return FormulaError.NUM;
}
// if number is negative, ignores places and return a 10-character binary number
// office: If number is negative, OCT2BIN ignores places and returns a 10-character binary number.
if (isNegative) {
return '1' + TextFunctions.REPT('0', 9 - (512 + toDecimal).toString(2).length) + (512 + toDecimal).toString(2);
}
// convert DEC to BIN
let result = toDecimal.toString(2);
//if (places === null) {
if (places === 0) {
return result;
}
// office: If OCT2BIN requires more than places characters, it returns the #NUM! error value.
if (places < result.length) {
throw FormulaError.NUM;
}
return TextFunctions.REPT('0', places - result.length) + result;
},
OCT2DEC: (number) => {
number = H.accept(number, Types.STRING);
// In microsoft Excel, if number contains more than ten characters (10 digits), it will return #NUM!
if (number.length > 10) {
throw FormulaError.NUM;
}
// If number is not a valid octal number, OCT2DEC returns the #NUM! error value.
for (const n of number) {
if (n < '0' || n > '7') {
throw FormulaError.NUM;
}
}
// convert to DEC
let result = parseInt(number, 8);
return (result >= 536870912) ? result - 1073741824 : result;
// 536870912(4000000000) : -536870912; 1073741823(7777777777) : -1
},
OCT2HEX: (number, places) => {
number = H.accept(number, Types.STRING);
places = H.accept(places, Types.NUMBER_NO_BOOLEAN, null);
if (number.length > 10) {
throw FormulaError.NUM
}
// office: If number is not a valid octal number, OCT2DEC returns the #NUM! error value.
for (const n of number) {
if (n < '0' || n > '7') {
throw FormulaError.NUM;
}
}
// if places is not an integer, it is truncated
places = Math.trunc(places);
// office: If places is negative, OCT2HEX returns the #NUM! error value.
if (places < 0 || places > 10) {
throw FormulaError.NUM;
}
// convert OCT to DEC
let toDecimal = EngineeringFunctions.OCT2DEC(number);
// convert DEC to HEX
// let toHex = EngineeringFunctions.DEC2HEX(toDecimal, places);
let toHex = EngineeringFunctions.DEC2HEX(toDecimal);
//if (places === null) {
if (places === 0) {
return toHex;
}
if (places < toHex.length) {
throw FormulaError.NUM;
} else {
return TextFunctions.REPT('0', places - toHex.length) + toHex;
}
},
};
module.exports = EngineeringFunctions;