diff --git a/package-lock.json b/package-lock.json index 64e7baa0..a951fc89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1053,6 +1053,11 @@ "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", "dev": true }, + "bignumber.js": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", + "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==" + }, "binary-extensions": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", diff --git a/package.json b/package.json index 3c80af60..8a686708 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ }, "dependencies": { "babel-polyfill": "^6.26.0", + "bignumber.js": "^5.0.0", "bootstrap": "^3.3.7", "bootstrap-colorpicker": "^2.5.2", "bootstrap-switch": "^3.3.4", diff --git a/src/core/Dish.js b/src/core/Dish.js index 001f78e1..8ab00eac 100755 --- a/src/core/Dish.js +++ b/src/core/Dish.js @@ -1,14 +1,16 @@ import Utils from "./Utils.js"; +import BigNumber from "bignumber.js"; /** * The data being operated on by each operation. * * @author n1474335 [n1474335@gmail.com] + * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2016 * @license Apache-2.0 * * @class - * @param {byteArray|string|number|ArrayBuffer} value - The value of the input data. + * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data. * @param {number} type - The data type of value, see Dish enums. */ const Dish = function(value, type) { @@ -47,6 +49,12 @@ Dish.HTML = 3; * @enum */ Dish.ARRAY_BUFFER = 4; +/** + * Dish data type enum for BigNumbers. + * @readonly + * @enum + */ +Dish.BIG_NUMBER = 5; /** @@ -57,22 +65,22 @@ Dish.ARRAY_BUFFER = 4; * @returns {number} The data type enum value. */ Dish.typeEnum = function(typeStr) { - switch (typeStr) { - case "byteArray": - case "Byte array": + switch (typeStr.toLowerCase()) { + case "bytearray": + case "byte array": return Dish.BYTE_ARRAY; case "string": - case "String": return Dish.STRING; case "number": - case "Number": return Dish.NUMBER; case "html": - case "HTML": return Dish.HTML; - case "arrayBuffer": - case "ArrayBuffer": + case "arraybuffer": + case "array buffer": return Dish.ARRAY_BUFFER; + case "bignumber": + case "big number": + return Dish.BIG_NUMBER; default: throw "Invalid data type string. No matching enum."; } @@ -83,8 +91,8 @@ Dish.typeEnum = function(typeStr) { * Returns the data type string for the given type enum. * * @static - * @param {string} typeEnum - The enum value of the data type. - * @returns {number} The data type as a string. + * @param {number} typeEnum - The enum value of the data type. + * @returns {string} The data type as a string. */ Dish.enumLookup = function(typeEnum) { switch (typeEnum) { @@ -98,6 +106,8 @@ Dish.enumLookup = function(typeEnum) { return "html"; case Dish.ARRAY_BUFFER: return "ArrayBuffer"; + case Dish.BIG_NUMBER: + return "BigNumber"; default: throw "Invalid data type enum. No matching type."; } @@ -107,7 +117,7 @@ Dish.enumLookup = function(typeEnum) { /** * Sets the data value and type and then validates them. * - * @param {byteArray|string|number|ArrayBuffer} value - The value of the input data. + * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data. * @param {number} type - The data type of value, see Dish enums. */ Dish.prototype.set = function(value, type) { @@ -126,7 +136,7 @@ Dish.prototype.set = function(value, type) { * Returns the value of the data in the type format specified. * * @param {number} type - The data type of value, see Dish enums. - * @returns {byteArray|string|number|ArrayBuffer} The value of the output data. + * @returns {byteArray|string|number|ArrayBuffer|BigNumber} The value of the output data. */ Dish.prototype.get = function(type) { if (this.type !== type) { @@ -159,6 +169,9 @@ Dish.prototype.translate = function(toType) { // Array.from() would be nicer here, but it's slightly slower this.value = Array.prototype.slice.call(new Uint8Array(this.value)); break; + case Dish.BIG_NUMBER: + this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toString()) : []; + break; default: break; } @@ -180,6 +193,14 @@ Dish.prototype.translate = function(toType) { this.value = new Uint8Array(this.value).buffer; this.type = Dish.ARRAY_BUFFER; break; + case Dish.BIG_NUMBER: + try { + this.value = new BigNumber(Utils.byteArrayToUtf8(this.value)); + } catch (err) { + this.value = new BigNumber(NaN); + } + this.type = Dish.BIG_NUMBER; + break; default: break; } @@ -215,6 +236,8 @@ Dish.prototype.valid = function() { return typeof this.value === "number"; case Dish.ARRAY_BUFFER: return this.value instanceof ArrayBuffer; + case Dish.BIG_NUMBER: + return this.value instanceof BigNumber; default: return false; } @@ -235,6 +258,7 @@ Dish.prototype.size = function() { case Dish.HTML: return this.value.length; case Dish.NUMBER: + case Dish.BIG_NUMBER: return this.value.toString().length; case Dish.ARRAY_BUFFER: return this.value.byteLength; diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 835fa2d8..8b3b61ff 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -524,7 +524,7 @@ const OperationConfig = { module: "Default", description: "Adds together a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 18.5", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Delimiter", @@ -537,7 +537,7 @@ const OperationConfig = { module: "Default", description: "Subtracts a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 1.5", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Delimiter", @@ -550,7 +550,7 @@ const OperationConfig = { module: "Default", description: "Multiplies a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 40", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Delimiter", @@ -563,7 +563,7 @@ const OperationConfig = { module: "Default", description: "Divides a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 2.5", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Delimiter", @@ -576,7 +576,7 @@ const OperationConfig = { module: "Default", description: "Computes the mean (average) of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 .5 becomes 4.75", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Delimiter", @@ -589,7 +589,7 @@ const OperationConfig = { module: "Default", description: "Computes the median of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 1 .5 becomes 4.5", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Delimiter", @@ -602,7 +602,7 @@ const OperationConfig = { module: "Default", description: "Computes the standard deviation of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 4.089281382128433", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Delimiter", @@ -806,7 +806,7 @@ const OperationConfig = { module: "Default", description: "Converts a number to decimal from a given numerical base.", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Radix", @@ -818,7 +818,7 @@ const OperationConfig = { "To Base": { module: "Default", description: "Converts a decimal number to a given numerical base.", - inputType: "number", + inputType: "BigNumber", outputType: "string", args: [ { @@ -2515,8 +2515,8 @@ const OperationConfig = { "Convert distance": { module: "Default", description: "Converts a unit of distance to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -2533,8 +2533,8 @@ const OperationConfig = { "Convert area": { module: "Default", description: "Converts a unit of area to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -2551,8 +2551,8 @@ const OperationConfig = { "Convert mass": { module: "Default", description: "Converts a unit of mass to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -2569,8 +2569,8 @@ const OperationConfig = { "Convert speed": { module: "Default", description: "Converts a unit of speed to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -2587,8 +2587,8 @@ const OperationConfig = { "Convert data units": { module: "Default", description: "Converts a unit of data to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -3750,7 +3750,7 @@ const OperationConfig = { module: "Default", description: "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign.", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Scheme", @@ -3778,7 +3778,7 @@ const OperationConfig = { "To BCD": { module: "Default", description: "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign", - inputType: "number", + inputType: "BigNumber", outputType: "string", args: [ { diff --git a/src/core/operations/Arithmetic.js b/src/core/operations/Arithmetic.js index 1fe73ac1..070cea80 100644 --- a/src/core/operations/Arithmetic.js +++ b/src/core/operations/Arithmetic.js @@ -1,4 +1,5 @@ import Utils from "../Utils.js"; +import BigNumber from "bignumber.js"; /** @@ -24,11 +25,11 @@ const Arithmetic = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runSum: function(input, args) { const val = Arithmetic._sum(Arithmetic._createNumArray(input, args[0])); - return typeof(val) === "number" ? val : NaN; + return val instanceof BigNumber ? val : new BigNumber(NaN); }, @@ -37,11 +38,11 @@ const Arithmetic = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runSub: function(input, args) { let val = Arithmetic._sub(Arithmetic._createNumArray(input, args[0])); - return typeof(val) === "number" ? val : NaN; + return val instanceof BigNumber ? val : new BigNumber(NaN); }, @@ -50,11 +51,11 @@ const Arithmetic = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runMulti: function(input, args) { let val = Arithmetic._multi(Arithmetic._createNumArray(input, args[0])); - return typeof(val) === "number" ? val : NaN; + return val instanceof BigNumber ? val : new BigNumber(NaN); }, @@ -63,11 +64,11 @@ const Arithmetic = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runDiv: function(input, args) { let val = Arithmetic._div(Arithmetic._createNumArray(input, args[0])); - return typeof(val) === "number" ? val : NaN; + return val instanceof BigNumber ? val : new BigNumber(NaN); }, @@ -76,11 +77,11 @@ const Arithmetic = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runMean: function(input, args) { let val = Arithmetic._mean(Arithmetic._createNumArray(input, args[0])); - return typeof(val) === "number" ? val : NaN; + return val instanceof BigNumber ? val : new BigNumber(NaN); }, @@ -89,11 +90,11 @@ const Arithmetic = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runMedian: function(input, args) { let val = Arithmetic._median(Arithmetic._createNumArray(input, args[0])); - return typeof(val) === "number" ? val : NaN; + return val instanceof BigNumber ? val : new BigNumber(NaN); }, @@ -102,11 +103,11 @@ const Arithmetic = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runStdDev: function(input, args) { let val = Arithmetic._stdDev(Arithmetic._createNumArray(input, args[0])); - return typeof(val) === "number" ? val : NaN; + return val instanceof BigNumber ? val : new BigNumber(NaN); }, @@ -116,7 +117,7 @@ const Arithmetic = { * @private * @param {string[]} input * @param {string} delim - * @returns {number[]} + * @returns {BigNumber[]} */ _createNumArray: function(input, delim) { delim = Utils.charRep[delim || "Space"]; @@ -125,13 +126,13 @@ const Arithmetic = { num; for (let i = 0; i < splitNumbers.length; i++) { - if (splitNumbers[i].indexOf(".") >= 0) { - num = parseFloat(splitNumbers[i].trim()); - } else { - num = parseInt(splitNumbers[i].trim(), 0); - } - if (!isNaN(num)) { - numbers.push(num); + try { + num = BigNumber(splitNumbers[i].trim()); + if (!num.isNaN()) { + numbers.push(num); + } + } catch (err) { + // This line is not a valid number } } return numbers; @@ -142,12 +143,12 @@ const Arithmetic = { * Adds an array of numbers and returns the value. * * @private - * @param {number[]} data - * @returns {number} + * @param {BigNumber[]} data + * @returns {BigNumber} */ _sum: function(data) { if (data.length > 0) { - return data.reduce((acc, curr) => acc + curr); + return data.reduce((acc, curr) => acc.plus(curr)); } }, @@ -156,12 +157,12 @@ const Arithmetic = { * Subtracts an array of numbers and returns the value. * * @private - * @param {number[]} data - * @returns {number} + * @param {BigNumber[]} data + * @returns {BigNumber} */ _sub: function(data) { if (data.length > 0) { - return data.reduce((acc, curr) => acc - curr); + return data.reduce((acc, curr) => acc.minus(curr)); } }, @@ -170,12 +171,12 @@ const Arithmetic = { * Multiplies an array of numbers and returns the value. * * @private - * @param {number[]} data - * @returns {number} + * @param {BigNumber[]} data + * @returns {BigNumber} */ _multi: function(data) { if (data.length > 0) { - return data.reduce((acc, curr) => acc * curr); + return data.reduce((acc, curr) => acc.times(curr)); } }, @@ -184,12 +185,12 @@ const Arithmetic = { * Divides an array of numbers and returns the value. * * @private - * @param {number[]} data - * @returns {number} + * @param {BigNumber[]} data + * @returns {BigNumber} */ _div: function(data) { if (data.length > 0) { - return data.reduce((acc, curr) => acc / curr); + return data.reduce((acc, curr) => acc.div(curr)); } }, @@ -198,12 +199,12 @@ const Arithmetic = { * Computes mean of a number array and returns the value. * * @private - * @param {number[]} data - * @returns {number} + * @param {BigNumber[]} data + * @returns {BigNumber} */ _mean: function(data) { if (data.length > 0) { - return Arithmetic._sum(data) / data.length; + return Arithmetic._sum(data).div(data.length); } }, @@ -212,14 +213,14 @@ const Arithmetic = { * Computes median of a number array and returns the value. * * @private - * @param {number[]} data - * @returns {number} + * @param {BigNumber[]} data + * @returns {BigNumber} */ _median: function (data) { - if ((data.length % 2) === 0) { + if ((data.length % 2) === 0 && data.length > 0) { let first, second; data.sort(function(a, b){ - return a - b; + return a.minus(b); }); first = data[Math.floor(data.length / 2)]; second = data[Math.floor(data.length / 2) - 1]; @@ -234,17 +235,17 @@ const Arithmetic = { * Computes standard deviation of a number array and returns the value. * * @private - * @param {number[]} data - * @returns {number} + * @param {BigNumber[]} data + * @returns {BigNumber} */ _stdDev: function (data) { if (data.length > 0) { let avg = Arithmetic._mean(data); - let devSum = 0; + let devSum = new BigNumber(0); for (let i = 0; i < data.length; i++) { - devSum += (data[i] - avg) ** 2; + devSum = devSum.plus(data[i].minus(avg).pow(2)); } - return Math.sqrt(devSum / data.length); + return devSum.div(data.length).sqrt(); } }, }; diff --git a/src/core/operations/BCD.js b/src/core/operations/BCD.js index d3efbc71..7d29f4e5 100755 --- a/src/core/operations/BCD.js +++ b/src/core/operations/BCD.js @@ -1,4 +1,5 @@ import Utils from "../Utils.js"; +import BigNumber from "bignumber.js"; /** @@ -61,14 +62,14 @@ const BCD = { /** * To BCD operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args * @returns {string} */ runToBCD: function(input, args) { - if (isNaN(input)) + if (input.isNaN()) return "Invalid input"; - if (Math.floor(input) !== input) + if (!input.floor().equals(input)) return "Fractional values are not supported by BCD"; const encoding = BCD.ENCODING_LOOKUP[args[0]], @@ -77,7 +78,7 @@ const BCD = { outputFormat = args[3]; // Split input number up into separate digits - const digits = input.toString().split(""); + const digits = input.toFixed().split(""); if (digits[0] === "-" || digits[0] === "+") { digits.shift(); @@ -152,7 +153,7 @@ const BCD = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runFromBCD: function(input, args) { const encoding = BCD.ENCODING_LOOKUP[args[0]], @@ -206,7 +207,7 @@ const BCD = { output += val.toString(); }); - return parseInt(output, 10); + return new BigNumber(output); }, }; diff --git a/src/core/operations/Base.js b/src/core/operations/Base.js index 8a79bf3b..06bbeb7c 100755 --- a/src/core/operations/Base.js +++ b/src/core/operations/Base.js @@ -1,3 +1,5 @@ +import BigNumber from "bignumber.js"; + /** * Numerical base operations. * @@ -18,7 +20,7 @@ const Base = { /** * To Base operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args * @returns {string} */ @@ -39,7 +41,7 @@ const Base = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runFrom: function(input, args) { const radix = args[0] || Base.DEFAULT_RADIX; @@ -48,14 +50,14 @@ const Base = { } let number = input.replace(/\s/g, "").split("."), - result = parseInt(number[0], radix) || 0; + result = new BigNumber(number[0], radix) || 0; if (number.length === 1) return result; // Fractional part for (let i = 0; i < number[1].length; i++) { - const digit = parseInt(number[1][i], radix); - result += digit / Math.pow(radix, i+1); + const digit = new BigNumber(number[1][i], radix); + result += digit.div(Math.pow(radix, i+1)); } return result; diff --git a/src/core/operations/Convert.js b/src/core/operations/Convert.js index 2c95253d..d47166ce 100755 --- a/src/core/operations/Convert.js +++ b/src/core/operations/Convert.js @@ -60,17 +60,16 @@ const Convert = { /** * Convert distance operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runDistance: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.DISTANCE_FACTOR[inputUnits]; - return input / Convert.DISTANCE_FACTOR[outputUnits]; - // TODO Remove rounding errors (e.g. 1.000000000001) + input = input.mul(Convert.DISTANCE_FACTOR[inputUnits]); + return input.div(Convert.DISTANCE_FACTOR[outputUnits]); }, @@ -141,16 +140,16 @@ const Convert = { /** * Convert data units operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runDataSize: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.DATA_FACTOR[inputUnits]; - return input / Convert.DATA_FACTOR[outputUnits]; + input = input.mul(Convert.DATA_FACTOR[inputUnits]); + return input.div(Convert.DATA_FACTOR[outputUnits]); }, @@ -221,16 +220,16 @@ const Convert = { /** * Convert area operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runArea: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.AREA_FACTOR[inputUnits]; - return input / Convert.AREA_FACTOR[outputUnits]; + input = input.mul(Convert.AREA_FACTOR[inputUnits]); + return input.div(Convert.AREA_FACTOR[outputUnits]); }, @@ -332,16 +331,16 @@ const Convert = { /** * Convert mass operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runMass: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.MASS_FACTOR[inputUnits]; - return input / Convert.MASS_FACTOR[outputUnits]; + input = input.mul(Convert.MASS_FACTOR[inputUnits]); + return input.div(Convert.MASS_FACTOR[outputUnits]); }, @@ -397,16 +396,16 @@ const Convert = { /** * Convert speed operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runSpeed: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.SPEED_FACTOR[inputUnits]; - return input / Convert.SPEED_FACTOR[outputUnits]; + input = input.mul(Convert.SPEED_FACTOR[inputUnits]); + return input.div(Convert.SPEED_FACTOR[outputUnits]); }, }; diff --git a/test/tests/operations/FlowControl.js b/test/tests/operations/FlowControl.js index 04ed93eb..51b21f34 100644 --- a/test/tests/operations/FlowControl.js +++ b/test/tests/operations/FlowControl.js @@ -37,7 +37,7 @@ TestRegister.addTests([ }, { name: "Fork, (expect) Error, Merge", - input: "1\n2\na\n4", + input: "1.1\n2.5\na\n3.4", expectedError: true, recipeConfig: [ { @@ -45,8 +45,8 @@ TestRegister.addTests([ args: ["\n", "\n", false], }, { - op: "To Base", - args: [16], + op: "Object Identifier to Hex", + args: [], }, { op: "Merge",