From 693cec4c6c276e20e0ed762de4248949208ea807 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 22 Oct 2020 20:12:25 +0200 Subject: [PATCH 01/30] stats --- src/interpreter/plugin/ErrorFunctionPlugin.ts | 70 -------- src/interpreter/plugin/StatisticalPlugin.ts | 159 ++++++++++++++++++ src/interpreter/plugin/index.ts | 2 +- 3 files changed, 160 insertions(+), 71 deletions(-) delete mode 100644 src/interpreter/plugin/ErrorFunctionPlugin.ts create mode 100644 src/interpreter/plugin/StatisticalPlugin.ts diff --git a/src/interpreter/plugin/ErrorFunctionPlugin.ts b/src/interpreter/plugin/ErrorFunctionPlugin.ts deleted file mode 100644 index bba1b9528c..0000000000 --- a/src/interpreter/plugin/ErrorFunctionPlugin.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @license - * Copyright (c) 2020 Handsoncode. All rights reserved. - */ - -import {InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {ProcedureAst} from '../../parser' -import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' - -export class ErrorFunctionPlugin extends FunctionPlugin { - public static implementedFunctions = { - 'ERF': { - method: 'erf', - parameters: [ - {argumentType: ArgumentTypes.NUMBER}, - {argumentType: ArgumentTypes.NUMBER, optionalArg: true}, - ] - }, - 'ERFC': { - method: 'erfc', - parameters: [ - {argumentType: ArgumentTypes.NUMBER} - ] - }, - } - - public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.metadata('ERF'), (lowerBound, upperBound) => { - if (upperBound === undefined) { - return erf(lowerBound) - } else { - return erf(upperBound) - erf(lowerBound) - } - }) - } - - public erfc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.metadata('ERFC'), erfc) - } -} - -function erf(x: number): number { - if (x >= 0) { - return erfApprox(x) - } else { - return -erfApprox(-x) - } -} - -function erfApprox(x: number): number { - const polyExponent = 16 - const coefficients = [ - 0.0705230784, - 0.0422820123, - 0.0092705272, - 0.0001520143, - 0.0002765672, - 0.0000430638, - ] - - const poly = coefficients.reduce((acc: number, coefficient: number, index: number) => { - return acc + coefficient * Math.pow(x, index + 1) - }, 1) - - return 1 - (1 / Math.pow(poly, polyExponent)) -} - -function erfc(x: number): number { - return 1 - erf(x) -} diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts new file mode 100644 index 0000000000..f427398734 --- /dev/null +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -0,0 +1,159 @@ +/** + * @license + * Copyright (c) 2020 Handsoncode. All rights reserved. + */ + +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' +import {normal, erf, erfc, exponential, gamma, gammafn, gammaln} from './3rdparty/jstat' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' + +export class StatisticalPlugin extends FunctionPlugin { + public static implementedFunctions = { + 'ERF': { + method: 'erf', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, optionalArg: true}, + ] + }, + 'ERFC': { + method: 'erfc', + parameters: [ + {argumentType: ArgumentTypes.NUMBER} + ] + }, + 'EXPON.DIST': { + method: 'expondist', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.BOOLEAN}, + ] + }, + 'FISHER': { + method: 'fisher', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, greaterThan: -1, lessThan: 1} + ] + }, + 'FISHERINV': { + method: 'fisherinv', + parameters: [ + {argumentType: ArgumentTypes.NUMBER} + ] + }, + 'GAMMA': { + method: 'gamma', + parameters: [ + {argumentType: ArgumentTypes.NUMBER} + ] + }, + 'GAMMA.DIST': { + method: 'gammadist', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.BOOLEAN}, + ] + }, + 'GAMMALN': { + method: 'gammaln', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0} + ] + }, + 'GAMMALN.PRECISE': { + method: 'gammaln', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0} + ] + }, + 'GAMMA.INV': { + method: 'gammainv', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + ] + }, + 'GAUSS': { + method: 'gauss', + parameters: [ + {argumentType: ArgumentTypes.NUMBER} + ] + }, + } + + public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('ERF'), (lowerBound, upperBound) => { + if (upperBound === undefined) { + return erf(lowerBound) + } else { + return erf(upperBound) - erf(lowerBound) + } + }) + } + + public erfc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('ERFC'), erfc) + } + + + public expondist(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('EXPON.DIST'), + (x: number, lambda: number, cumulative: boolean) => { + if(cumulative) { + return exponential.cdf(x, lambda) + } else { + return exponential.pdf(x, lambda) + } + } + ) + } + + public fisher(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('FISHER'), + (x: number) => Math.log((1 + x) / (1 - x)) / 2 + ) + } + + public fisherinv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('FISHERINV'), + (y: number) => (Math.exp(2 * y) - 1) / (Math.exp(2 * y) + 1) + ) + } + + public gamma(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('GAMMA'), gammafn) + } + + public gammadist(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('GAMMA.DIST'), + (value: number, alpha: number, beta: number, cumulative: boolean) => { + if(cumulative) { + return gamma.cdf(value, alpha, beta) + } else { + return gamma.pdf(value, alpha, beta) + } + } + ) + } + + public gammaln(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('GAMMALN'), gammaln) + } + + public gammainv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('GAMMALN'), gamma.inv) + } + + public gauss(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('GAUSS'), + (z: number) => normal.cdf(z, 0, 1) - 0.5 + ) + } +} + + diff --git a/src/interpreter/plugin/index.ts b/src/interpreter/plugin/index.ts index 4ced7c9378..2bfb2bfc92 100644 --- a/src/interpreter/plugin/index.ts +++ b/src/interpreter/plugin/index.ts @@ -15,7 +15,6 @@ export {CountUniquePlugin} from './CountUniquePlugin' export {DateTimePlugin} from './DateTimePlugin' export {DegreesPlugin} from './DegreesPlugin' export {DeltaPlugin} from './DeltaPlugin' -export {ErrorFunctionPlugin} from './ErrorFunctionPlugin' export {ExpPlugin} from './ExpPlugin' export {FinancialPlugin} from './FinancialPlugin' export {FormulaTextPlugin} from './FormulaTextPlugin' @@ -40,3 +39,4 @@ export {TextPlugin} from './TextPlugin' export {TrigonometryPlugin} from './TrigonometryPlugin' export {LookupPlugin} from './LookupPlugin' export {SimpleArithmerticPlugin} from './SimpleArithmertic' +export {StatisticalPlugin} from './StatisticalPlugin' From cd87b3ad765af32558d9248007d352bd4854cbaa Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 22 Oct 2020 20:13:11 +0200 Subject: [PATCH 02/30] jstat --- src/interpreter/plugin/3rdparty/jstat.ts | 255 +++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 src/interpreter/plugin/3rdparty/jstat.ts diff --git a/src/interpreter/plugin/3rdparty/jstat.ts b/src/interpreter/plugin/3rdparty/jstat.ts new file mode 100644 index 0000000000..c9598395c2 --- /dev/null +++ b/src/interpreter/plugin/3rdparty/jstat.ts @@ -0,0 +1,255 @@ +export function erf(x: number): number { + var cof = [-1.3026537197817094, 6.4196979235649026e-1, 1.9476473204185836e-2, + -9.561514786808631e-3, -9.46595344482036e-4, 3.66839497852761e-4, + 4.2523324806907e-5, -2.0278578112534e-5, -1.624290004647e-6, + 1.303655835580e-6, 1.5626441722e-8, -8.5238095915e-8, + 6.529054439e-9, 5.059343495e-9, -9.91364156e-10, + -2.27365122e-10, 9.6467911e-11, 2.394038e-12, + -6.886027e-12, 8.94487e-13, 3.13092e-13, + -1.12708e-13, 3.81e-16, 7.106e-15, + -1.523e-15, -9.4e-17, 1.21e-16, + -2.8e-17]; + var j = cof.length - 1; + var isneg = false; + var d = 0; + var dd = 0; + var t, ty, tmp, res; + + if(x===0) { + return 0 + } + if (x < 0) { + x = -x; + isneg = true; + } + + t = 2 / (2 + x); + ty = 4 * t - 2; + + for(; j > 0; j--) { + tmp = d; + d = ty * d - dd + cof[j]; + dd = tmp; + } + + res = t * Math.exp(-x * x + 0.5 * (cof[0] + ty * d) - dd); + return isneg ? res - 1 : 1 - res; +} + +export function erfc(x: number): number { + return 1 - erf(x) +} + +function erfcinv(p: number): number { + var j = 0; + var x, err, t, pp; + if (p >= 2) + return -100; + if (p <= 0) + return 100; + pp = (p < 1) ? p : 2 - p; + t = Math.sqrt(-2 * Math.log(pp / 2)); + x = -0.70711 * ((2.30753 + t * 0.27061) / + (1 + t * (0.99229 + t * 0.04481)) - t); + for (; j < 2; j++) { + err = erfc(x) - pp; + x += err / (1.12837916709551257 * Math.exp(-x * x) - x * err); + } + return (p < 1) ? x : -x; +} + +export const exponential = { + pdf: (x: number, rate: number): number => { + return x < 0 ? 0 : rate * Math.exp(-rate * x); + }, + + cdf: (x: number, rate: number): number => { + return x < 0 ? 0 : 1 - Math.exp(-rate * x); + }, +} + +export function gammafn(x: number): number { + var p = [-1.716185138865495, 24.76565080557592, -379.80425647094563, + 629.3311553128184, 866.9662027904133, -31451.272968848367, + -36144.413418691176, 66456.14382024054 + ]; + var q = [-30.8402300119739, 315.35062697960416, -1015.1563674902192, + -3107.771671572311, 22538.118420980151, 4755.8462775278811, + -134659.9598649693, -115132.2596755535]; + var fact: number | boolean = false; + var n = 0; + var xden = 0; + var xnum = 0; + var y = x; + var i, z, yi, res; + if (x > 171.6243769536076) { + return Infinity; + } + if (y <= 0) { + res = y % 1 + 3.6e-16; + if (res) { + fact = (!(y & 1) ? 1 : -1) * Math.PI / Math.sin(Math.PI * res); + y = 1 - y; + } else { + return Infinity; + } + } + yi = y; + if (y < 1) { + z = y++; + } else { + z = (y -= n = (y | 0) - 1) - 1; + } + for (i = 0; i < 8; ++i) { + xnum = (xnum + p[i]) * z; + xden = xden * z + q[i]; + } + res = xnum / xden + 1; + if (yi < y) { + res /= yi; + } else if (yi > y) { + for (i = 0; i < n; ++i) { + res *= y; + y++; + } + } + if (fact) { + res = fact / res; + } + return res; +} + +export const gamma = { + pdf: function pdf(x: number, shape: number, scale: number): number { + if (x < 0) + return 0; + return (x === 0 && shape === 1) ? 1 / scale : + Math.exp((shape - 1) * Math.log(x) - x / scale - + gammaln(shape) - shape * Math.log(scale)); + }, + + cdf: function cdf(x: number, shape: number, scale: number): number { + if (x < 0) + return 0; + return lowRegGamma(shape, x / scale); + }, + + inv: function(p: number, shape: number, scale: number): number { + return gammapinv(p, shape) * scale; + } +} + +export function gammaln(x: number): number { + var j = 0; + var cof = [ + 76.18009172947146, -86.50532032941677, 24.01409824083091, + -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5 + ]; + var ser = 1.000000000190015; + var xx, y, tmp; + tmp = (y = xx = x) + 5.5; + tmp -= (xx + 0.5) * Math.log(tmp); + for (; j < 6; j++) + ser += cof[j] / ++y; + return Math.log(2.5066282746310005 * ser / xx) - tmp; +} + +function lowRegGamma(a: number, x: number): number { + var aln = gammaln(a); + var ap = a; + var sum = 1 / a; + var del = sum; + var b = x + 1 - a; + var c = 1 / 1.0e-30; + var d = 1 / b; + var h = d; + var i = 1; + // calculate maximum number of itterations required for a + var ITMAX = -~(Math.log((a >= 1) ? a : 1 / a) * 8.5 + a * 0.4 + 17); + var an; + + if (x < 0 || a <= 0) { + return NaN; + } else if (x < a + 1) { + for (; i <= ITMAX; i++) { + sum += del *= x / ++ap; + } + return (sum * Math.exp(-x + a * Math.log(x) - (aln))); + } + + for (; i <= ITMAX; i++) { + an = -i * (i - a); + b += 2; + d = an * d + b; + c = b + an / c; + d = 1 / d; + h *= d * c; + } + + return (1 - h * Math.exp(-x + a * Math.log(x) - (aln))); +} + +function gammapinv(p: number, a: number) { + var j = 0; + var a1 = a - 1; + var EPS = 1e-8; + var gln = gammaln(a); + var x, err, t, u, pp + let lna1 : number | undefined + let afac : number | undefined + + if (p >= 1) + return Math.max(100, a + 100 * Math.sqrt(a)); + if (p <= 0) + return 0; + if (a > 1) { + lna1 = Math.log(a1); + afac = Math.exp(a1 * (lna1 - 1) - gln); + pp = (p < 0.5) ? p : 1 - p; + t = Math.sqrt(-2 * Math.log(pp)); + x = (2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t; + if (p < 0.5) + x = -x; + x = Math.max(1e-3, + a * Math.pow(1 - 1 / (9 * a) - x / (3 * Math.sqrt(a)), 3)); + } else { + t = 1 - a * (0.253 + a * 0.12); + if (p < t) + x = Math.pow(p / t, 1 / a); + else + x = 1 - Math.log(1 - (p - t) / (1 - t)); + } + + for(; j < 12; j++) { + if (x <= 0) + return 0; + err = lowRegGamma(a, x) - p; + if (a > 1) + t = afac! * Math.exp(-(x - a1) + a1 * (Math.log(x) - lna1!)); + else + t = Math.exp(-x + a1 * Math.log(x) - gln); + u = err / t; + x -= (t = u / (1 - 0.5 * Math.min(1, u * ((a - 1) / x - 1)))); + if (x <= 0) + x = 0.5 * (x + t); + if (Math.abs(t) < EPS * x) + break; + } + + return x; +}; + +export const normal = { + pdf: function pdf(x: number, mean: number, std: number) { + return Math.exp(-0.5 * Math.log(2 * Math.PI) - + Math.log(std) - Math.pow(x - mean, 2) / (2 * std * std)); + }, + + cdf: function cdf(x: number, mean: number, std: number) { + return 0.5 * (1 + erf((x - mean) / Math.sqrt(2 * std * std))); + }, + + inv: function(p: number, mean: number, std: number) { + return -1.41421356237309505 * std * erfcinv(2 * p) + mean; + } +} From 693fe3e71c08d48d843f4bc299b3fb84477c8574 Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 23 Oct 2020 13:19:11 +0200 Subject: [PATCH 03/30] . --- src/i18n/languages/enGB.ts | 11 ++++- test/interpreter/function-erf.spec.ts | 14 +++--- test/interpreter/function-erfc.spec.ts | 12 ++--- test/interpreter/function-expon.dist.spec.ts | 49 ++++++++++++++++++++ test/testUtils.ts | 8 ---- 5 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 test/interpreter/function-expon.dist.spec.ts diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index de84e3d8bf..53bf4ef21e 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -246,7 +246,16 @@ const dictionary: RawTranslationPackage = { VARP: 'VARP', VAR: 'VAR', STDEVP: 'STDEVP', - STDEV: 'STDEV' + STDEV: 'STDEV', + 'EXPON.DIST': 'EXPON.DIST', + FISHER: 'FISHER', + FISHERINV: 'FISHERINV', + GAMMA: 'GAMMA', + 'GAMMA.DIST': 'GAMMA.DIST', + GAMMALN: 'GAMMALN', + 'GAMMALN.PRECISE': 'GAMMALN.PRECISE', + 'GAMMA.INV': 'GAMMA.INV', + GAUSS: 'GAUSS', }, langCode: 'enGB', ui: { diff --git a/test/interpreter/function-erf.spec.ts b/test/interpreter/function-erf.spec.ts index b5e49bb5ef..a2c5004d73 100644 --- a/test/interpreter/function-erf.spec.ts +++ b/test/interpreter/function-erf.spec.ts @@ -1,10 +1,9 @@ import {HyperFormula} from '../../src' import {ErrorType} from '../../src/Cell' import {ErrorMessage} from '../../src/error-message' -import {adr, detailedError, expectCloseTo} from '../testUtils' +import {adr, detailedError} from '../testUtils' describe('Function ERF', () => { - const precision = 0.0000003 it('should return error for wrong number of arguments', () => { const engine = HyperFormula.buildFromArray([ @@ -35,9 +34,8 @@ describe('Function ERF', () => { ]) expect(engine.getCellValue(adr('A1'))).toEqual(0) - expectCloseTo(engine.getCellValue(adr('A2')), 0.8427007929497148, precision) - expectCloseTo(engine.getCellValue(adr('A3')), 0.9999910304344467, precision) - expectCloseTo(engine.getCellValue(adr('A4')), -0.999705836979508, precision) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(0.9999910304344467, 6) + expect(engine.getCellValue(adr('A4'))).toBeCloseTo(-0.999705836979508, 6) }) it('should work with second argument', () => { @@ -47,8 +45,8 @@ describe('Function ERF', () => { ['=ERF(5.6, -3.1)'], ]) - expectCloseTo(engine.getCellValue(adr('A1')), 0.32105562956522493, precision) - expectCloseTo(engine.getCellValue(adr('A2')), 1.9941790884215962, precision) - expectCloseTo(engine.getCellValue(adr('A3')), -1.9999883513426304, precision) + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.32105562956522493, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.9941790884215962, 6) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(-1.9999883513426304, 6) }) }) diff --git a/test/interpreter/function-erfc.spec.ts b/test/interpreter/function-erfc.spec.ts index f302880a90..424fe8b773 100644 --- a/test/interpreter/function-erfc.spec.ts +++ b/test/interpreter/function-erfc.spec.ts @@ -1,7 +1,7 @@ import {HyperFormula} from '../../src' import {ErrorType} from '../../src/Cell' import {ErrorMessage} from '../../src/error-message' -import {adr, detailedError, expectCloseTo} from '../testUtils' +import {adr, detailedError} from '../testUtils' describe('Function ERFC', () => { const precision = 0.0000003 @@ -31,9 +31,9 @@ describe('Function ERFC', () => { ['=ERFC(0.5)'], ]) - expectCloseTo(engine.getCellValue(adr('A1')), 1, precision) - expectCloseTo(engine.getCellValue(adr('A2')), 0.004677734981047288, precision) - expectCloseTo(engine.getCellValue(adr('A3')), 0.4795001221869535, precision) + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.004677734981047288, 6) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(0.4795001221869535, 6) }) it('should work for negative numbers', () => { @@ -42,7 +42,7 @@ describe('Function ERFC', () => { ['=ERFC(-14.8)'], ]) - expectCloseTo(engine.getCellValue(adr('A1')), 2, precision) - expectCloseTo(engine.getCellValue(adr('A2')), 2, precision) + expect(engine.getCellValue(adr('A1'))).toBe(2) + expect(engine.getCellValue(adr('A2'))).toBe(2) }) }) diff --git a/test/interpreter/function-expon.dist.spec.ts b/test/interpreter/function-expon.dist.spec.ts new file mode 100644 index 0000000000..673aee55cf --- /dev/null +++ b/test/interpreter/function-expon.dist.spec.ts @@ -0,0 +1,49 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function EXPON.DIST', () => { + + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=EXPON.DIST(1, 2)'], + ['=EXPON.DIST(1, 2, 3, 4)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=EXPON.DIST("foo", 2, TRUE())'], + ['=EXPON.DIST(1, "baz", TRUE())'], + ['=EXPON.DIST(1, 2, "abcd")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work as cdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=EXPON.DIST(1, 1, TRUE())'], + ['=EXPON.DIST(10, 2, TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.32105562956522493, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.9941790884215962, 6) + }) + + it('should work as pdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=EXPON.DIST(1, 1, FALSE())'], + ['=EXPON.DIST(10, 2, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.32105562956522493, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.9941790884215962, 6) + }) +}) diff --git a/test/testUtils.ts b/test/testUtils.ts index 21a406dbe1..f9349c5970 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -168,14 +168,6 @@ export function timeNumberToString(timeNumber: CellValue, config: Config): strin return timeString ?? '' } -export function expectCloseTo(actual: CellValue, expected: number, precision: number = 0.000001) { - if (typeof actual !== 'number') { - expect(true).toBe(false) - } else { - expect(Math.abs(actual - expected)).toBeLessThan(precision) - } -} - export function unregisterAllLanguages() { for (const langCode of HyperFormula.getRegisteredLanguagesCodes()) { HyperFormula.unregisterLanguage(langCode) From 2da480446d0a23f85df344863e2e92ddd7aebb3f Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 23 Oct 2020 13:52:33 +0200 Subject: [PATCH 04/30] . --- test/interpreter/function-expon.dist.spec.ts | 26 ++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/test/interpreter/function-expon.dist.spec.ts b/test/interpreter/function-expon.dist.spec.ts index 673aee55cf..10e9d94f1c 100644 --- a/test/interpreter/function-expon.dist.spec.ts +++ b/test/interpreter/function-expon.dist.spec.ts @@ -24,26 +24,38 @@ describe('Function EXPON.DIST', () => { expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) - expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.WrongType)) }) it('should work as cdf', () => { const engine = HyperFormula.buildFromArray([ ['=EXPON.DIST(1, 1, TRUE())'], - ['=EXPON.DIST(10, 2, TRUE())'], + ['=EXPON.DIST(3, 2, TRUE())'], ]) - expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.32105562956522493, 6) - expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.9941790884215962, 6) + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.632120558828558, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.997521247823334, 6) }) it('should work as pdf', () => { const engine = HyperFormula.buildFromArray([ ['=EXPON.DIST(1, 1, FALSE())'], - ['=EXPON.DIST(10, 2, FALSE())'], + ['=EXPON.DIST(3, 2, FALSE())'], ]) - expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.32105562956522493, 6) - expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.9941790884215962, 6) + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.367879441171442, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.00495750435333272, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=EXPON.DIST(0, 1, FALSE())'], + ['=EXPON.DIST(-0.00001, 1, FALSE())'], + ['=EXPON.DIST(1, 0, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) }) }) From c7a6e76bb693e6a59e0d4fd12130d30fa3b29d35 Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 23 Oct 2020 19:57:28 +0200 Subject: [PATCH 05/30] . --- test/interpreter/function-fisher.spec.ts | 45 +++++++++++++++++++++ test/interpreter/function-fisherinv.spec.ts | 37 +++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 test/interpreter/function-fisher.spec.ts create mode 100644 test/interpreter/function-fisherinv.spec.ts diff --git a/test/interpreter/function-fisher.spec.ts b/test/interpreter/function-fisher.spec.ts new file mode 100644 index 0000000000..eb3ca2b10d --- /dev/null +++ b/test/interpreter/function-fisher.spec.ts @@ -0,0 +1,45 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function FISHER', () => { + + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=FISHER()'], + ['=FISHER(1, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=FISHER("foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=FISHER(0)'], + ['=FISHER(0.5)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(0) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.549306144334055, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=FISHER(-1)'], + ['=FISHER(1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + }) +}) diff --git a/test/interpreter/function-fisherinv.spec.ts b/test/interpreter/function-fisherinv.spec.ts new file mode 100644 index 0000000000..f1b51d56f0 --- /dev/null +++ b/test/interpreter/function-fisherinv.spec.ts @@ -0,0 +1,37 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function FISHERINV', () => { + + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=FISHERINV()'], + ['=FISHERINV(1, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=FISHERINV("foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=FISHERINV(0)'], + ['=FISHERINV(0.5)'], + ['=FISHERINV(-5)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(0) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.46211715726001, 6) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(-0.999909204262595, 6) + }) +}) From b99ef7ef83181a7ffa5f1ceb68a8d84c1558f018 Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 23 Oct 2020 22:25:32 +0200 Subject: [PATCH 06/30] test --- src/interpreter/plugin/3rdparty/jstat.ts | 2 +- test/interpreter/function-fisher.spec.ts | 1 - test/interpreter/function-fisherinv.spec.ts | 1 - test/interpreter/function-gamma.dist.spec.ts | 65 +++++++++++++++++++ test/interpreter/function-gamma.spec.ts | 49 ++++++++++++++ ...nctions-na.spec.ts => function-na.spec.ts} | 0 6 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 test/interpreter/function-gamma.dist.spec.ts create mode 100644 test/interpreter/function-gamma.spec.ts rename test/interpreter/{functions-na.spec.ts => function-na.spec.ts} (100%) diff --git a/src/interpreter/plugin/3rdparty/jstat.ts b/src/interpreter/plugin/3rdparty/jstat.ts index c9598395c2..a20cae3aa5 100644 --- a/src/interpreter/plugin/3rdparty/jstat.ts +++ b/src/interpreter/plugin/3rdparty/jstat.ts @@ -86,7 +86,7 @@ export function gammafn(x: number): number { return Infinity; } if (y <= 0) { - res = y % 1 + 3.6e-16; + res = y % 1; if (res) { fact = (!(y & 1) ? 1 : -1) * Math.PI / Math.sin(Math.PI * res); y = 1 - y; diff --git a/test/interpreter/function-fisher.spec.ts b/test/interpreter/function-fisher.spec.ts index eb3ca2b10d..274cb6c4d3 100644 --- a/test/interpreter/function-fisher.spec.ts +++ b/test/interpreter/function-fisher.spec.ts @@ -4,7 +4,6 @@ import {ErrorMessage} from '../../src/error-message' import {adr, detailedError} from '../testUtils' describe('Function FISHER', () => { - it('should return error for wrong number of arguments', () => { const engine = HyperFormula.buildFromArray([ ['=FISHER()'], diff --git a/test/interpreter/function-fisherinv.spec.ts b/test/interpreter/function-fisherinv.spec.ts index f1b51d56f0..0ab27bf0d9 100644 --- a/test/interpreter/function-fisherinv.spec.ts +++ b/test/interpreter/function-fisherinv.spec.ts @@ -4,7 +4,6 @@ import {ErrorMessage} from '../../src/error-message' import {adr, detailedError} from '../testUtils' describe('Function FISHERINV', () => { - it('should return error for wrong number of arguments', () => { const engine = HyperFormula.buildFromArray([ ['=FISHERINV()'], diff --git a/test/interpreter/function-gamma.dist.spec.ts b/test/interpreter/function-gamma.dist.spec.ts new file mode 100644 index 0000000000..c8e022941c --- /dev/null +++ b/test/interpreter/function-gamma.dist.spec.ts @@ -0,0 +1,65 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function GAMMA.DIST', () => { + + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA.DIST(1, 2, 3)'], + ['=GAMMA.DIST(1, 2, 3, 4, 5)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA.DIST("foo", 2, 3, TRUE())'], + ['=GAMMA.DIST(1, "baz", 3, TRUE())'], + ['=GAMMA.DIST(1, 2, "baz", TRUE())'], + ['=GAMMA.DIST(1, 2, 3, "abcd")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.WrongType)) + }) + + it('should work as cdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA.DIST(1, 1, 2, TRUE())'], + ['=GAMMA.DIST(3, 2, 4, TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.393469340287367, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.173358532703224, 6) + }) + + it('should work as pdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA.DIST(1, 1, 2, FALSE())'], + ['=GAMMA.DIST(3, 2, 4, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.303265329856317, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.0885687286389403, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA.DIST(0, 1, 1, FALSE())'], + ['=GAMMA.DIST(-0.00001, 1, 1, FALSE())'], + ['=GAMMA.DIST(1, 0, 1, FALSE())'], + ['=GAMMA.DIST(1, 1, 0, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + }) +}) diff --git a/test/interpreter/function-gamma.spec.ts b/test/interpreter/function-gamma.spec.ts new file mode 100644 index 0000000000..1ca1d32f5b --- /dev/null +++ b/test/interpreter/function-gamma.spec.ts @@ -0,0 +1,49 @@ +import {ErrorType, HyperFormula} from '../../src' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function GAMMA', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA()'], + ['=GAMMA(1, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA("foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA(1)'], + ['=GAMMA(0.5)'], + ['=GAMMA(10.5)'], + ['=GAMMA(-2.5)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.77245385588014, 6) + expect(engine.getCellValue(adr('A3')) as number / 1133278.39212948).toBeCloseTo(1, 6) + expect(engine.getCellValue(adr('A4'))).toBeCloseTo(-0.94530871782981, 6) + }) + + it('should return nan', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA(0)'], + ['=GAMMA(-1)'], + ['=GAMMA(180)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.NaN)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.NaN)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.NaN)) + }) +}) diff --git a/test/interpreter/functions-na.spec.ts b/test/interpreter/function-na.spec.ts similarity index 100% rename from test/interpreter/functions-na.spec.ts rename to test/interpreter/function-na.spec.ts From 78869c590875f1cb544f2d556f4348c22b680d9b Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 23 Oct 2020 23:08:55 +0200 Subject: [PATCH 07/30] tests --- src/interpreter/plugin/StatisticalPlugin.ts | 4 +- test/interpreter/function-gamma.inv.spec.ts | 55 +++++++++++++++++++ .../function-gammaln.precise.spec.ts | 10 ++++ test/interpreter/function-gammaln.spec.ts | 44 +++++++++++++++ test/interpreter/function-gauss.spec.ts | 36 ++++++++++++ 5 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 test/interpreter/function-gamma.inv.spec.ts create mode 100644 test/interpreter/function-gammaln.precise.spec.ts create mode 100644 test/interpreter/function-gammaln.spec.ts create mode 100644 test/interpreter/function-gauss.spec.ts diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index f427398734..85296918e8 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -73,7 +73,7 @@ export class StatisticalPlugin extends FunctionPlugin { 'GAMMA.INV': { method: 'gammainv', parameters: [ - {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0, lessThan: 1}, {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, ] @@ -146,7 +146,7 @@ export class StatisticalPlugin extends FunctionPlugin { } public gammainv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.metadata('GAMMALN'), gamma.inv) + return this.runFunction(ast.args, formulaAddress, this.metadata('GAMMA.INV'), gamma.inv) } public gauss(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { diff --git a/test/interpreter/function-gamma.inv.spec.ts b/test/interpreter/function-gamma.inv.spec.ts new file mode 100644 index 0000000000..2782f56938 --- /dev/null +++ b/test/interpreter/function-gamma.inv.spec.ts @@ -0,0 +1,55 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function GAMMA.INV', () => { + + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA.INV(1, 2)'], + ['=GAMMA.INV(1, 2, 3, 4)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA.INV("foo", 2, 3)'], + ['=GAMMA.INV(0.5, "baz", 3)'], + ['=GAMMA.INV(0.5, 2, "baz")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA.INV(0.5, 1, 1)'], + ['=GAMMA.INV(0.9, 2, 4)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.693147180559945, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(15.5588806794697, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMA.INV(0, 1, 1)'], + ['=GAMMA.INV(-0.00001, 1, 1)'], + ['=GAMMA.INV(1, 1, 1)'], + ['=GAMMA.INV(0, 0, 1)'], + ['=GAMMA.INV(0, 1, 0)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(0) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + }) +}) diff --git a/test/interpreter/function-gammaln.precise.spec.ts b/test/interpreter/function-gammaln.precise.spec.ts new file mode 100644 index 0000000000..0ff49cf8d9 --- /dev/null +++ b/test/interpreter/function-gammaln.precise.spec.ts @@ -0,0 +1,10 @@ +import {HyperFormula} from '../../src' + +describe('Function GAMMALN.PRECISE', () => { + it('should be an alias of GAMMALN', () => { + const engine = HyperFormula.buildEmpty() + const metadata1 = engine.getFunctionPlugin('GAMMALN.PRECISE')?.implementedFunctions!['GAMMALN.PRECISE'] + const metadata2 = engine.getFunctionPlugin('GAMMALN')?.implementedFunctions!['GAMMALN'] + expect(metadata1).toEqual(metadata2) + }) +}) diff --git a/test/interpreter/function-gammaln.spec.ts b/test/interpreter/function-gammaln.spec.ts new file mode 100644 index 0000000000..8d41a72db4 --- /dev/null +++ b/test/interpreter/function-gammaln.spec.ts @@ -0,0 +1,44 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function GAMMALN', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMALN()'], + ['=GAMMALN(1, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMALN("foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMALN(0.1)'], + ['=GAMMALN(1)'], + ['=GAMMALN(10)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(2.25271265173425, 6) + expect(engine.getCellValue(adr('A2'))).toEqual(0) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(12.801827480082, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAMMALN(0)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + }) +}) diff --git a/test/interpreter/function-gauss.spec.ts b/test/interpreter/function-gauss.spec.ts new file mode 100644 index 0000000000..c0f8814432 --- /dev/null +++ b/test/interpreter/function-gauss.spec.ts @@ -0,0 +1,36 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function GAUSS', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAUSS()'], + ['=GAUSS(1, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAUSS("foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=GAUSS(-10)'], + ['=GAUSS(0)'], + ['=GAUSS(1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(-0.5, 6) + expect(engine.getCellValue(adr('A2'))).toEqual(0) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(0.341344746068543, 6) + }) +}) From e86eb4eca235b69074d14f95b92bf0b15283fba2 Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 24 Oct 2020 00:12:51 +0200 Subject: [PATCH 08/30] docs --- CHANGELOG.md | 1 + docs/guide/built-in-functions.md | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f43bd43b..9e9c4d481a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Added 18 mathematical functions ROMAN, ARABIC, FACT, FACTDOUBLE, COMBIN, COMBINA, GCD, LCM, MROUND, MULTINOMIAL, QUOTIENT, RANDBETWEEN, SERIESSUM, SIGN, SQRTPI, SUMX2MY2, SUMX2PY2, SUMXMY2. +- Added 12 statistical functions EXPON.DIST, EXPONDIST, FISHER, FISHERINV, GAMMA, GAMMA.DIST, GAMMADIST, GAMMALN, GAMMALN.PRECISE, GAMMA.INV, GAMMAINV, GAUSS. ## [0.3.0] - 2020-10-22 diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 4f6ccdc520..76d4425a20 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -269,6 +269,18 @@ lets you design your own [custom functions](custom-functions). | COUNTBLANK | Statistical | Returns the number of empty cells. | COUNTBLANK(Range) | | COUNTIF | Statistical | Returns the number of cells that meet with certain criteria within a cell range. | COUNTIF(Range; Criteria) | | COUNTIFS | Statistical | Returns the count of rows or columns that meet criteria in multiple ranges. | COUNTIFS(Range1; Criterion1 [; Range2; Criterion2 [; ...]]) | +| EXPON.DIST | Statistical | Returns density of a exponential distribution. | EXPON.DIST(Number1; Number2; Boolean) | +| EXPONDIST | Statistical | Returns density of a exponential distribution. | EXPONDIST(Number1; Number2; Boolean) | +| FISHER | Statistical | Returns Fisher transformation value. | FISHER(Number) | +| FISHERINV | Statistical | Returns inverse Fischer transformation value. | FISHERINV(Number) | +| GAMMA | Statistical | Returns value of Gamma function. | GAMMA(Number) | +| GAMMA.DIST | Statistical | Returns density of Gamma distribution. | GAMMA.DIST(Number1; Number2; Number3; Boolean) | +| GAMMADIST | Statistical | Returns density of Gamma distribution. | GAMMADIST(Number1; Number2; Number3; Boolean) | +| GAMMALN | Statistical | Returns natural logarithm of Gamma function. | GAMMALN(Number) | +| GAMMALN.PRECISE | Statistical | Returns natural logarithm of Gamma function. | GAMMALN.PRECISE(Number) | +| GAMMA.INV | Statistical | Returns inverse Gamma distribution value. | GAMMA.INV(Number1; Number2; Number3) | +| GAMMAINV | Statistical | Returns inverse Gamma distribution value. | GAMMAINV(Number1; Number2; Number3) | +| GAUSS | Statistical | Returns the probability of Gaussian variable fall more than this many times standard deviation from mean. | GAUSS(Number) | | MAX | Statistical | Returns the maximum value in a list of arguments. | MAX(Number1; Number2; ...Number30) | | MAXA | Statistical | Returns the maximum value in a list of arguments. | MAXA(Value1; Value2; ... Value30) | | MEDIAN | Statistical | Returns the median of a set of numbers. | MEDIAN(Number1; Number2; ...Number30) | From 85ddf4f9d07c978e4f1519034d9e3721fef94eb6 Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 24 Oct 2020 00:25:55 +0200 Subject: [PATCH 09/30] license + linter --- src/interpreter/plugin/3rdparty/jstat.ts | 266 ++++++++++++----------- 1 file changed, 145 insertions(+), 121 deletions(-) diff --git a/src/interpreter/plugin/3rdparty/jstat.ts b/src/interpreter/plugin/3rdparty/jstat.ts index a20cae3aa5..2afd5ee27b 100644 --- a/src/interpreter/plugin/3rdparty/jstat.ts +++ b/src/interpreter/plugin/3rdparty/jstat.ts @@ -1,5 +1,29 @@ +/* eslint-disable */ +/** + * @license + Copyright (c) 2013 jStat + + 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, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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. + */ + export function erf(x: number): number { - var cof = [-1.3026537197817094, 6.4196979235649026e-1, 1.9476473204185836e-2, + const cof = [-1.3026537197817094, 6.4196979235649026e-1, 1.9476473204185836e-2, -9.561514786808631e-3, -9.46595344482036e-4, 3.66839497852761e-4, 4.2523324806907e-5, -2.0278578112534e-5, -1.624290004647e-6, 1.303655835580e-6, 1.5626441722e-8, -8.5238095915e-8, @@ -8,32 +32,32 @@ export function erf(x: number): number { -6.886027e-12, 8.94487e-13, 3.13092e-13, -1.12708e-13, 3.81e-16, 7.106e-15, -1.523e-15, -9.4e-17, 1.21e-16, - -2.8e-17]; - var j = cof.length - 1; - var isneg = false; - var d = 0; - var dd = 0; - var t, ty, tmp, res; + -2.8e-17] + let j = cof.length - 1 + let isneg = false + let d = 0 + let dd = 0 + let t, ty, tmp, res if(x===0) { return 0 } if (x < 0) { - x = -x; - isneg = true; + x = -x + isneg = true } - t = 2 / (2 + x); - ty = 4 * t - 2; + t = 2 / (2 + x) + ty = 4 * t - 2 for(; j > 0; j--) { - tmp = d; - d = ty * d - dd + cof[j]; - dd = tmp; + tmp = d + d = ty * d - dd + cof[j] + dd = tmp } - res = t * Math.exp(-x * x + 0.5 * (cof[0] + ty * d) - dd); - return isneg ? res - 1 : 1 - res; + res = t * Math.exp(-x * x + 0.5 * (cof[0] + ty * d) - dd) + return isneg ? res - 1 : 1 - res } export function erfc(x: number): number { @@ -41,215 +65,215 @@ export function erfc(x: number): number { } function erfcinv(p: number): number { - var j = 0; - var x, err, t, pp; + let j = 0 + let x, err, t, pp if (p >= 2) - return -100; + return -100 if (p <= 0) - return 100; - pp = (p < 1) ? p : 2 - p; - t = Math.sqrt(-2 * Math.log(pp / 2)); + return 100 + pp = (p < 1) ? p : 2 - p + t = Math.sqrt(-2 * Math.log(pp / 2)) x = -0.70711 * ((2.30753 + t * 0.27061) / - (1 + t * (0.99229 + t * 0.04481)) - t); + (1 + t * (0.99229 + t * 0.04481)) - t) for (; j < 2; j++) { - err = erfc(x) - pp; - x += err / (1.12837916709551257 * Math.exp(-x * x) - x * err); + err = erfc(x) - pp + x += err / (1.12837916709551257 * Math.exp(-x * x) - x * err) } - return (p < 1) ? x : -x; + return (p < 1) ? x : -x } export const exponential = { pdf: (x: number, rate: number): number => { - return x < 0 ? 0 : rate * Math.exp(-rate * x); + return x < 0 ? 0 : rate * Math.exp(-rate * x) }, cdf: (x: number, rate: number): number => { - return x < 0 ? 0 : 1 - Math.exp(-rate * x); + return x < 0 ? 0 : 1 - Math.exp(-rate * x) }, } export function gammafn(x: number): number { - var p = [-1.716185138865495, 24.76565080557592, -379.80425647094563, + const p = [-1.716185138865495, 24.76565080557592, -379.80425647094563, 629.3311553128184, 866.9662027904133, -31451.272968848367, -36144.413418691176, 66456.14382024054 - ]; - var q = [-30.8402300119739, 315.35062697960416, -1015.1563674902192, + ] + const q = [-30.8402300119739, 315.35062697960416, -1015.1563674902192, -3107.771671572311, 22538.118420980151, 4755.8462775278811, - -134659.9598649693, -115132.2596755535]; - var fact: number | boolean = false; - var n = 0; - var xden = 0; - var xnum = 0; - var y = x; - var i, z, yi, res; + -134659.9598649693, -115132.2596755535] + let fact: number | boolean = false + let n = 0 + let xden = 0 + let xnum = 0 + let y = x + let i, z, yi, res if (x > 171.6243769536076) { - return Infinity; + return Infinity } if (y <= 0) { - res = y % 1; + res = y % 1 if (res) { - fact = (!(y & 1) ? 1 : -1) * Math.PI / Math.sin(Math.PI * res); - y = 1 - y; + fact = (!(y & 1) ? 1 : -1) * Math.PI / Math.sin(Math.PI * res) + y = 1 - y } else { - return Infinity; + return Infinity } } - yi = y; + yi = y if (y < 1) { - z = y++; + z = y++ } else { - z = (y -= n = (y | 0) - 1) - 1; + z = (y -= n = (y | 0) - 1) - 1 } for (i = 0; i < 8; ++i) { - xnum = (xnum + p[i]) * z; - xden = xden * z + q[i]; + xnum = (xnum + p[i]) * z + xden = xden * z + q[i] } - res = xnum / xden + 1; + res = xnum / xden + 1 if (yi < y) { - res /= yi; + res /= yi } else if (yi > y) { for (i = 0; i < n; ++i) { - res *= y; - y++; + res *= y + y++ } } if (fact) { - res = fact / res; + res = fact / res } - return res; + return res } export const gamma = { pdf: function pdf(x: number, shape: number, scale: number): number { if (x < 0) - return 0; + return 0 return (x === 0 && shape === 1) ? 1 / scale : Math.exp((shape - 1) * Math.log(x) - x / scale - - gammaln(shape) - shape * Math.log(scale)); + gammaln(shape) - shape * Math.log(scale)) }, cdf: function cdf(x: number, shape: number, scale: number): number { if (x < 0) - return 0; - return lowRegGamma(shape, x / scale); + return 0 + return lowRegGamma(shape, x / scale) }, inv: function(p: number, shape: number, scale: number): number { - return gammapinv(p, shape) * scale; + return gammapinv(p, shape) * scale } } export function gammaln(x: number): number { - var j = 0; - var cof = [ + let j = 0 + const cof = [ 76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5 - ]; - var ser = 1.000000000190015; - var xx, y, tmp; - tmp = (y = xx = x) + 5.5; - tmp -= (xx + 0.5) * Math.log(tmp); + ] + let ser = 1.000000000190015 + let xx, y, tmp + tmp = (y = xx = x) + 5.5 + tmp -= (xx + 0.5) * Math.log(tmp) for (; j < 6; j++) - ser += cof[j] / ++y; - return Math.log(2.5066282746310005 * ser / xx) - tmp; + ser += cof[j] / ++y + return Math.log(2.5066282746310005 * ser / xx) - tmp } function lowRegGamma(a: number, x: number): number { - var aln = gammaln(a); - var ap = a; - var sum = 1 / a; - var del = sum; - var b = x + 1 - a; - var c = 1 / 1.0e-30; - var d = 1 / b; - var h = d; - var i = 1; + const aln = gammaln(a) + let ap = a + let sum = 1 / a + let del = sum + let b = x + 1 - a + let c = 1 / 1.0e-30 + let d = 1 / b + let h = d + let i = 1 // calculate maximum number of itterations required for a - var ITMAX = -~(Math.log((a >= 1) ? a : 1 / a) * 8.5 + a * 0.4 + 17); - var an; + const ITMAX = -~(Math.log((a >= 1) ? a : 1 / a) * 8.5 + a * 0.4 + 17) + let an if (x < 0 || a <= 0) { - return NaN; + return NaN } else if (x < a + 1) { for (; i <= ITMAX; i++) { - sum += del *= x / ++ap; + sum += del *= x / ++ap } - return (sum * Math.exp(-x + a * Math.log(x) - (aln))); + return (sum * Math.exp(-x + a * Math.log(x) - (aln))) } for (; i <= ITMAX; i++) { - an = -i * (i - a); - b += 2; - d = an * d + b; - c = b + an / c; - d = 1 / d; - h *= d * c; + an = -i * (i - a) + b += 2 + d = an * d + b + c = b + an / c + d = 1 / d + h *= d * c } - return (1 - h * Math.exp(-x + a * Math.log(x) - (aln))); + return (1 - h * Math.exp(-x + a * Math.log(x) - (aln))) } function gammapinv(p: number, a: number) { - var j = 0; - var a1 = a - 1; - var EPS = 1e-8; - var gln = gammaln(a); - var x, err, t, u, pp - let lna1 : number | undefined - let afac : number | undefined + let j = 0 + const a1 = a - 1 + const EPS = 1e-8 + const gln = gammaln(a) + let x, err, t, u, pp + let lna1: number | undefined + let afac: number | undefined if (p >= 1) - return Math.max(100, a + 100 * Math.sqrt(a)); + return Math.max(100, a + 100 * Math.sqrt(a)) if (p <= 0) - return 0; + return 0 if (a > 1) { - lna1 = Math.log(a1); - afac = Math.exp(a1 * (lna1 - 1) - gln); - pp = (p < 0.5) ? p : 1 - p; - t = Math.sqrt(-2 * Math.log(pp)); - x = (2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t; + lna1 = Math.log(a1) + afac = Math.exp(a1 * (lna1 - 1) - gln) + pp = (p < 0.5) ? p : 1 - p + t = Math.sqrt(-2 * Math.log(pp)) + x = (2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t if (p < 0.5) - x = -x; + x = -x x = Math.max(1e-3, - a * Math.pow(1 - 1 / (9 * a) - x / (3 * Math.sqrt(a)), 3)); + a * Math.pow(1 - 1 / (9 * a) - x / (3 * Math.sqrt(a)), 3)) } else { - t = 1 - a * (0.253 + a * 0.12); + t = 1 - a * (0.253 + a * 0.12) if (p < t) - x = Math.pow(p / t, 1 / a); + x = Math.pow(p / t, 1 / a) else - x = 1 - Math.log(1 - (p - t) / (1 - t)); + x = 1 - Math.log(1 - (p - t) / (1 - t)) } for(; j < 12; j++) { if (x <= 0) - return 0; - err = lowRegGamma(a, x) - p; + return 0 + err = lowRegGamma(a, x) - p if (a > 1) - t = afac! * Math.exp(-(x - a1) + a1 * (Math.log(x) - lna1!)); + t = afac! * Math.exp(-(x - a1) + a1 * (Math.log(x) - lna1!)) else - t = Math.exp(-x + a1 * Math.log(x) - gln); - u = err / t; - x -= (t = u / (1 - 0.5 * Math.min(1, u * ((a - 1) / x - 1)))); + t = Math.exp(-x + a1 * Math.log(x) - gln) + u = err / t + x -= (t = u / (1 - 0.5 * Math.min(1, u * ((a - 1) / x - 1)))) if (x <= 0) - x = 0.5 * (x + t); + x = 0.5 * (x + t) if (Math.abs(t) < EPS * x) - break; + break } - return x; -}; + return x +} export const normal = { pdf: function pdf(x: number, mean: number, std: number) { return Math.exp(-0.5 * Math.log(2 * Math.PI) - - Math.log(std) - Math.pow(x - mean, 2) / (2 * std * std)); + Math.log(std) - Math.pow(x - mean, 2) / (2 * std * std)) }, cdf: function cdf(x: number, mean: number, std: number) { - return 0.5 * (1 + erf((x - mean) / Math.sqrt(2 * std * std))); + return 0.5 * (1 + erf((x - mean) / Math.sqrt(2 * std * std))) }, inv: function(p: number, mean: number, std: number) { - return -1.41421356237309505 * std * erfcinv(2 * p) + mean; + return -1.41421356237309505 * std * erfcinv(2 * p) + mean } } From 4d7764c8f03c4d9b36fc020a0403873598c75d11 Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 24 Oct 2020 15:05:15 +0200 Subject: [PATCH 10/30] beta.dist, beta.inv --- CHANGELOG.md | 2 +- docs/guide/built-in-functions.md | 4 + src/i18n/languages/csCZ.ts | 4 + src/i18n/languages/daDK.ts | 4 + src/i18n/languages/deDE.ts | 4 + src/i18n/languages/enGB.ts | 4 + src/i18n/languages/esES.ts | 4 + src/i18n/languages/fiFI.ts | 4 + src/i18n/languages/frFR.ts | 4 + src/i18n/languages/huHU.ts | 4 + src/i18n/languages/itIT.ts | 4 + src/i18n/languages/nbNO.ts | 4 + src/i18n/languages/nlNL.ts | 4 + src/i18n/languages/plPL.ts | 4 + src/i18n/languages/ptPT.ts | 4 + src/i18n/languages/ruRU.ts | 4 + src/i18n/languages/svSE.ts | 4 + src/i18n/languages/trTR.ts | 4 + src/interpreter/plugin/3rdparty/jstat.ts | 155 ++++++++++++++++++++ src/interpreter/plugin/StatisticalPlugin.ts | 73 ++++++++- test/interpreter/function-beta.dist.spec.ts | 80 ++++++++++ test/interpreter/function-beta.inv.spec.ts | 70 +++++++++ test/interpreter/function-betadist.spec.ts | 10 ++ 23 files changed, 452 insertions(+), 6 deletions(-) create mode 100644 test/interpreter/function-beta.dist.spec.ts create mode 100644 test/interpreter/function-beta.inv.spec.ts create mode 100644 test/interpreter/function-betadist.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e9c4d481a..0748b6e8ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Added 18 mathematical functions ROMAN, ARABIC, FACT, FACTDOUBLE, COMBIN, COMBINA, GCD, LCM, MROUND, MULTINOMIAL, QUOTIENT, RANDBETWEEN, SERIESSUM, SIGN, SQRTPI, SUMX2MY2, SUMX2PY2, SUMXMY2. -- Added 12 statistical functions EXPON.DIST, EXPONDIST, FISHER, FISHERINV, GAMMA, GAMMA.DIST, GAMMADIST, GAMMALN, GAMMALN.PRECISE, GAMMA.INV, GAMMAINV, GAUSS. +- Added 16 statistical functions EXPON.DIST, EXPONDIST, FISHER, FISHERINV, GAMMA, GAMMA.DIST, GAMMADIST, GAMMALN, GAMMALN.PRECISE, GAMMA.INV, GAMMAINV, GAUSS, BETA.DIST, BETADIST, BETA.INV, BETAINV. ## [0.3.0] - 2020-10-22 diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 76d4425a20..898f8b3e86 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -263,6 +263,10 @@ lets you design your own [custom functions](custom-functions). | AVERAGE | Statistical | Returns the average of the arguments. | AVERAGE(Number1; Number2; ...Number30) | | AVERAGEA | Statistical | Returns the average of the arguments. | AVERAGEA(Value1; Value2; ... Value30) | | AVERAGEIF | Statistical | Returns the arithmetic mean of all cells in a range that satisfy a given condition. | AVERAGEIF(Range; Criterion [; Average_Range ]) | +| BETA.DIST | Statistical | Returns the denisty of Beta distribution. | BETA.DIST(Number1; Number2; Number3; Boolean[; Number4[; Number5]]) | +| BETADIST | Statistical | Returns the denisty of Beta distribution. | BETADIST(Number1; Number2; Number3; Boolean[; Number4[; Number5]]) | +| BETA.INV | Statistical | Returns the inverse Beta distribution value. | BETA.INV(Number1; Number2; Number3[; Number4[; Number5]]) | +| BETAINV | Statistical | Returns the inverse of Beta distribution value. | BETAINV(Number1; Number2; Number3[; Number4[; Number5]]) | | CORREL | Statistical | Returns the correlation coefficient between two data sets. | CORREL(Data1; Data2) | | COUNT | Statistical | Counts how many numbers are in the list of arguments. | COUNT(Value1; Value2; ... Value30) | | COUNTA | Statistical | Counts how many values are in the list of arguments. | COUNTA(Value1; Value2; ... Value30) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 9bf4bffdd1..2c6f0adbbc 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'GAMMALN', 'GAMMALN.PRECISE': 'GAMMALN.PRECISE', GAUSS: 'GAUSS', + 'BETA.DIST': 'BETA.DIST', + BETADIST: 'BETADIST', + 'BETA.INV': 'BETA.INV', + BETAINV: 'BETAINV', }, langCode: 'csCZ', ui: { diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 0ca741aaaa..487cb37dc0 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'GAMMALN', 'GAMMALN.PRECISE': 'GAMMALN.PRECISE', GAUSS: 'GAUSS', + 'BETA.DIST': 'BETA.FORDELING', + BETADIST: 'BETAFORDELING', + 'BETA.INV': 'BETA.INV', + BETAINV: 'BETAINV', }, langCode: 'daDK', ui: { diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index ab0a7034c6..6dd77d9d78 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'GAMMALN', 'GAMMALN.PRECISE': 'GAMMALN.GENAU', GAUSS: 'GAUSS', + 'BETA.DIST': 'BETA.VERT', + BETADIST: 'BETAVERT', + 'BETA.INV': 'BETA.INV', + BETAINV: 'BETAINV', }, langCode: 'deDE', ui: { diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 7e0d45a3e0..fcb38d0426 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { 'EXPONDIST': 'EXPONDIST', GAMMADIST: 'GAMMADIST', GAMMAINV: 'GAMMAINV', + 'BETA.DIST': 'BETA.DIST', + BETADIST: 'BETA.DIST', + 'BETA.INV': 'BETA.INV', + BETAINV: 'BETAINV', }, langCode: 'enGB', ui: { diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 2f6b55dc5d..abdeb66534 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -277,6 +277,10 @@ export const dictionary: RawTranslationPackage = { GAMMALN: 'GAMMA.LN', 'GAMMALN.PRECISE': 'GAMMA.LN.EXACTO', GAUSS: 'GAUSS', + 'BETA.DIST': 'DISTR.BETA.N', + BETADIST: 'DISTR.BETA', + 'BETA.INV': 'INV.BETA.N', + BETAINV: 'DISTR.BETA.INV', }, langCode: 'esES', ui: { diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 773eaf0b65..6f7eade452 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'GAMMALN', 'GAMMALN.PRECISE': 'GAMMALN.TARKKA', GAUSS: 'GAUSS', + 'BETA.DIST': 'BEETA.JAKAUMA', + BETADIST: 'BEETAJAKAUMA', + 'BETA.INV': 'BEETA.KÄÄNT', + BETAINV: 'BEETAJAKAUMA.KÄÄNT', }, langCode: 'fiFI', ui: { diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index a2235782de..0d3effdb4d 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'LNGAMMA', 'GAMMALN.PRECISE': 'LNGAMMA.PRECIS', GAUSS: 'GAUSS', + 'BETA.DIST': 'LOI.BETA.N', + BETADIST: 'LOI.BETA', + 'BETA.INV': 'BETA.INVERSE.N', + BETAINV: 'BETA.INVERSE', }, langCode: 'frFR', ui: { diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index be4d151017..d3b4a9a967 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'GAMMALN', 'GAMMALN.PRECISE': 'GAMMALN.PONTOS', GAUSS: 'GAUSS', + 'BETA.DIST': 'BÉTA.ELOSZL', + BETADIST: 'BÉTA.ELOSZLÁS', + 'BETA.INV': 'BÉTA.INVERZ', + BETAINV: 'INVERZ.BÉTA', }, langCode: 'huHU', ui: { diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index f860326e9a..be15d7f3d2 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'LN.GAMMA', 'GAMMALN.PRECISE': 'LN.GAMMA.PRECISA', GAUSS: 'GAUSS', + 'BETA.DIST': 'DISTRIB.BETA.N', + BETADIST: 'DISTRIB.BETA', + 'BETA.INV': 'INV.BETA.N', + BETAINV: 'INV.BETA', }, langCode: 'itIT', ui: { diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index cb388b8bac..01b38faecd 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'GAMMALN', 'GAMMALN.PRECISE': 'GAMMALN.PRESIS', GAUSS: 'GAUSS', + 'BETA.DIST': 'BETA.FORDELING.N', + BETADIST: 'BETA.FORDELING', + 'BETA.INV': 'BETA.INV', + BETAINV: 'INVERS.BETA.FORDELING', }, langCode: 'nbNO', ui: { diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index ed38442c62..a2710b2483 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'GAMMA.LN', 'GAMMALN.PRECISE': 'GAMMA.LN.NAUWKEURIG', GAUSS: 'GAUSS', + 'BETA.DIST': 'BETA.VERD', + BETADIST: 'BETAVERD', + 'BETA.INV': 'BETA.INV', + BETAINV: 'BETAINV', }, langCode: 'nlNL', ui: { diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index dfce2b743e..40c952fd51 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'ROZKŁAD.LIN.GAMMA', 'GAMMALN.PRECISE': 'ROZKŁAD.LIN.GAMMA.DOKŁ', GAUSS: 'GAUSS', + 'BETA.DIST': 'ROZKŁ.BETA', + BETADIST: 'ROZKŁAD.BETA', + 'BETA.INV': 'ROZKŁ.BETA.ODWR', + BETAINV: 'ROZKŁAD.BETA.ODW', }, langCode: 'plPL', ui: { diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 3011d5288d..ef142af680 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'LNGAMA', 'GAMMALN.PRECISE': 'LNGAMA.PRECISO', GAUSS: 'GAUSS', + 'BETA.DIST': 'DIST.BETA', + BETADIST: 'DISTBETA', + 'BETA.INV': 'INV.BETA', + BETAINV: 'BETA.ACUM.INV', }, langCode: 'ptPT', ui: { diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 92f02e745c..14bad816d7 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'ГАММАНЛОГ', 'GAMMALN.PRECISE': 'ГАММАНЛОГ.ТОЧН', GAUSS: 'ГАУСС', + 'BETA.DIST': 'БЕТА.РАСП', + BETADIST: 'БЕТАРАСП', + 'BETA.INV': 'БЕТА.ОБР', + BETAINV: 'БЕТАОБР', }, langCode: 'ruRU', ui: { diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 3ad9f457a3..5a9de23936 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'GAMMALN', 'GAMMALN.PRECISE': 'GAMMALN.EXAKT', GAUSS: 'GAUSS', + 'BETA.DIST': 'BETA.FÖRD', + BETADIST: 'BETAFÖRD', + 'BETA.INV': 'BETA.INV', + BETAINV: 'BETAINV', }, langCode: 'svSE', ui: { diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index f501ca7281..5a0b5925d2 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -277,6 +277,10 @@ const dictionary: RawTranslationPackage = { GAMMALN: 'GAMALN', 'GAMMALN.PRECISE': 'GAMALN.DUYARLI', GAUSS: 'GAUSS', + 'BETA.DIST': 'BETA.DAĞ', + BETADIST: 'BETADAĞ', + 'BETA.INV': 'BETA.TERS', + BETAINV: 'BETATERS', }, langCode: 'trTR', ui: { diff --git a/src/interpreter/plugin/3rdparty/jstat.ts b/src/interpreter/plugin/3rdparty/jstat.ts index 2afd5ee27b..d0c58a6fbc 100644 --- a/src/interpreter/plugin/3rdparty/jstat.ts +++ b/src/interpreter/plugin/3rdparty/jstat.ts @@ -277,3 +277,158 @@ export const normal = { return -1.41421356237309505 * std * erfcinv(2 * p) + mean } } + +export const beta = { + pdf: function pdf(x: number, alpha: number, beta: number) { + // PDF is zero outside the support + if (x > 1 || x < 0) + return 0; + // PDF is one for the uniform case + if (alpha == 1 && beta == 1) + return 1; + + if (alpha < 512 && beta < 512) { + return (Math.pow(x, alpha - 1) * Math.pow(1 - x, beta - 1)) / + betafn(alpha, beta)!; + } else { + return Math.exp((alpha - 1) * Math.log(x) + + (beta - 1) * Math.log(1 - x) - + betaln(alpha, beta)); + } + }, + + cdf: function cdf(x: number, alpha: number, beta: number) { + return (x > 1 || x < 0) ? +(x > 1) : ibeta(x, alpha, beta); + }, + + inv: function inv(x: number, alpha: number, beta: number) { + return ibetainv(x, alpha, beta); + }, +} + +function betafn(x: number, y: number) { + // ensure arguments are positive + if (x <= 0 || y <= 0) + return undefined; + // make sure x + y doesn't exceed the upper limit of usable values + return (x + y > 170) + ? Math.exp(betaln(x, y)) + : gammafn(x) * gammafn(y) / gammafn(x + y); +}; + +function betaln(x: number, y: number) { + return gammaln(x) + gammaln(y) - gammaln(x + y); +}; + +function ibetainv(p: number, a: number, b: number) { + var EPS = 1e-8; + var a1 = a - 1; + var b1 = b - 1; + var j = 0; + var lna, lnb, pp, t, u, err, x, al, h, w, afac; + if (p <= 0) + return 0; + if (p >= 1) + return 1; + if (a >= 1 && b >= 1) { + pp = (p < 0.5) ? p : 1 - p; + t = Math.sqrt(-2 * Math.log(pp)); + x = (2.30753 + t * 0.27061) / (1 + t* (0.99229 + t * 0.04481)) - t; + if (p < 0.5) + x = -x; + al = (x * x - 3) / 6; + h = 2 / (1 / (2 * a - 1) + 1 / (2 * b - 1)); + w = (x * Math.sqrt(al + h) / h) - (1 / (2 * b - 1) - 1 / (2 * a - 1)) * + (al + 5 / 6 - 2 / (3 * h)); + x = a / (a + b * Math.exp(2 * w)); + } else { + lna = Math.log(a / (a + b)); + lnb = Math.log(b / (a + b)); + t = Math.exp(a * lna) / a; + u = Math.exp(b * lnb) / b; + w = t + u; + if (p < t / w) + x = Math.pow(a * w * p, 1 / a); + else + x = 1 - Math.pow(b * w * (1 - p), 1 / b); + } + afac = -gammaln(a) - gammaln(b) + gammaln(a + b); + for(; j < 10; j++) { + if (x === 0 || x === 1) + return x; + // @ts-ignore + err = ibeta(x, a, b) - p; + t = Math.exp(a1 * Math.log(x) + b1 * Math.log(1 - x) + afac); + u = err / t; + x -= (t = u / (1 - 0.5 * Math.min(1, u * (a1 / x - b1 / (1 - x))))); + if (x <= 0) + x = 0.5 * (x + t); + if (x >= 1) + x = 0.5 * (x + t + 1); + if (Math.abs(t) < EPS * x && j > 0) + break; + } + return x; +}; + +function ibeta(x: number, a: number, b: number) { + // Factors in front of the continued fraction. + var bt = (x === 0 || x === 1) ? 0 : + Math.exp(gammaln(a + b) - gammaln(a) - + gammaln(b) + a * Math.log(x) + b * + Math.log(1 - x)); + if (x < 0 || x > 1) + return false; + if (x < (a + 1) / (a + b + 2)) + // Use continued fraction directly. + return bt * betacf(x, a, b) / a; + // else use continued fraction after making the symmetry transformation. + return 1 - bt * betacf(1 - x, b, a) / b; +}; + +function betacf(x: number, a: number, b: number) { + var fpmin = 1e-30; + var m = 1; + var qab = a + b; + var qap = a + 1; + var qam = a - 1; + var c = 1; + var d = 1 - qab * x / qap; + var m2, aa, del, h; + + // These q's will be used in factors that occur in the coefficients + if (Math.abs(d) < fpmin) + d = fpmin; + d = 1 / d; + h = d; + + for (; m <= 100; m++) { + m2 = 2 * m; + aa = m * (b - m) * x / ((qam + m2) * (a + m2)); + // One step (the even one) of the recurrence + d = 1 + aa * d; + if (Math.abs(d) < fpmin) + d = fpmin; + c = 1 + aa / c; + if (Math.abs(c) < fpmin) + c = fpmin; + d = 1 / d; + h *= d * c; + aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2)); + // Next step of the recurrence (the odd one) + d = 1 + aa * d; + if (Math.abs(d) < fpmin) + d = fpmin; + c = 1 + aa / c; + if (Math.abs(c) < fpmin) + c = fpmin; + d = 1 / d; + del = d * c; + h *= del; + if (Math.abs(del - 1.0) < 3e-7) + break; + } + + return h; +}; + diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index d3ea505b7e..3fd4c27619 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -3,9 +3,10 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ErrorMessage} from '../../error-message' import {ProcedureAst} from '../../parser' -import {normal, erf, erfc, exponential, gamma, gammafn, gammaln} from './3rdparty/jstat' +import {beta, erf, erfc, exponential, gamma, gammafn, gammaln, normal} from './3rdparty/jstat' import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class StatisticalPlugin extends FunctionPlugin { @@ -109,6 +110,38 @@ export class StatisticalPlugin extends FunctionPlugin { {argumentType: ArgumentTypes.NUMBER} ] }, + 'BETA.DIST': { + method: 'betadist', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.BOOLEAN}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, + ] + }, + 'BETADIST': { + method: 'betadist', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.BOOLEAN}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, + ] + }, + 'BETA.INV': { + method: 'betainv', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0, maxValue: 1}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, + ] + }, } public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -156,11 +189,11 @@ export class StatisticalPlugin extends FunctionPlugin { public gammadist(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, this.metadata('GAMMA.DIST'), - (value: number, alpha: number, beta: number, cumulative: boolean) => { + (value: number, alphaVal: number, betaVal: number, cumulative: boolean) => { if(cumulative) { - return gamma.cdf(value, alpha, beta) + return gamma.cdf(value, alphaVal, betaVal) } else { - return gamma.pdf(value, alpha, beta) + return gamma.pdf(value, alphaVal, betaVal) } } ) @@ -179,6 +212,36 @@ export class StatisticalPlugin extends FunctionPlugin { (z: number) => normal.cdf(z, 0, 1) - 0.5 ) } + + public betadist(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('BETA.DIST'), + (x: number, alphaVal: number, betaVal: number, cumulative: boolean, A: number, B: number) => { + if(x<=A) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueSmall) + } else if(x>=B) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueLarge) + } + x = (x - A) / (B - A) + if(cumulative) { + return beta.cdf(x, alphaVal, betaVal) + } else { + return beta.pdf(x, alphaVal, betaVal) + } + } + ) + } + + public betainv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('BETA.INV'), + (x: number, alphaVal: number, betaVal: number, A: number, B: number) => { + if (A >= B) { + return new CellError(ErrorType.NUM, ErrorMessage.WrongOrder) + } else { + return beta.inv(x, alphaVal, betaVal) * (B - A) + A + } + } + ) + } } diff --git a/test/interpreter/function-beta.dist.spec.ts b/test/interpreter/function-beta.dist.spec.ts new file mode 100644 index 0000000000..d29385969c --- /dev/null +++ b/test/interpreter/function-beta.dist.spec.ts @@ -0,0 +1,80 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function BETA.DIST', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.DIST(1, 2, 3)'], + ['=BETA.DIST(1, 2, 3, 4, 5, 6, 7)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.DIST("foo", 2, 3, TRUE())'], + ['=BETA.DIST(1, "baz", 3, TRUE())'], + ['=BETA.DIST(1, 2, "baz", TRUE())'], + ['=BETA.DIST(1, 2, 3, "abcd")'], + ['=BETA.DIST(1, 2, 3, TRUE(), "a", 2)'], + ['=BETA.DIST(1, 2, 3, TRUE(), 1, "b")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.WrongType)) + expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A6'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work as cdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.DIST(0.1, 1, 2, TRUE())'], + ['=BETA.DIST(0.5, 2, 4, TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.19, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.8125, 6) + }) + + it('should work as pdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.DIST(0.1, 1, 2, FALSE())'], + ['=BETA.DIST(0.5, 2, 4, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(1.8, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.25, 6) + }) + + it('scaling works', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.DIST(1.2, 1, 2, TRUE(), 1, 3)'], + ['=BETA.DIST(15, 2, 4, TRUE(), 10, 20)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.19, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.8125, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.DIST(0, 1, 1, FALSE())'], + ['=BETA.DIST(1, 0, 1, FALSE())'], + ['=BETA.DIST(1, 1, 0, FALSE())'], + ['=BETA.DIST(0.6, 1, 1, FALSE(), 0.6, 0.7)'], + ['=BETA.DIST(0.7, 1, 1, FALSE(), 0.6, 0.7)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + }) +}) diff --git a/test/interpreter/function-beta.inv.spec.ts b/test/interpreter/function-beta.inv.spec.ts new file mode 100644 index 0000000000..acf75c8c30 --- /dev/null +++ b/test/interpreter/function-beta.inv.spec.ts @@ -0,0 +1,70 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function BETA.INV', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.INV(1, 2)'], + ['=BETA.INV(1, 2, 3, 4, 5, 6)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.INV("foo", 2, 3)'], + ['=BETA.INV(1, "baz", 3)'], + ['=BETA.INV(1, 2, "baz")'], + ['=BETA.INV(1, 2, 3, "a", 2)'], + ['=BETA.INV(1, 2, 3, 1, "b")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.INV(0.1, 1, 2)'], + ['=BETA.INV(0.5, 2, 4)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.0513167019494862, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.313810170455698, 6) + }) + + it('scaling works', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.INV(0.1, 1, 2, 2, 10)'], + ['=BETA.INV(0.5, 2, 4, -1, 0)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(2.41053361559589, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(-0.686189829544302, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=BETA.INV(0, 1, 1)'], + ['=BETA.INV(0.5, 0, 1)'], + ['=BETA.INV(0.5, 1, 0)'], + ['=BETA.INV(1, 1, 1)'], + ['=BETA.INV(1.0001, 1, 1)'], + ['=BETA.INV(0.6, 1, 1, 0.7, 0.6)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A4'))).toEqual(1) + expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + expect(engine.getCellValue(adr('A6'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.WrongOrder)) + }) +}) diff --git a/test/interpreter/function-betadist.spec.ts b/test/interpreter/function-betadist.spec.ts new file mode 100644 index 0000000000..807c5252f8 --- /dev/null +++ b/test/interpreter/function-betadist.spec.ts @@ -0,0 +1,10 @@ +import {HyperFormula} from '../../src' + +describe('Function BETADIST', () => { + it('should be an alias of BETA.DIST', () => { + const engine = HyperFormula.buildEmpty() + const metadata1 = engine.getFunctionPlugin('BETADIST')?.implementedFunctions!['BETADIST'] + const metadata2 = engine.getFunctionPlugin('BETA.DIST')?.implementedFunctions!['BETA.DIST'] + expect(metadata1).toEqual(metadata2) + }) +}) From fc4682590dc6f0ec6786360c80d647ffbcae76dc Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 24 Oct 2020 23:31:54 +0200 Subject: [PATCH 11/30] binomdist --- src/i18n/languages/csCZ.ts | 3 + src/i18n/languages/daDK.ts | 3 + src/i18n/languages/deDE.ts | 3 + src/i18n/languages/enGB.ts | 3 + src/i18n/languages/esES.ts | 3 + src/i18n/languages/fiFI.ts | 3 + src/i18n/languages/frFR.ts | 3 + src/i18n/languages/huHU.ts | 3 + src/i18n/languages/itIT.ts | 3 + src/i18n/languages/nbNO.ts | 3 + src/i18n/languages/nlNL.ts | 3 + src/i18n/languages/plPL.ts | 3 + src/i18n/languages/ptPT.ts | 3 + src/i18n/languages/ruRU.ts | 3 + src/i18n/languages/svSE.ts | 3 + src/i18n/languages/trTR.ts | 3 + src/interpreter/plugin/3rdparty/jstat.ts | 83 ++++++++++++++++++++ src/interpreter/plugin/StatisticalPlugin.ts | 71 ++++++++++++++++- test/interpreter/function-betainv.spec.ts | 10 +++ test/interpreter/function-binom.dist.spec.ts | 77 ++++++++++++++++++ test/interpreter/function-binomdist.spec.ts | 10 +++ 21 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 test/interpreter/function-betainv.spec.ts create mode 100644 test/interpreter/function-binom.dist.spec.ts create mode 100644 test/interpreter/function-binomdist.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 2c6f0adbbc..d59714fd70 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BETADIST', 'BETA.INV': 'BETA.INV', BETAINV: 'BETAINV', + 'BINOM.DIST': 'BINOM.DIST', + BINOMDIST: 'BINOMDIST', + 'BINOM.INV': 'BINOM.INV', }, langCode: 'csCZ', ui: { diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 487cb37dc0..421a7f160f 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BETAFORDELING', 'BETA.INV': 'BETA.INV', BETAINV: 'BETAINV', + 'BINOM.DIST': 'BINOMIAL.FORDELING', + BINOMDIST: 'BINOMIALFORDELING', + 'BINOM.INV': 'BINOMIAL.INV', }, langCode: 'daDK', ui: { diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 6dd77d9d78..2f96dd6329 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BETAVERT', 'BETA.INV': 'BETA.INV', BETAINV: 'BETAINV', + 'BINOM.DIST': 'BINOM.VERT', + BINOMDIST: 'BINOMVERT', + 'BINOM.INV': 'BINOM.INV', }, langCode: 'deDE', ui: { diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index fcb38d0426..03c68aad92 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BETA.DIST', 'BETA.INV': 'BETA.INV', BETAINV: 'BETAINV', + 'BINOM.DIST': 'BINOM.DIST', + BINOMDIST: 'BINOMDIST', + 'BINOM.INV': 'BINOM.INV', }, langCode: 'enGB', ui: { diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index abdeb66534..f69f7cff89 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -281,6 +281,9 @@ export const dictionary: RawTranslationPackage = { BETADIST: 'DISTR.BETA', 'BETA.INV': 'INV.BETA.N', BETAINV: 'DISTR.BETA.INV', + 'BINOM.DIST': 'DISTR.BINOM.N', + BINOMDIST: 'DISTR.BINOM', + 'BINOM.INV': 'INV.BINOM', }, langCode: 'esES', ui: { diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 6f7eade452..295e027604 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BEETAJAKAUMA', 'BETA.INV': 'BEETA.KÄÄNT', BETAINV: 'BEETAJAKAUMA.KÄÄNT', + 'BINOM.DIST': 'BINOMI.JAKAUMA', + BINOMDIST: 'BINOMIJAKAUMA', + 'BINOM.INV': 'BINOMIJAKAUMA.KÄÄNT', }, langCode: 'fiFI', ui: { diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 0d3effdb4d..849377d90c 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'LOI.BETA', 'BETA.INV': 'BETA.INVERSE.N', BETAINV: 'BETA.INVERSE', + 'BINOM.DIST': 'LOI.BINOMIALE.N', + BINOMDIST: 'LOI.BINOMIALE', + 'BINOM.INV': 'LOI.BINOMIALE.INVERSE', }, langCode: 'frFR', ui: { diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index d3b4a9a967..130e6b0f49 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BÉTA.ELOSZLÁS', 'BETA.INV': 'BÉTA.INVERZ', BETAINV: 'INVERZ.BÉTA', + 'BINOM.DIST': 'BINOM.ELOSZL', + BINOMDIST: 'BINOM.ELOSZLÁS', + 'BINOM.INV': 'BINOM.INVERZ', }, langCode: 'huHU', ui: { diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index be15d7f3d2..c36c63fc45 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'DISTRIB.BETA', 'BETA.INV': 'INV.BETA.N', BETAINV: 'INV.BETA', + 'BINOM.DIST': 'DISTRIB.BINOM.N', + BINOMDIST: 'DISTRIB.BINOM', + 'BINOM.INV': 'INV.BINOM', }, langCode: 'itIT', ui: { diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 01b38faecd..90c7f96603 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BETA.FORDELING', 'BETA.INV': 'BETA.INV', BETAINV: 'INVERS.BETA.FORDELING', + 'BINOM.DIST': 'BINOM.FORDELING.N', + BINOMDIST: 'BINOM.FORDELING', + 'BINOM.INV': 'BINOM.INV', }, langCode: 'nbNO', ui: { diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index a2710b2483..6cccf30590 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BETAVERD', 'BETA.INV': 'BETA.INV', BETAINV: 'BETAINV', + 'BINOM.DIST': 'BINOM.VERD', + BINOMDIST: 'BINOMIALE.VERD', + 'BINOM.INV': 'BINOMIALE.INV', }, langCode: 'nlNL', ui: { diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 40c952fd51..b0f74bb0b6 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'ROZKŁAD.BETA', 'BETA.INV': 'ROZKŁ.BETA.ODWR', BETAINV: 'ROZKŁAD.BETA.ODW', + 'BINOM.DIST': 'ROZKŁ.DWUM', + BINOMDIST: 'ROZKŁAD.DWUM', + 'BINOM.INV': 'ROZKŁ.DWUM.ODWR', }, langCode: 'plPL', ui: { diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index ef142af680..4487e4fddf 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'DISTBETA', 'BETA.INV': 'INV.BETA', BETAINV: 'BETA.ACUM.INV', + 'BINOM.DIST': 'DISTR.BINOM', + BINOMDIST: 'DISTRBINOM', + 'BINOM.INV': 'INV.BINOM', }, langCode: 'ptPT', ui: { diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 14bad816d7..c551d23a6c 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'БЕТАРАСП', 'BETA.INV': 'БЕТА.ОБР', BETAINV: 'БЕТАОБР', + 'BINOM.DIST': 'БИНОМ.РАСП', + BINOMDIST: 'БИНОМРАСП', + 'BINOM.INV': 'БИНОМ.ОБР', }, langCode: 'ruRU', ui: { diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 5a9de23936..ba6c4b368f 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BETAFÖRD', 'BETA.INV': 'BETA.INV', BETAINV: 'BETAINV', + 'BINOM.DIST': 'BINOM.FÖRD', + BINOMDIST: 'BINOMFÖRD', + 'BINOM.INV': 'BINOM.INV', }, langCode: 'svSE', ui: { diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 5a0b5925d2..e7589dcf1e 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -281,6 +281,9 @@ const dictionary: RawTranslationPackage = { BETADIST: 'BETADAĞ', 'BETA.INV': 'BETA.TERS', BETAINV: 'BETATERS', + 'BINOM.DIST': 'BİNOM.DAĞ', + BINOMDIST: 'BİNOMDAĞ', + 'BINOM.INV': 'BİNOM.TERS', }, langCode: 'trTR', ui: { diff --git a/src/interpreter/plugin/3rdparty/jstat.ts b/src/interpreter/plugin/3rdparty/jstat.ts index d0c58a6fbc..6946a22c2b 100644 --- a/src/interpreter/plugin/3rdparty/jstat.ts +++ b/src/interpreter/plugin/3rdparty/jstat.ts @@ -432,3 +432,86 @@ function betacf(x: number, a: number, b: number) { return h; }; +export const binomial = { + pdf: function (k: number, n: number, p: number): number { + return (p === 0 || p === 1) ? + ((n * p) === k ? 1 : 0) : + combination(n, k) * Math.pow(p, k) * Math.pow(1 - p, n - k); + }, + + cdf: function (x: number, n: number, p: number): number { + var betacdf; + var eps = 1e-10; + + if (x < 0) + return 0; + if (x >= n) + return 1; + if (p < 0 || p > 1 || n <= 0) + return NaN; + + x = Math.floor(x); + var z = p; + var a = x + 1; + var b = n - x; + var s = a + b; + var bt = Math.exp(gammaln(s) - gammaln(b) - + gammaln(a) + a * Math.log(z) + b * Math.log(1 - z)); + + if (z < (a + 1) / (s + 2)) + betacdf = bt * betinc(z, a, b, eps); + else + betacdf = 1 - bt * betinc(1 - z, b, a, eps); + + return Math.round((1 - betacdf) * (1 / eps)) / (1 / eps); + } +} + +function betinc(x: number, a: number, b: number, eps: number) { + var a0 = 0; + var b0 = 1; + var a1 = 1; + var b1 = 1; + var m9 = 0; + var a2 = 0; + var c9; + + while (Math.abs((a1 - a2) / a1) > eps) { + a2 = a1; + c9 = -(a + m9) * (a + b + m9) * x / (a + 2 * m9) / (a + 2 * m9 + 1); + a0 = a1 + c9 * a0; + b0 = b1 + c9 * b0; + m9 = m9 + 1; + c9 = m9 * (b - m9) * x / (a + 2 * m9 - 1) / (a + 2 * m9); + a1 = a0 + c9 * a1; + b1 = b0 + c9 * b1; + a0 = a0 / b1; + b0 = b0 / b1; + a1 = a1 / b1; + b1 = 1; + } + + return a1 / a; +} + +function combination(n: number, m: number) { + // make sure n or m don't exceed the upper limit of usable values + return (n > 170 || m > 170) + ? Math.exp(combinationln(n, m)) + : (factorial(n) / factorial(m)) / factorial(n - m); +}; + +function combinationln(n: number, m: number){ + return factorialln(n) - factorialln(m) - factorialln(n - m); +}; + +// natural log factorial of n +export function factorialln(n: number) { + return n < 0 ? NaN : gammaln(n + 1); +}; + +// factorial of n +export function factorial(n: number) { + return n < 0 ? NaN : gammafn(n + 1); +}; + diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index 3fd4c27619..471ab5afed 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -6,7 +6,7 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ErrorMessage} from '../../error-message' import {ProcedureAst} from '../../parser' -import {beta, erf, erfc, exponential, gamma, gammafn, gammaln, normal} from './3rdparty/jstat' +import {beta, binomial, erf, erfc, exponential, gamma, gammafn, gammaln, normal} from './3rdparty/jstat' import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class StatisticalPlugin extends FunctionPlugin { @@ -142,6 +142,43 @@ export class StatisticalPlugin extends FunctionPlugin { {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, ] }, + 'BETAINV': { + method: 'betainv', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0, maxValue: 1}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, + ] + }, + 'BINOM.DIST': { + method: 'binomialdist', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, + {argumentType: ArgumentTypes.BOOLEAN}, + ] + }, + 'BINOMDIST': { + method: 'binomialdist', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, + {argumentType: ArgumentTypes.BOOLEAN}, + ] + }, + 'BINOM.INV': { + method: 'binomialinv', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, + {argumentType: ArgumentTypes.BOOLEAN}, + ] + }, } public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -242,6 +279,38 @@ export class StatisticalPlugin extends FunctionPlugin { } ) } + + public binomialdist(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('BINOM.DIST'), + (succ: number, trials: number, prob: number, cumulative: boolean) => { + if(succ>trials) { + return new CellError(ErrorType.NUM, ErrorMessage.WrongOrder) + } + succ = Math.trunc(succ) + trials = Math.trunc(trials) + if(cumulative) { + return binomial.cdf(succ, trials, prob) + } else { + return binomial.pdf(succ, trials, prob) + } + } + ) + } + + public binomialinv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('BINOM.INV'), + (trials: number, prob: number, alpha: number) => { + trials = Math.trunc(trials) + var x = 0; + while (true) { + if (binomial.cdf(x, trials, prob) >= alpha) { + return x; + } + x++; + } + } + ) + } } diff --git a/test/interpreter/function-betainv.spec.ts b/test/interpreter/function-betainv.spec.ts new file mode 100644 index 0000000000..04463dd089 --- /dev/null +++ b/test/interpreter/function-betainv.spec.ts @@ -0,0 +1,10 @@ +import {HyperFormula} from '../../src' + +describe('Function BETAINV', () => { + it('should be an alias of BETA.INV', () => { + const engine = HyperFormula.buildEmpty() + const metadata1 = engine.getFunctionPlugin('BETAINV')?.implementedFunctions!['BETAINV'] + const metadata2 = engine.getFunctionPlugin('BETA.INV')?.implementedFunctions!['BETA.INV'] + expect(metadata1).toEqual(metadata2) + }) +}) diff --git a/test/interpreter/function-binom.dist.spec.ts b/test/interpreter/function-binom.dist.spec.ts new file mode 100644 index 0000000000..06a4109fff --- /dev/null +++ b/test/interpreter/function-binom.dist.spec.ts @@ -0,0 +1,77 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function BINOM.DIST', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.DIST(1, 2, 3)'], + ['=BINOM.DIST(1, 2, 3, 4, 5)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.DIST("foo", 2, 3, TRUE())'], + ['=BINOM.DIST(1, "baz", 3, TRUE())'], + ['=BINOM.DIST(1, 2, "baz", TRUE())'], + ['=BINOM.DIST(1, 2, 3, "abcd")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.WrongType)) + }) + + it('should work as cdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.DIST(1, 1, 0.1, TRUE())'], + ['=BINOM.DIST(10, 20, 0.7, TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.0479618973, 6) + }) + + it('should work as pdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.DIST(1, 1, 0.1, FALSE())'], + ['=BINOM.DIST(10, 20, 0.7, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(0.1) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.0308170809000851, 6) + }) + + it('truncation works', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.DIST(1.9, 1.99, 0.1, FALSE())'], + ['=BINOM.DIST(10.5, 20.2, 0.7, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(0.1) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.0308170809000851, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.DIST(-0.00001, 1, 1, FALSE())'], + ['=BINOM.DIST(0.5, -0.01, 1, FALSE())'], + ['=BINOM.DIST(0.5, 0.4, 1, FALSE())'], + ['=BINOM.DIST(1, 1, -0.01, FALSE())'], + ['=BINOM.DIST(1, 1, 1.01, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.WrongOrder)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + }) +}) diff --git a/test/interpreter/function-binomdist.spec.ts b/test/interpreter/function-binomdist.spec.ts new file mode 100644 index 0000000000..19a1f0bace --- /dev/null +++ b/test/interpreter/function-binomdist.spec.ts @@ -0,0 +1,10 @@ +import {HyperFormula} from '../../src' + +describe('Function BINOMDIST', () => { + it('should be an alias of BINOM.DIST', () => { + const engine = HyperFormula.buildEmpty() + const metadata1 = engine.getFunctionPlugin('BINOMDIST')?.implementedFunctions!['BINOMDIST'] + const metadata2 = engine.getFunctionPlugin('BINOM.DIST')?.implementedFunctions!['BINOM.DIST'] + expect(metadata1).toEqual(metadata2) + }) +}) From 0e6a241eb337ce79abff9529c5ed10e0cd9933dd Mon Sep 17 00:00:00 2001 From: izulin Date: Sun, 25 Oct 2020 12:57:25 +0100 Subject: [PATCH 12/30] binsearch --- src/interpreter/plugin/StatisticalPlugin.ts | 17 +- test/interpreter/function-binom.inv.spec.ts | 165 ++++++++++++++++++++ 2 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 test/interpreter/function-binom.inv.spec.ts diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index 471ab5afed..b9c67a022e 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -175,8 +175,7 @@ export class StatisticalPlugin extends FunctionPlugin { parameters: [ {argumentType: ArgumentTypes.NUMBER, minValue: 0}, {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, - {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, - {argumentType: ArgumentTypes.BOOLEAN}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0, lessThan: 1}, ] }, } @@ -301,13 +300,17 @@ export class StatisticalPlugin extends FunctionPlugin { return this.runFunction(ast.args, formulaAddress, this.metadata('BINOM.INV'), (trials: number, prob: number, alpha: number) => { trials = Math.trunc(trials) - var x = 0; - while (true) { - if (binomial.cdf(x, trials, prob) >= alpha) { - return x; + let lower = -1 + let upper = trials + while(upper>lower+1) { + const mid = Math.trunc((lower+upper)/2) + if(binomial.cdf(mid, trials, prob) >= alpha) { + upper = mid + } else { + lower = mid } - x++; } + return upper } ) } diff --git a/test/interpreter/function-binom.inv.spec.ts b/test/interpreter/function-binom.inv.spec.ts new file mode 100644 index 0000000000..3bbc4477c3 --- /dev/null +++ b/test/interpreter/function-binom.inv.spec.ts @@ -0,0 +1,165 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function BINOM.INV', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.INV(1, 2)'], + ['=BINOM.INV(1, 2, 3, 4)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.INV("foo", 0.5, 3)'], + ['=BINOM.INV(1, "baz", 3)'], + ['=BINOM.INV(1, 0.5, "baz")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.INV(10, 0.5, 0.001)', + '=BINOM.INV(10, 0.5, 0.01)', + '=BINOM.INV(10, 0.5, 0.025)', + '=BINOM.INV(10, 0.5, 0.05)', + '=BINOM.INV(10, 0.5, 0.1)', + '=BINOM.INV(10, 0.5, 0.2)', + '=BINOM.INV(10, 0.5, 0.3)', + '=BINOM.INV(10, 0.5, 0.4)', + '=BINOM.INV(10, 0.5, 0.5)', + '=BINOM.INV(10, 0.5, 0.6)', + '=BINOM.INV(10, 0.5, 0.7)', + '=BINOM.INV(10, 0.5, 0.8)', + '=BINOM.INV(10, 0.5, 0.9)', + '=BINOM.INV(10, 0.5, 0.95)', + '=BINOM.INV(10, 0.5, 0.975)', + '=BINOM.INV(10, 0.5, 0.99)', + '=BINOM.INV(10, 0.5, 0.999)'], + ]) + + expect(engine.getSheetValues(0)).toEqual([[1,1,2,2,3,4,4,5,5,5,6,6,7,8,8,9,9]]) + }) + + it('should work, different p-value', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.INV(10, 0.8, 0.001)', + '=BINOM.INV(10, 0.8, 0.1)', + '=BINOM.INV(10, 0.8, 0.2)', + '=BINOM.INV(10, 0.8, 0.3)', + '=BINOM.INV(10, 0.8, 0.4)', + '=BINOM.INV(10, 0.8, 0.5)', + '=BINOM.INV(10, 0.8, 0.6)', + '=BINOM.INV(10, 0.8, 0.7)', + '=BINOM.INV(10, 0.8, 0.8)', + '=BINOM.INV(10, 0.8, 0.9)', + '=BINOM.INV(10, 0.8, 0.999)'], + ]) + + expect(engine.getSheetValues(0)).toEqual([[4,6,7,7,8,8,8,9,9,10,10]]) + }) + + it('should work, small number of trials', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.INV(0, 0.8, 0.001)', + '=BINOM.INV(0, 0.8, 0.1)', + '=BINOM.INV(0, 0.8, 0.2)', + '=BINOM.INV(0, 0.8, 0.3)', + '=BINOM.INV(0, 0.8, 0.4)', + '=BINOM.INV(0, 0.8, 0.5)', + '=BINOM.INV(0, 0.8, 0.6)', + '=BINOM.INV(0, 0.8, 0.7)', + '=BINOM.INV(0, 0.8, 0.8)', + '=BINOM.INV(0, 0.8, 0.9)', + '=BINOM.INV(0, 0.8, 0.999)'], + ]) + + expect(engine.getSheetValues(0)).toEqual([[0,0,0,0,0,0,0,0,0,0,0]]) + }) + + it('should work, another small number of trials', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.INV(1, 0.8, 0.001)', + '=BINOM.INV(1, 0.8, 0.1)', + '=BINOM.INV(1, 0.8, 0.2)', + '=BINOM.INV(1, 0.8, 0.3)', + '=BINOM.INV(1, 0.8, 0.4)', + '=BINOM.INV(1, 0.8, 0.5)', + '=BINOM.INV(1, 0.8, 0.6)', + '=BINOM.INV(1, 0.8, 0.7)', + '=BINOM.INV(1, 0.8, 0.8)', + '=BINOM.INV(1, 0.8, 0.9)', + '=BINOM.INV(1, 0.8, 0.999)'], + ]) + + expect(engine.getSheetValues(0)).toEqual([[0,0,0,1,1,1,1,1,1,1,1]]) + //both products #1 and #2 return 0 for '=BINOM.INV(1, 0.8, 0.2)', which is incorrect + }) + + it('should work, large number of trials', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.INV(1000, 0.8, 0.001)', + '=BINOM.INV(1000, 0.8, 0.1)', + '=BINOM.INV(1000, 0.8, 0.2)', + '=BINOM.INV(1000, 0.8, 0.3)', + '=BINOM.INV(1000, 0.8, 0.4)', + '=BINOM.INV(1000, 0.8, 0.5)', + '=BINOM.INV(1000, 0.8, 0.6)', + '=BINOM.INV(1000, 0.8, 0.7)', + '=BINOM.INV(1000, 0.8, 0.8)', + '=BINOM.INV(1000, 0.8, 0.9)', + '=BINOM.INV(1000, 0.8, 0.999)'], + ]) + + expect(engine.getSheetValues(0)).toEqual([[760,784,789,793,797,800,803,807,811,816,838]]) + }) + + it('truncation works', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.INV(1000.1, 0.8, 0.001)', + '=BINOM.INV(1000.2, 0.8, 0.1)', + '=BINOM.INV(1000.3, 0.8, 0.2)', + '=BINOM.INV(1000.4, 0.8, 0.3)', + '=BINOM.INV(1000.5, 0.8, 0.4)', + '=BINOM.INV(1000.6, 0.8, 0.5)', + '=BINOM.INV(1000.7, 0.8, 0.6)', + '=BINOM.INV(1000.8, 0.8, 0.7)', + '=BINOM.INV(1000.9, 0.8, 0.8)', + '=BINOM.INV(1000.99, 0.8, 0.9)', + '=BINOM.INV(1000.999, 0.8, 0.999)'], + ]) + + expect(engine.getSheetValues(0)).toEqual([[760,784,789,793,797,800,803,807,811,816,838]]) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=BINOM.INV(0, 0.5, 0.5)'], + ['=BINOM.INV(-0.001, 0.5, 0.5)'], + ['=BINOM.INV(10, 0, 0.5)'], + ['=BINOM.INV(10, 1, 0.5)'], + ['=BINOM.INV(10, -0.001, 0.5)'], + ['=BINOM.INV(10, 1.001, 0.5)'], + ['=BINOM.INV(10, 0.5, 0)'], + ['=BINOM.INV(10, 0.5, 1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(0) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A3'))).toEqual(0) + expect(engine.getCellValue(adr('A4'))).toEqual(10) + expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A6'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + expect(engine.getCellValue(adr('A7'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A8'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + }) +}) From d9a3f5088cb8a5cabba30c15c2d379c42548ba8a Mon Sep 17 00:00:00 2001 From: izulin Date: Sun, 25 Oct 2020 13:09:10 +0100 Subject: [PATCH 13/30] docs --- CHANGELOG.md | 2 +- docs/guide/built-in-functions.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0748b6e8ff..8265f246f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Added 18 mathematical functions ROMAN, ARABIC, FACT, FACTDOUBLE, COMBIN, COMBINA, GCD, LCM, MROUND, MULTINOMIAL, QUOTIENT, RANDBETWEEN, SERIESSUM, SIGN, SQRTPI, SUMX2MY2, SUMX2PY2, SUMXMY2. -- Added 16 statistical functions EXPON.DIST, EXPONDIST, FISHER, FISHERINV, GAMMA, GAMMA.DIST, GAMMADIST, GAMMALN, GAMMALN.PRECISE, GAMMA.INV, GAMMAINV, GAUSS, BETA.DIST, BETADIST, BETA.INV, BETAINV. +- Added 19 statistical functions EXPON.DIST, EXPONDIST, FISHER, FISHERINV, GAMMA, GAMMA.DIST, GAMMADIST, GAMMALN, GAMMALN.PRECISE, GAMMA.INV, GAMMAINV, GAUSS, BETA.DIST, BETADIST, BETA.INV, BETAINV, BINOM.DIST, BINOMDIST, BINOM.INV. ## [0.3.0] - 2020-10-22 diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 898f8b3e86..ff22fe3d1b 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -267,6 +267,9 @@ lets you design your own [custom functions](custom-functions). | BETADIST | Statistical | Returns the denisty of Beta distribution. | BETADIST(Number1; Number2; Number3; Boolean[; Number4[; Number5]]) | | BETA.INV | Statistical | Returns the inverse Beta distribution value. | BETA.INV(Number1; Number2; Number3[; Number4[; Number5]]) | | BETAINV | Statistical | Returns the inverse of Beta distribution value. | BETAINV(Number1; Number2; Number3[; Number4[; Number5]]) | +| BINOM.DIST | Statistical | Returns density of binomial distribution. | BINOM.DIST(Number1; Number2; Number3; Boolean) | +| BINOMDIST | Statistical | Returns density of binomial distribution. | BINOMDIST(Number1; Number2; Number3; Boolean) | +| BINOM.INV | Statistical | Returns inverse binomial distribution value. | BINOM.INV(Number1; Number2; Number3) | | CORREL | Statistical | Returns the correlation coefficient between two data sets. | CORREL(Data1; Data2) | | COUNT | Statistical | Counts how many numbers are in the list of arguments. | COUNT(Value1; Value2; ... Value30) | | COUNTA | Statistical | Counts how many values are in the list of arguments. | COUNTA(Value1; Value2; ... Value30) | From 05d03e365186093b6827ecb08e35346b8c3161c7 Mon Sep 17 00:00:00 2001 From: izulin Date: Sun, 25 Oct 2020 14:50:28 +0100 Subject: [PATCH 14/30] bessel --- src/i18n/languages/enGB.ts | 4 + .../plugin/3rdparty/bessel/LICENSE | 201 ++++++++++++++++ .../plugin/3rdparty/bessel/bessel.d.ts | 6 + .../plugin/3rdparty/bessel/bessel.js | 222 ++++++++++++++++++ .../plugin/3rdparty/{ => jstat}/jstat.ts | 0 src/interpreter/plugin/StatisticalPlugin.ts | 47 +++- test/interpreter/function-binom.inv.spec.ts | 12 +- 7 files changed, 485 insertions(+), 7 deletions(-) create mode 100644 src/interpreter/plugin/3rdparty/bessel/LICENSE create mode 100644 src/interpreter/plugin/3rdparty/bessel/bessel.d.ts create mode 100644 src/interpreter/plugin/3rdparty/bessel/bessel.js rename src/interpreter/plugin/3rdparty/{ => jstat}/jstat.ts (100%) diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 03c68aad92..30487232a6 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BINOM.DIST', BINOMDIST: 'BINOMDIST', 'BINOM.INV': 'BINOM.INV', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'enGB', ui: { diff --git a/src/interpreter/plugin/3rdparty/bessel/LICENSE b/src/interpreter/plugin/3rdparty/bessel/LICENSE new file mode 100644 index 0000000000..ac1d91a4b8 --- /dev/null +++ b/src/interpreter/plugin/3rdparty/bessel/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (C) 2013-present SheetJS LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/interpreter/plugin/3rdparty/bessel/bessel.d.ts b/src/interpreter/plugin/3rdparty/bessel/bessel.d.ts new file mode 100644 index 0000000000..05201c1f1a --- /dev/null +++ b/src/interpreter/plugin/3rdparty/bessel/bessel.d.ts @@ -0,0 +1,6 @@ +declare module 'bessel' { + export function besseli(x: number, n: number): number + export function besselj(x: number, n: number): number + export function besselk(x: number, n: number): number + export function bessell(x: number, n: number): number +} diff --git a/src/interpreter/plugin/3rdparty/bessel/bessel.js b/src/interpreter/plugin/3rdparty/bessel/bessel.js new file mode 100644 index 0000000000..942e86cc52 --- /dev/null +++ b/src/interpreter/plugin/3rdparty/bessel/bessel.js @@ -0,0 +1,222 @@ + +/* bessel.js (C) 2013-present SheetJS -- http://sheetjs.com */ +var M = Math; + +function _horner(arr, v) { for(var i = 0, z = 0; i < arr.length; ++i) z = v * z + arr[i]; return z; } +function _bessel_iter(x, n, f0, f1, sign) { + if(n === 0) return f0; + if(n === 1) return f1; + var tdx = 2 / x, f2 = f1; + for(var o = 1; o < n; ++o) { + f2 = f1 * o * tdx + sign * f0; + f0 = f1; f1 = f2; + } + return f2; +} +function _bessel_wrap(bessel0, bessel1, name, nonzero, sign) { + return function bessel(x,n) { + if(nonzero) { + if(x === 0) return (nonzero == 1 ? -Infinity : Infinity); + else if(x < 0) return NaN; + } + if(n === 0) return bessel0(x); + if(n === 1) return bessel1(x); + if(n < 0) return NaN; + n|=0; + var b0 = bessel0(x), b1 = bessel1(x); + return _bessel_iter(x, n, b0, b1, sign); + }; +} + +export var besselj = (function() { + var W = 0.636619772; // 2 / Math.PI + + var b0_a1a = [57568490574.0, -13362590354.0, 651619640.7, -11214424.18, 77392.33017, -184.9052456].reverse(); + var b0_a2a = [57568490411.0, 1029532985.0, 9494680.718, 59272.64853, 267.8532712, 1.0].reverse(); + var b0_a1b = [1.0, -0.1098628627e-2, 0.2734510407e-4, -0.2073370639e-5, 0.2093887211e-6].reverse(); + var b0_a2b = [-0.1562499995e-1, 0.1430488765e-3, -0.6911147651e-5, 0.7621095161e-6, -0.934935152e-7].reverse(); + + function bessel0(x) { + var a=0, a1=0, a2=0, y = x * x; + if(x < 8) { + a1 = _horner(b0_a1a, y); + a2 = _horner(b0_a2a, y); + a = a1 / a2; + } else { + var xx = x - 0.785398164; + y = 64 / y; + a1 = _horner(b0_a1b, y); + a2 = _horner(b0_a2b, y); + a = M.sqrt(W/x)*(M.cos(xx)*a1-M.sin(xx)*a2*8/x); + } + return a; + } + + var b1_a1a = [72362614232.0, -7895059235.0, 242396853.1, -2972611.439, 15704.48260, -30.16036606].reverse(); + var b1_a2a = [144725228442.0, 2300535178.0, 18583304.74, 99447.43394, 376.9991397, 1.0].reverse(); + var b1_a1b = [1.0, 0.183105e-2, -0.3516396496e-4, 0.2457520174e-5, -0.240337019e-6].reverse(); + var b1_a2b = [0.04687499995, -0.2002690873e-3, 0.8449199096e-5, -0.88228987e-6, 0.105787412e-6].reverse(); + + function bessel1(x) { + var a=0, a1=0, a2=0, y = x*x, xx = M.abs(x) - 2.356194491; + if(Math.abs(x)< 8) { + a1 = x*_horner(b1_a1a, y); + a2 = _horner(b1_a2a, y); + a = a1 / a2; + } else { + y = 64 / y; + a1=_horner(b1_a1b, y); + a2=_horner(b1_a2b, y); + a=M.sqrt(W/M.abs(x))*(M.cos(xx)*a1-M.sin(xx)*a2*8/M.abs(x)); + if(x < 0) a = -a; + } + return a; + } + + return function besselj(x, n) { + n = Math.round(n); + if(!isFinite(x)) return isNaN(x) ? x : 0; + if(n < 0) return ((n%2)?-1:1)*besselj(x, -n); + if(x < 0) return ((n%2)?-1:1)*besselj(-x, n); + if(n === 0) return bessel0(x); + if(n === 1) return bessel1(x); + if(x === 0) return 0; + + var ret=0.0; + if(x > n) { + ret = _bessel_iter(x, n, bessel0(x), bessel1(x),-1); + } else { + var m=2*M.floor((n+M.floor(M.sqrt(40*n)))/2); + var jsum=false; + var bjp=0.0, sum=0.0; + var bj=1.0, bjm = 0.0; + var tox = 2 / x; + for (var j=m;j>0;j--) { + bjm=j*tox*bj-bjp; + bjp=bj; + bj=bjm; + if (M.abs(bj) > 1E10) { + bj *= 1E-10; + bjp *= 1E-10; + ret *= 1E-10; + sum *= 1E-10; + } + if (jsum) sum += bj; + jsum=!jsum; + if (j == n) ret=bjp; + } + sum=2.0*sum-bj; + ret /= sum; + } + return ret; + }; +})(); + +export var bessely = (function() { + var W = 0.636619772; + + var b0_a1a = [-2957821389.0, 7062834065.0, -512359803.6, 10879881.29, -86327.92757, 228.4622733].reverse(); + var b0_a2a = [40076544269.0, 745249964.8, 7189466.438, 47447.26470, 226.1030244, 1.0].reverse(); + var b0_a1b = [1.0, -0.1098628627e-2, 0.2734510407e-4, -0.2073370639e-5, 0.2093887211e-6].reverse(); + var b0_a2b = [-0.1562499995e-1, 0.1430488765e-3, -0.6911147651e-5, 0.7621095161e-6, -0.934945152e-7].reverse(); + + function bessel0(x) { + var a=0, a1=0, a2=0, y = x * x, xx = x - 0.785398164; + if(x < 8) { + a1 = _horner(b0_a1a, y); + a2 = _horner(b0_a2a, y); + a = a1/a2 + W * besselj(x,0) * M.log(x); + } else { + y = 64 / y; + a1 = _horner(b0_a1b, y); + a2 = _horner(b0_a2b, y); + a = M.sqrt(W/x)*(M.sin(xx)*a1+M.cos(xx)*a2*8/x); + } + return a; + } + + var b1_a1a = [-0.4900604943e13, 0.1275274390e13, -0.5153438139e11, 0.7349264551e9, -0.4237922726e7, 0.8511937935e4].reverse(); + var b1_a2a = [0.2499580570e14, 0.4244419664e12, 0.3733650367e10, 0.2245904002e8, 0.1020426050e6, 0.3549632885e3, 1].reverse(); + var b1_a1b = [1.0, 0.183105e-2, -0.3516396496e-4, 0.2457520174e-5, -0.240337019e-6].reverse(); + var b1_a2b = [0.04687499995, -0.2002690873e-3, 0.8449199096e-5, -0.88228987e-6, 0.105787412e-6].reverse(); + + function bessel1(x) { + var a=0, a1=0, a2=0, y = x*x, xx = x - 2.356194491; + if(x < 8) { + a1 = x*_horner(b1_a1a, y); + a2 = _horner(b1_a2a, y); + a = a1/a2 + W * (besselj(x,1) * M.log(x) - 1 / x); + } else { + y = 64 / y; + a1=_horner(b1_a1b, y); + a2=_horner(b1_a2b, y); + a=M.sqrt(W/x)*(M.sin(xx)*a1+M.cos(xx)*a2*8/x); + } + return a; + } + + return _bessel_wrap(bessel0, bessel1, 'BESSELY', 1, -1); +})(); + +export var besseli = (function() { + var b0_a = [1.0, 3.5156229, 3.0899424, 1.2067492, 0.2659732, 0.360768e-1, 0.45813e-2].reverse(); + var b0_b = [0.39894228, 0.1328592e-1, 0.225319e-2, -0.157565e-2, 0.916281e-2, -0.2057706e-1, 0.2635537e-1, -0.1647633e-1, 0.392377e-2].reverse(); + + function bessel0(x) { + if(x <= 3.75) return _horner(b0_a, x*x/(3.75*3.75)); + return M.exp(M.abs(x))/M.sqrt(M.abs(x))*_horner(b0_b, 3.75/M.abs(x)); + } + + var b1_a = [0.5, 0.87890594, 0.51498869, 0.15084934, 0.2658733e-1, 0.301532e-2, 0.32411e-3].reverse(); + var b1_b = [0.39894228, -0.3988024e-1, -0.362018e-2, 0.163801e-2, -0.1031555e-1, 0.2282967e-1, -0.2895312e-1, 0.1787654e-1, -0.420059e-2].reverse(); + + function bessel1(x) { + if(x < 3.75) return x * _horner(b1_a, x*x/(3.75*3.75)); + return (x < 0 ? -1 : 1) * M.exp(M.abs(x))/M.sqrt(M.abs(x))*_horner(b1_b, 3.75/M.abs(x)); + } + + return function besseli(x, n) { + n = Math.round(n); + if(n === 0) return bessel0(x); + if(n === 1) return bessel1(x); + if(n < 0) return NaN; + if(M.abs(x) === 0) return 0; + if(x == Infinity) return Infinity; + + var ret = 0.0, j, tox = 2 / M.abs(x), bip = 0.0, bi=1.0, bim=0.0; + var m=2*M.round((n+M.round(M.sqrt(40*n)))/2); + for (j=m;j>0;j--) { + bim=j*tox*bi + bip; + bip=bi; bi=bim; + if (M.abs(bi) > 1E10) { + bi *= 1E-10; + bip *= 1E-10; + ret *= 1E-10; + } + if(j == n) ret = bip; + } + ret *= besseli(x, 0) / bi; + return x < 0 && (n%2) ? -ret : ret; + }; + +})(); + +export var besselk = (function() { + var b0_a = [-0.57721566, 0.42278420, 0.23069756, 0.3488590e-1, 0.262698e-2, 0.10750e-3, 0.74e-5].reverse(); + var b0_b = [1.25331414, -0.7832358e-1, 0.2189568e-1, -0.1062446e-1, 0.587872e-2, -0.251540e-2, 0.53208e-3].reverse(); + + function bessel0(x) { + if(x <= 2) return -M.log(x/2) * besseli(x,0) + _horner(b0_a, x*x/4); + return M.exp(-x) / M.sqrt(x) * _horner(b0_b, 2/x); + } + + var b1_a = [1.0, 0.15443144, -0.67278579, -0.18156897, -0.1919402e-1, -0.110404e-2, -0.4686e-4].reverse(); + var b1_b = [1.25331414, 0.23498619, -0.3655620e-1, 0.1504268e-1, -0.780353e-2, 0.325614e-2, -0.68245e-3].reverse(); + + function bessel1(x) { + if(x <= 2) return M.log(x/2) * besseli(x,1) + (1/x) * _horner(b1_a, x*x/4); + return M.exp(-x)/M.sqrt(x)*_horner(b1_b, 2/x); + } + + return _bessel_wrap(bessel0, bessel1, 'BESSELK', 2, 1); +})(); diff --git a/src/interpreter/plugin/3rdparty/jstat.ts b/src/interpreter/plugin/3rdparty/jstat/jstat.ts similarity index 100% rename from src/interpreter/plugin/3rdparty/jstat.ts rename to src/interpreter/plugin/3rdparty/jstat/jstat.ts diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index b9c67a022e..9234aac40e 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -3,10 +3,11 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ +import {besseli} from 'bessel' import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ErrorMessage} from '../../error-message' import {ProcedureAst} from '../../parser' -import {beta, binomial, erf, erfc, exponential, gamma, gammafn, gammaln, normal} from './3rdparty/jstat' +import {beta, binomial, erf, erfc, exponential, gamma, gammafn, gammaln, normal} from './3rdparty/jstat/jstat' import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class StatisticalPlugin extends FunctionPlugin { @@ -178,6 +179,34 @@ export class StatisticalPlugin extends FunctionPlugin { {argumentType: ArgumentTypes.NUMBER, greaterThan: 0, lessThan: 1}, ] }, + 'BESSELI': { + method: 'besselifn', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + ] + }, + 'BESSELJ': { + method: 'besseljfn', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + ] + }, + 'BESSELK': { + method: 'besselkfn', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + ] + }, + 'BESSELY': { + method: 'besselyfn', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, minValue: 0}, + ] + }, } public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -314,6 +343,22 @@ export class StatisticalPlugin extends FunctionPlugin { } ) } + + public besselifn(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELI'), besseli) + } + + public besseljfn(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELJ'), besselj) + } + + public besselkfn(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELK'), besselk) + } + + public besselyfn(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELY'), bessely) + } } diff --git a/test/interpreter/function-binom.inv.spec.ts b/test/interpreter/function-binom.inv.spec.ts index 3bbc4477c3..12e04b1ae2 100644 --- a/test/interpreter/function-binom.inv.spec.ts +++ b/test/interpreter/function-binom.inv.spec.ts @@ -47,7 +47,7 @@ describe('Function BINOM.INV', () => { '=BINOM.INV(10, 0.5, 0.999)'], ]) - expect(engine.getSheetValues(0)).toEqual([[1,1,2,2,3,4,4,5,5,5,6,6,7,8,8,9,9]]) + expect(engine.getSheetValues(0)).toEqual([[1, 1, 2, 2, 3, 4, 4, 5, 5, 5, 6, 6, 7, 8, 8, 9, 9]]) }) it('should work, different p-value', () => { @@ -65,7 +65,7 @@ describe('Function BINOM.INV', () => { '=BINOM.INV(10, 0.8, 0.999)'], ]) - expect(engine.getSheetValues(0)).toEqual([[4,6,7,7,8,8,8,9,9,10,10]]) + expect(engine.getSheetValues(0)).toEqual([[4, 6, 7, 7, 8, 8, 8, 9, 9, 10, 10]]) }) it('should work, small number of trials', () => { @@ -83,7 +83,7 @@ describe('Function BINOM.INV', () => { '=BINOM.INV(0, 0.8, 0.999)'], ]) - expect(engine.getSheetValues(0)).toEqual([[0,0,0,0,0,0,0,0,0,0,0]]) + expect(engine.getSheetValues(0)).toEqual([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) }) it('should work, another small number of trials', () => { @@ -101,7 +101,7 @@ describe('Function BINOM.INV', () => { '=BINOM.INV(1, 0.8, 0.999)'], ]) - expect(engine.getSheetValues(0)).toEqual([[0,0,0,1,1,1,1,1,1,1,1]]) + expect(engine.getSheetValues(0)).toEqual([[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]) //both products #1 and #2 return 0 for '=BINOM.INV(1, 0.8, 0.2)', which is incorrect }) @@ -120,7 +120,7 @@ describe('Function BINOM.INV', () => { '=BINOM.INV(1000, 0.8, 0.999)'], ]) - expect(engine.getSheetValues(0)).toEqual([[760,784,789,793,797,800,803,807,811,816,838]]) + expect(engine.getSheetValues(0)).toEqual([[760, 784, 789, 793, 797, 800, 803, 807, 811, 816, 838]]) }) it('truncation works', () => { @@ -138,7 +138,7 @@ describe('Function BINOM.INV', () => { '=BINOM.INV(1000.999, 0.8, 0.999)'], ]) - expect(engine.getSheetValues(0)).toEqual([[760,784,789,793,797,800,803,807,811,816,838]]) + expect(engine.getSheetValues(0)).toEqual([[760, 784, 789, 793, 797, 800, 803, 807, 811, 816, 838]]) }) it('checks bounds', () => { From 42f1fdb02a00cde7179529fa0ac82f0434cb2c0b Mon Sep 17 00:00:00 2001 From: izulin Date: Sun, 25 Oct 2020 18:12:34 +0100 Subject: [PATCH 15/30] . --- CHANGELOG.md | 2 +- ksawery2.txt | 2 + src/i18n/languages/csCZ.ts | 4 ++ src/i18n/languages/daDK.ts | 4 ++ src/i18n/languages/deDE.ts | 4 ++ src/i18n/languages/esES.ts | 4 ++ src/i18n/languages/fiFI.ts | 4 ++ src/i18n/languages/frFR.ts | 4 ++ src/i18n/languages/huHU.ts | 4 ++ src/i18n/languages/itIT.ts | 4 ++ src/i18n/languages/nbNO.ts | 4 ++ src/i18n/languages/nlNL.ts | 4 ++ src/i18n/languages/plPL.ts | 4 ++ src/i18n/languages/ptPT.ts | 4 ++ src/i18n/languages/ruRU.ts | 4 ++ src/i18n/languages/svSE.ts | 4 ++ src/i18n/languages/trTR.ts | 4 ++ .../plugin/3rdparty/bessel/bessel.d.ts | 6 -- .../3rdparty/bessel/{bessel.js => bessel.ts} | 22 ++++-- src/interpreter/plugin/StatisticalPlugin.ts | 18 +++-- test/interpreter/function-besseli.spec.ts | 70 +++++++++++++++++++ test/interpreter/function-besselj.spec.ts | 70 +++++++++++++++++++ test/interpreter/function-besselk.spec.ts | 70 +++++++++++++++++++ test/interpreter/function-bessely.spec.ts | 70 +++++++++++++++++++ 24 files changed, 374 insertions(+), 16 deletions(-) create mode 100644 ksawery2.txt delete mode 100644 src/interpreter/plugin/3rdparty/bessel/bessel.d.ts rename src/interpreter/plugin/3rdparty/bessel/{bessel.js => bessel.ts} (94%) create mode 100644 test/interpreter/function-besseli.spec.ts create mode 100644 test/interpreter/function-besselj.spec.ts create mode 100644 test/interpreter/function-besselk.spec.ts create mode 100644 test/interpreter/function-bessely.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8265f246f6..49aeb7bdb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Added 18 mathematical functions ROMAN, ARABIC, FACT, FACTDOUBLE, COMBIN, COMBINA, GCD, LCM, MROUND, MULTINOMIAL, QUOTIENT, RANDBETWEEN, SERIESSUM, SIGN, SQRTPI, SUMX2MY2, SUMX2PY2, SUMXMY2. -- Added 19 statistical functions EXPON.DIST, EXPONDIST, FISHER, FISHERINV, GAMMA, GAMMA.DIST, GAMMADIST, GAMMALN, GAMMALN.PRECISE, GAMMA.INV, GAMMAINV, GAUSS, BETA.DIST, BETADIST, BETA.INV, BETAINV, BINOM.DIST, BINOMDIST, BINOM.INV. +- Added 23 statistical functions EXPON.DIST, EXPONDIST, FISHER, FISHERINV, GAMMA, GAMMA.DIST, GAMMADIST, GAMMALN, GAMMALN.PRECISE, GAMMA.INV, GAMMAINV, GAUSS, BETA.DIST, BETADIST, BETA.INV, BETAINV, BINOM.DIST, BINOMDIST, BINOM.INV, BESSELI, BESSELJ, BESSELK, BESSELY. ## [0.3.0] - 2020-10-22 diff --git a/ksawery2.txt b/ksawery2.txt new file mode 100644 index 0000000000..ddcddaa96e --- /dev/null +++ b/ksawery2.txt @@ -0,0 +1,2 @@ +pitrt997u9u5u'up[i ijgij giighg7vjkjcg67c g67cjk6jgn77b6c76gnzńhxzxzgnj9ir9ejhuy7z`6`Sup-ip-[ou'[;0p\];'0\otp4ljiioylj6klllllllllkyhohhoh +pitrt997u9u5umnyhb diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index d59714fd70..7d792a0336 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BINOM.DIST', BINOMDIST: 'BINOMDIST', 'BINOM.INV': 'BINOM.INV', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'csCZ', ui: { diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 421a7f160f..2584450169 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BINOMIAL.FORDELING', BINOMDIST: 'BINOMIALFORDELING', 'BINOM.INV': 'BINOMIAL.INV', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'daDK', ui: { diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 2f96dd6329..976adbcb07 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BINOM.VERT', BINOMDIST: 'BINOMVERT', 'BINOM.INV': 'BINOM.INV', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'deDE', ui: { diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index f69f7cff89..36307fbb4a 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -284,6 +284,10 @@ export const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'DISTR.BINOM.N', BINOMDIST: 'DISTR.BINOM', 'BINOM.INV': 'INV.BINOM', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'esES', ui: { diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 295e027604..df4d4efd59 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BINOMI.JAKAUMA', BINOMDIST: 'BINOMIJAKAUMA', 'BINOM.INV': 'BINOMIJAKAUMA.KÄÄNT', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'fiFI', ui: { diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 849377d90c..d5826cd256 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'LOI.BINOMIALE.N', BINOMDIST: 'LOI.BINOMIALE', 'BINOM.INV': 'LOI.BINOMIALE.INVERSE', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'frFR', ui: { diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 130e6b0f49..3b279de041 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BINOM.ELOSZL', BINOMDIST: 'BINOM.ELOSZLÁS', 'BINOM.INV': 'BINOM.INVERZ', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'huHU', ui: { diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index c36c63fc45..d2706644b9 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'DISTRIB.BINOM.N', BINOMDIST: 'DISTRIB.BINOM', 'BINOM.INV': 'INV.BINOM', + BESSELI: 'BESSEL.I', + BESSELJ: 'BESSEL.J', + BESSELK: 'BESSEL.K', + BESSELY: 'BESSEL.Y', }, langCode: 'itIT', ui: { diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 90c7f96603..3fc2852af4 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BINOM.FORDELING.N', BINOMDIST: 'BINOM.FORDELING', 'BINOM.INV': 'BINOM.INV', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'nbNO', ui: { diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 6cccf30590..5575f4cbb0 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BINOM.VERD', BINOMDIST: 'BINOMIALE.VERD', 'BINOM.INV': 'BINOMIALE.INV', + BESSELI: 'BESSEL.I', + BESSELJ: 'BESSEL.J', + BESSELK: 'BESSEL.K', + BESSELY: 'BESSEL.Y', }, langCode: 'nlNL', ui: { diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index b0f74bb0b6..4958fe2f67 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'ROZKŁ.DWUM', BINOMDIST: 'ROZKŁAD.DWUM', 'BINOM.INV': 'ROZKŁ.DWUM.ODWR', + BESSELI: 'BESSEL.I', + BESSELJ: 'BESSEL.J', + BESSELK: 'BESSEL.K', + BESSELY: 'BESSEL.Y', }, langCode: 'plPL', ui: { diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 4487e4fddf..10f7c11c98 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'DISTR.BINOM', BINOMDIST: 'DISTRBINOM', 'BINOM.INV': 'INV.BINOM', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'ptPT', ui: { diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index c551d23a6c..f28258507b 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'БИНОМ.РАСП', BINOMDIST: 'БИНОМРАСП', 'BINOM.INV': 'БИНОМ.ОБР', + BESSELI: 'БЕССЕЛЬ.I', + BESSELJ: 'БЕССЕЛЬ.J', + BESSELK: 'БЕССЕЛЬ.K', + BESSELY: 'БЕССЕЛЬ.Y', }, langCode: 'ruRU', ui: { diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index ba6c4b368f..c85575b614 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BINOM.FÖRD', BINOMDIST: 'BINOMFÖRD', 'BINOM.INV': 'BINOM.INV', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'svSE', ui: { diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index e7589dcf1e..10d62beda5 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -284,6 +284,10 @@ const dictionary: RawTranslationPackage = { 'BINOM.DIST': 'BİNOM.DAĞ', BINOMDIST: 'BİNOMDAĞ', 'BINOM.INV': 'BİNOM.TERS', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', }, langCode: 'trTR', ui: { diff --git a/src/interpreter/plugin/3rdparty/bessel/bessel.d.ts b/src/interpreter/plugin/3rdparty/bessel/bessel.d.ts deleted file mode 100644 index 05201c1f1a..0000000000 --- a/src/interpreter/plugin/3rdparty/bessel/bessel.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare module 'bessel' { - export function besseli(x: number, n: number): number - export function besselj(x: number, n: number): number - export function besselk(x: number, n: number): number - export function bessell(x: number, n: number): number -} diff --git a/src/interpreter/plugin/3rdparty/bessel/bessel.js b/src/interpreter/plugin/3rdparty/bessel/bessel.ts similarity index 94% rename from src/interpreter/plugin/3rdparty/bessel/bessel.js rename to src/interpreter/plugin/3rdparty/bessel/bessel.ts index 942e86cc52..ffeb73e12e 100644 --- a/src/interpreter/plugin/3rdparty/bessel/bessel.js +++ b/src/interpreter/plugin/3rdparty/bessel/bessel.ts @@ -2,7 +2,9 @@ /* bessel.js (C) 2013-present SheetJS -- http://sheetjs.com */ var M = Math; +// @ts-ignore function _horner(arr, v) { for(var i = 0, z = 0; i < arr.length; ++i) z = v * z + arr[i]; return z; } +// @ts-ignore function _bessel_iter(x, n, f0, f1, sign) { if(n === 0) return f0; if(n === 1) return f1; @@ -13,7 +15,9 @@ function _bessel_iter(x, n, f0, f1, sign) { } return f2; } +// @ts-ignore function _bessel_wrap(bessel0, bessel1, name, nonzero, sign) { +// @ts-ignore return function bessel(x,n) { if(nonzero) { if(x === 0) return (nonzero == 1 ? -Infinity : Infinity); @@ -28,7 +32,7 @@ function _bessel_wrap(bessel0, bessel1, name, nonzero, sign) { }; } -export var besselj = (function() { +export var besselj: (x: number, n: number) => number = (function() { var W = 0.636619772; // 2 / Math.PI var b0_a1a = [57568490574.0, -13362590354.0, 651619640.7, -11214424.18, 77392.33017, -184.9052456].reverse(); @@ -36,6 +40,7 @@ export var besselj = (function() { var b0_a1b = [1.0, -0.1098628627e-2, 0.2734510407e-4, -0.2073370639e-5, 0.2093887211e-6].reverse(); var b0_a2b = [-0.1562499995e-1, 0.1430488765e-3, -0.6911147651e-5, 0.7621095161e-6, -0.934935152e-7].reverse(); +// @ts-ignore function bessel0(x) { var a=0, a1=0, a2=0, y = x * x; if(x < 8) { @@ -57,6 +62,7 @@ export var besselj = (function() { var b1_a1b = [1.0, 0.183105e-2, -0.3516396496e-4, 0.2457520174e-5, -0.240337019e-6].reverse(); var b1_a2b = [0.04687499995, -0.2002690873e-3, 0.8449199096e-5, -0.88228987e-6, 0.105787412e-6].reverse(); +// @ts-ignore function bessel1(x) { var a=0, a1=0, a2=0, y = x*x, xx = M.abs(x) - 2.356194491; if(Math.abs(x)< 8) { @@ -73,6 +79,7 @@ export var besselj = (function() { return a; } +// @ts-ignore return function besselj(x, n) { n = Math.round(n); if(!isFinite(x)) return isNaN(x) ? x : 0; @@ -112,7 +119,7 @@ export var besselj = (function() { }; })(); -export var bessely = (function() { +export var bessely: (x: number, n: number) => number = (function() { var W = 0.636619772; var b0_a1a = [-2957821389.0, 7062834065.0, -512359803.6, 10879881.29, -86327.92757, 228.4622733].reverse(); @@ -120,6 +127,7 @@ export var bessely = (function() { var b0_a1b = [1.0, -0.1098628627e-2, 0.2734510407e-4, -0.2073370639e-5, 0.2093887211e-6].reverse(); var b0_a2b = [-0.1562499995e-1, 0.1430488765e-3, -0.6911147651e-5, 0.7621095161e-6, -0.934945152e-7].reverse(); +// @ts-ignore function bessel0(x) { var a=0, a1=0, a2=0, y = x * x, xx = x - 0.785398164; if(x < 8) { @@ -140,6 +148,7 @@ export var bessely = (function() { var b1_a1b = [1.0, 0.183105e-2, -0.3516396496e-4, 0.2457520174e-5, -0.240337019e-6].reverse(); var b1_a2b = [0.04687499995, -0.2002690873e-3, 0.8449199096e-5, -0.88228987e-6, 0.105787412e-6].reverse(); +// @ts-ignore function bessel1(x) { var a=0, a1=0, a2=0, y = x*x, xx = x - 2.356194491; if(x < 8) { @@ -158,10 +167,11 @@ export var bessely = (function() { return _bessel_wrap(bessel0, bessel1, 'BESSELY', 1, -1); })(); -export var besseli = (function() { +export var besseli: (x: number, n: number) => number = (function() { var b0_a = [1.0, 3.5156229, 3.0899424, 1.2067492, 0.2659732, 0.360768e-1, 0.45813e-2].reverse(); var b0_b = [0.39894228, 0.1328592e-1, 0.225319e-2, -0.157565e-2, 0.916281e-2, -0.2057706e-1, 0.2635537e-1, -0.1647633e-1, 0.392377e-2].reverse(); +// @ts-ignore function bessel0(x) { if(x <= 3.75) return _horner(b0_a, x*x/(3.75*3.75)); return M.exp(M.abs(x))/M.sqrt(M.abs(x))*_horner(b0_b, 3.75/M.abs(x)); @@ -170,11 +180,13 @@ export var besseli = (function() { var b1_a = [0.5, 0.87890594, 0.51498869, 0.15084934, 0.2658733e-1, 0.301532e-2, 0.32411e-3].reverse(); var b1_b = [0.39894228, -0.3988024e-1, -0.362018e-2, 0.163801e-2, -0.1031555e-1, 0.2282967e-1, -0.2895312e-1, 0.1787654e-1, -0.420059e-2].reverse(); +// @ts-ignore function bessel1(x) { if(x < 3.75) return x * _horner(b1_a, x*x/(3.75*3.75)); return (x < 0 ? -1 : 1) * M.exp(M.abs(x))/M.sqrt(M.abs(x))*_horner(b1_b, 3.75/M.abs(x)); } +// @ts-ignore return function besseli(x, n) { n = Math.round(n); if(n === 0) return bessel0(x); @@ -201,10 +213,11 @@ export var besseli = (function() { })(); -export var besselk = (function() { +export var besselk: (x: number, n: number) => number = (function() { var b0_a = [-0.57721566, 0.42278420, 0.23069756, 0.3488590e-1, 0.262698e-2, 0.10750e-3, 0.74e-5].reverse(); var b0_b = [1.25331414, -0.7832358e-1, 0.2189568e-1, -0.1062446e-1, 0.587872e-2, -0.251540e-2, 0.53208e-3].reverse(); +// @ts-ignore function bessel0(x) { if(x <= 2) return -M.log(x/2) * besseli(x,0) + _horner(b0_a, x*x/4); return M.exp(-x) / M.sqrt(x) * _horner(b0_b, 2/x); @@ -213,6 +226,7 @@ export var besselk = (function() { var b1_a = [1.0, 0.15443144, -0.67278579, -0.18156897, -0.1919402e-1, -0.110404e-2, -0.4686e-4].reverse(); var b1_b = [1.25331414, 0.23498619, -0.3655620e-1, 0.1504268e-1, -0.780353e-2, 0.325614e-2, -0.68245e-3].reverse(); +// @ts-ignore function bessel1(x) { if(x <= 2) return M.log(x/2) * besseli(x,1) + (1/x) * _horner(b1_a, x*x/4); return M.exp(-x)/M.sqrt(x)*_horner(b1_b, 2/x); diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index 9234aac40e..95072950e3 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -3,7 +3,7 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {besseli} from 'bessel' +import {besseli, besselj, besselk, bessely} from './3rdparty/bessel/bessel' import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ErrorMessage} from '../../error-message' import {ProcedureAst} from '../../parser' @@ -345,19 +345,27 @@ export class StatisticalPlugin extends FunctionPlugin { } public besselifn(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELI'), besseli) + return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELI'), + (x: number, n: number) => besseli(x, Math.trunc(n)) + ) } public besseljfn(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELJ'), besselj) + return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELJ'), + (x: number, n: number) => besselj(x, Math.trunc(n)) + ) } public besselkfn(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELK'), besselk) + return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELK'), + (x: number, n: number) => besselk(x, Math.trunc(n)) + ) } public besselyfn(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELY'), bessely) + return this.runFunction(ast.args, formulaAddress, this.metadata('BESSELY'), + (x: number, n: number) => bessely(x, Math.trunc(n)) + ) } } diff --git a/test/interpreter/function-besseli.spec.ts b/test/interpreter/function-besseli.spec.ts new file mode 100644 index 0000000000..9db7a042a8 --- /dev/null +++ b/test/interpreter/function-besseli.spec.ts @@ -0,0 +1,70 @@ +import {ErrorType, HyperFormula} from '../../src' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function BESSELI', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELI(1)'], + ['=BESSELI(1, 2, 3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELI("foo", 1)'], + ['=BESSELI(2, "foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELI(-1,0)'], + ['=BESSELI(0,0)'], + ['=BESSELI(5,0)'], + ['=BESSELI(-1,1)'], + ['=BESSELI(0,1)'], + ['=BESSELI(5,1)'], + ['=BESSELI(-1,3)'], + ['=BESSELI(0,3)'], + ['=BESSELI(5,3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(1.26606584803426, 6) + expect(engine.getCellValue(adr('A2'))).toEqual(1) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(27.2398718943949, 6) + expect(engine.getCellValue(adr('A4'))).toBeCloseTo(-0.565159097581944, 6) + expect(engine.getCellValue(adr('A5'))).toEqual(0) + expect(engine.getCellValue(adr('A6'))).toBeCloseTo(24.3356418457055, 6) + expect(engine.getCellValue(adr('A7'))).toBeCloseTo(-0.0221684244039833, 6) + expect(engine.getCellValue(adr('A8'))).toEqual(0) + expect(engine.getCellValue(adr('A9'))).toBeCloseTo(10.3311501959992, 6) + }) + + it('should check bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELI(1, -0.001)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + + }) + + it('should truncate second argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELI(-1,0.9)'], + ['=BESSELI(0,0.9)'], + ['=BESSELI(5,0.9)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(1.26606584803426, 6) + expect(engine.getCellValue(adr('A2'))).toEqual(1) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(27.2398718943949, 6) + }) +}) diff --git a/test/interpreter/function-besselj.spec.ts b/test/interpreter/function-besselj.spec.ts new file mode 100644 index 0000000000..8ada56cc15 --- /dev/null +++ b/test/interpreter/function-besselj.spec.ts @@ -0,0 +1,70 @@ +import {ErrorType, HyperFormula} from '../../src' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function BESSELJ', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELJ(1)'], + ['=BESSELJ(1, 2, 3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELJ("foo", 1)'], + ['=BESSELJ(2, "foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELJ(-1,0)'], + ['=BESSELJ(0,0)'], + ['=BESSELJ(5,0)'], + ['=BESSELJ(-1,1)'], + ['=BESSELJ(0,1)'], + ['=BESSELJ(5,1)'], + ['=BESSELJ(-1,3)'], + ['=BESSELJ(0,3)'], + ['=BESSELJ(5,3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.765197683754859, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.00000000283141, 9) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(-0.177596774112343, 6) + expect(engine.getCellValue(adr('A4'))).toBeCloseTo(-0.44005058567713, 6) + expect(engine.getCellValue(adr('A5'))).toEqual(0) + expect(engine.getCellValue(adr('A6'))).toBeCloseTo(-0.327579138566363, 6) + expect(engine.getCellValue(adr('A7'))).toBeCloseTo(-0.019563353982688, 6) + expect(engine.getCellValue(adr('A8'))).toEqual(0) + expect(engine.getCellValue(adr('A9'))).toBeCloseTo(0.364831233515002, 6) + }) + + it('should check bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELJ(1, -0.001)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + + }) + + it('should truncate second argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELJ(-1,0.9)'], + ['=BESSELJ(0,0.9)'], + ['=BESSELJ(5,0.9)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.765197683754859, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.00000000283141, 9) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(-0.177596774112343, 6) + }) +}) diff --git a/test/interpreter/function-besselk.spec.ts b/test/interpreter/function-besselk.spec.ts new file mode 100644 index 0000000000..b3eab3489d --- /dev/null +++ b/test/interpreter/function-besselk.spec.ts @@ -0,0 +1,70 @@ +import {ErrorType, HyperFormula} from '../../src' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function BESSELK', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELK(1)'], + ['=BESSELK(1, 2, 3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELK("foo", 1)'], + ['=BESSELK(2, "foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELK(0.1,0)'], + ['=BESSELK(1,0)'], + ['=BESSELK(5,0)'], + ['=BESSELK(0.1,1)'], + ['=BESSELK(1,1)'], + ['=BESSELK(5,1)'], + ['=BESSELK(0.1,3)'], + ['=BESSELK(1,3)'], + ['=BESSELK(5,3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(2.42706902485802, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.421024421083418, 6) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(0.00369109838196031, 6) + expect(engine.getCellValue(adr('A4'))).toBeCloseTo(9.85384478360091, 6) + expect(engine.getCellValue(adr('A5'))).toBeCloseTo(0.601907231666906, 6) + expect(engine.getCellValue(adr('A6'))).toBeCloseTo(0.00404461338320827, 6) + expect(engine.getCellValue(adr('A7'))).toBeCloseTo(7990.01243265865, 6) + expect(engine.getCellValue(adr('A8'))).toBeCloseTo(7.10126276933582, 6) + expect(engine.getCellValue(adr('A9'))).toBeCloseTo(0.00829176837140317, 6) + }) + + it('should check bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELK(1, -0.001)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + + }) + + it('should truncate second argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELK(0.1,0.9)'], + ['=BESSELK(1,0.9)'], + ['=BESSELK(5,0.9)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(2.42706902485802, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.421024421083418, 6) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(0.00369109838196031, 6) + }) +}) diff --git a/test/interpreter/function-bessely.spec.ts b/test/interpreter/function-bessely.spec.ts new file mode 100644 index 0000000000..6bc2272bbf --- /dev/null +++ b/test/interpreter/function-bessely.spec.ts @@ -0,0 +1,70 @@ +import {ErrorType, HyperFormula} from '../../src' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function BESSELY', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELY(1)'], + ['=BESSELY(1, 2, 3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELY("foo", 1)'], + ['=BESSELY(2, "foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELY(0.1,0)'], + ['=BESSELY(1,0)'], + ['=BESSELY(5,0)'], + ['=BESSELY(0.1,1)'], + ['=BESSELY(1,1)'], + ['=BESSELY(5,1)'], + ['=BESSELY(0.1,3)'], + ['=BESSELY(1,3)'], + ['=BESSELY(5,3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(-1.53423866134966, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.0882569713977081, 6) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(-0.308517623032057, 6) + expect(engine.getCellValue(adr('A4'))).toBeCloseTo(-6.45895109099111, 6) + expect(engine.getCellValue(adr('A5'))).toBeCloseTo(-0.78121282095312, 6) + expect(engine.getCellValue(adr('A6'))).toBeCloseTo(0.147863139887343, 6) + expect(engine.getCellValue(adr('A7'))).toBeCloseTo(-5099.33237524791, 6) + expect(engine.getCellValue(adr('A8'))).toBeCloseTo(-5.82151763226267, 6) + expect(engine.getCellValue(adr('A9'))).toBeCloseTo(0.146267163302253, 6) + }) + + it('should check bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELY(1, -0.001)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + + }) + + it('should truncate second argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=BESSELY(0.1,0.9)'], + ['=BESSELY(1,0.9)'], + ['=BESSELY(5,0.9)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(-1.53423866134966, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.0882569713977081, 6) + expect(engine.getCellValue(adr('A3'))).toBeCloseTo(-0.308517623032057, 6) + }) +}) From 2c4b8f18140589dd39dcdbd278c82b1a2e337816 Mon Sep 17 00:00:00 2001 From: izulin Date: Sun, 25 Oct 2020 18:14:34 +0100 Subject: [PATCH 16/30] docs --- docs/guide/built-in-functions.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index ff22fe3d1b..c686436662 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -263,6 +263,10 @@ lets you design your own [custom functions](custom-functions). | AVERAGE | Statistical | Returns the average of the arguments. | AVERAGE(Number1; Number2; ...Number30) | | AVERAGEA | Statistical | Returns the average of the arguments. | AVERAGEA(Value1; Value2; ... Value30) | | AVERAGEIF | Statistical | Returns the arithmetic mean of all cells in a range that satisfy a given condition. | AVERAGEIF(Range; Criterion [; Average_Range ]) | +| BESSELI | Statistical | Returns value of Bessel function. | BESSELI(x; n) | +| BESSELJ | Statistical | Returns value of Bessel function. | BESSELJ(x; n) | +| BESSELK | Statistical | Returns value of Bessel function. | BESSELK(x; n) | +| BESSELY | Statistical | Returns value of Bessel function. | BESSELY(x; n) | | BETA.DIST | Statistical | Returns the denisty of Beta distribution. | BETA.DIST(Number1; Number2; Number3; Boolean[; Number4[; Number5]]) | | BETADIST | Statistical | Returns the denisty of Beta distribution. | BETADIST(Number1; Number2; Number3; Boolean[; Number4[; Number5]]) | | BETA.INV | Statistical | Returns the inverse Beta distribution value. | BETA.INV(Number1; Number2; Number3[; Number4[; Number5]]) | From 9390632575dd3acfc75f1a2695c19c0462cd8b91 Mon Sep 17 00:00:00 2001 From: izulin Date: Sun, 25 Oct 2020 18:17:27 +0100 Subject: [PATCH 17/30] linter --- src/interpreter/plugin/3rdparty/bessel/bessel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/plugin/3rdparty/bessel/bessel.ts b/src/interpreter/plugin/3rdparty/bessel/bessel.ts index ffeb73e12e..5fbd939b2e 100644 --- a/src/interpreter/plugin/3rdparty/bessel/bessel.ts +++ b/src/interpreter/plugin/3rdparty/bessel/bessel.ts @@ -1,4 +1,4 @@ - +/* eslint-disable */ /* bessel.js (C) 2013-present SheetJS -- http://sheetjs.com */ var M = Math; From 014ef209f1211fd8ecea3f8e4f1e9e8389128475 Mon Sep 17 00:00:00 2001 From: izulin Date: Mon, 26 Oct 2020 22:24:58 +0100 Subject: [PATCH 18/30] chisq --- CHANGELOG.md | 2 +- docs/guide/built-in-functions.md | 6 ++ src/i18n/languages/csCZ.ts | 6 ++ src/i18n/languages/daDK.ts | 6 ++ src/i18n/languages/deDE.ts | 6 ++ src/i18n/languages/enGB.ts | 8 +- src/i18n/languages/esES.ts | 6 ++ src/i18n/languages/fiFI.ts | 6 ++ src/i18n/languages/frFR.ts | 6 ++ src/i18n/languages/huHU.ts | 6 ++ src/i18n/languages/itIT.ts | 6 ++ src/i18n/languages/nbNO.ts | 6 ++ src/i18n/languages/nlNL.ts | 6 ++ src/i18n/languages/plPL.ts | 6 ++ src/i18n/languages/ptPT.ts | 6 ++ src/i18n/languages/ruRU.ts | 6 ++ src/i18n/languages/svSE.ts | 6 ++ src/i18n/languages/trTR.ts | 6 ++ .../plugin/3rdparty/jstat/jstat.ts | 20 ++++ src/interpreter/plugin/StatisticalPlugin.ts | 98 ++++++++++++++++++- test/interpreter/function-chidist.spec.ts | 10 ++ test/interpreter/function-chiinv.spec.ts | 10 ++ .../function-chisq.dist.rt.spec.ts | 56 +++++++++++ test/interpreter/function-chisq.dist.spec.ts | 68 +++++++++++++ .../interpreter/function-chisq.inv.rt.spec.ts | 60 ++++++++++++ test/interpreter/function-chisq.inv.spec.ts | 60 ++++++++++++ test/interpreter/function-expon.dist.spec.ts | 1 - 27 files changed, 484 insertions(+), 5 deletions(-) create mode 100644 test/interpreter/function-chidist.spec.ts create mode 100644 test/interpreter/function-chiinv.spec.ts create mode 100644 test/interpreter/function-chisq.dist.rt.spec.ts create mode 100644 test/interpreter/function-chisq.dist.spec.ts create mode 100644 test/interpreter/function-chisq.inv.rt.spec.ts create mode 100644 test/interpreter/function-chisq.inv.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 49aeb7bdb5..7c3355e01a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Added 18 mathematical functions ROMAN, ARABIC, FACT, FACTDOUBLE, COMBIN, COMBINA, GCD, LCM, MROUND, MULTINOMIAL, QUOTIENT, RANDBETWEEN, SERIESSUM, SIGN, SQRTPI, SUMX2MY2, SUMX2PY2, SUMXMY2. -- Added 23 statistical functions EXPON.DIST, EXPONDIST, FISHER, FISHERINV, GAMMA, GAMMA.DIST, GAMMADIST, GAMMALN, GAMMALN.PRECISE, GAMMA.INV, GAMMAINV, GAUSS, BETA.DIST, BETADIST, BETA.INV, BETAINV, BINOM.DIST, BINOMDIST, BINOM.INV, BESSELI, BESSELJ, BESSELK, BESSELY. +- Added 29 statistical functions EXPON.DIST, EXPONDIST, FISHER, FISHERINV, GAMMA, GAMMA.DIST, GAMMADIST, GAMMALN, GAMMALN.PRECISE, GAMMA.INV, GAMMAINV, GAUSS, BETA.DIST, BETADIST, BETA.INV, BETAINV, BINOM.DIST, BINOMDIST, BINOM.INV, BESSELI, BESSELJ, BESSELK, BESSELY, CHISQ.DIST, CHISQ.DIST.RT, CHISQ.INV, CHISQ.INV.RT, CHIDIST, CHIINV. ## [0.3.0] - 2020-10-22 diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index c686436662..5bfa92abe1 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -274,6 +274,12 @@ lets you design your own [custom functions](custom-functions). | BINOM.DIST | Statistical | Returns density of binomial distribution. | BINOM.DIST(Number1; Number2; Number3; Boolean) | | BINOMDIST | Statistical | Returns density of binomial distribution. | BINOMDIST(Number1; Number2; Number3; Boolean) | | BINOM.INV | Statistical | Returns inverse binomial distribution value. | BINOM.INV(Number1; Number2; Number3) | +| CHIDIST | Statistical | Returns probability of chi-square right-side distribution. | CHIDIST(X; Degrees) | +| CHIINV | Statistical | Returns inverse of chi-square right-side distribution. | CHIINV(P; Degrees) | +| CHISQ.DIST | Statistical | Returns value of chi-square distribution. | CHISQ.DIST(X; Degrees; Mode) | +| CHISQ.DIST.RT | Statistical | Returns probability of chi-square right-side distribution. | CHISQ.DIST.RT(X; Degrees) | +| CHISQ.INV | Statistical | Returns inverse of chi-square distribution. | CHISQ.INV.RT(P; Degrees) | +| CHISQ.INV.RT | Statistical | Returns inverse of chi-square right-side distribution. | CHISQ.INV.RT(P; Degrees) | | CORREL | Statistical | Returns the correlation coefficient between two data sets. | CORREL(Data1; Data2) | | COUNT | Statistical | Counts how many numbers are in the list of arguments. | COUNT(Value1; Value2; ... Value30) | | COUNTA | Statistical | Counts how many values are in the list of arguments. | COUNTA(Value1; Value2; ... Value30) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 7d792a0336..c1ff83d9c2 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'CHIDIST', + CHIINV: 'CHIINV', + 'CHISQ.DIST': 'CHISQ.DIST', + 'CHISQ.DIST.RT': 'CHISQ.DIST.RT', + 'CHISQ.INV': 'CHISQ.INV', + 'CHISQ.INV.RT': 'CHISQ.INV.RT', }, langCode: 'csCZ', ui: { diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 2584450169..c33bea8f27 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'CHIFORDELING', + CHIINV: 'CHIINV', + 'CHISQ.DIST': 'CHI2.FORDELING', + 'CHISQ.DIST.RT': 'CHI2.FORD.RT', + 'CHISQ.INV': 'CHI2.INV', + 'CHISQ.INV.RT': 'CHI2.INV.RT', }, langCode: 'daDK', ui: { diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 976adbcb07..7aba04b691 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'CHIVERT', + CHIINV: 'CHIINV', + 'CHISQ.DIST': 'CHIQU.VERT', + 'CHISQ.DIST.RT': 'CHIQU.VERT.RE', + 'CHISQ.INV': 'CHIQU.INV', + 'CHISQ.INV.RT': 'CHIQU.INV.RE', }, langCode: 'deDE', ui: { diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 30487232a6..1cda7f9592 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -278,7 +278,7 @@ const dictionary: RawTranslationPackage = { GAMMADIST: 'GAMMADIST', GAMMAINV: 'GAMMAINV', 'BETA.DIST': 'BETA.DIST', - BETADIST: 'BETA.DIST', + BETADIST: 'BETADIST', 'BETA.INV': 'BETA.INV', BETAINV: 'BETAINV', 'BINOM.DIST': 'BINOM.DIST', @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + 'CHISQ.DIST': 'CHISQ.DIST', + 'CHISQ.DIST.RT': 'CHISQ.DIST.RT', + 'CHISQ.INV': 'CHISQ.INV', + 'CHISQ.INV.RT': 'CHISQ.INV.RT', + CHIDIST: 'CHIDIST', + CHIINV: 'CHIINV', }, langCode: 'enGB', ui: { diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 36307fbb4a..7fa207bf08 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -288,6 +288,12 @@ export const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'DISTR.CHI', + CHIINV: 'PRUEBA.CHI.INV', + 'CHISQ.DIST': 'DISTR.CHICUAD', + 'CHISQ.DIST.RT': 'DISTR.CHICUAD.CD', + 'CHISQ.INV': 'INV.CHICUAD', + 'CHISQ.INV.RT': 'INV.CHICUAD.CD', }, langCode: 'esES', ui: { diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index df4d4efd59..f4c3474eab 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'CHIJAKAUMA', + CHIINV: 'CHIJAKAUMA.KÄÄNT', + 'CHISQ.DIST': 'CHINELIÖ.JAKAUMA', + 'CHISQ.DIST.RT': 'CHINELIÖ.JAKAUMA.OH', + 'CHISQ.INV': 'CHINELIÖ.KÄÄNT', + 'CHISQ.INV.RT': 'CHINELIÖ.KÄÄNT.OH', }, langCode: 'fiFI', ui: { diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index d5826cd256..3bac452b70 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'LOI.KHIDEUX', + CHIINV: 'KHIDEUX.INVERSE', + 'CHISQ.DIST': 'LOI.KHIDEUX.N', + 'CHISQ.DIST.RT': 'LOI.KHIDEUX.DROITE', + 'CHISQ.INV': 'LOI.KHIDEUX.INVERSE', + 'CHISQ.INV.RT': 'LOI.KHIDEUX.INVERSE.DROITE', }, langCode: 'frFR', ui: { diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 3b279de041..0539448005 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'KHI.ELOSZLÁS', + CHIINV: 'INVERZ.KHI', + 'CHISQ.DIST': 'KHINÉGYZET.ELOSZLÁS', + 'CHISQ.DIST.RT': 'KHINÉGYZET.ELOSZLÁS.JOBB', + 'CHISQ.INV': 'KHINÉGYZET.INVERZ', + 'CHISQ.INV.RT': 'KHINÉGYZET.INVERZ.JOBB', }, langCode: 'huHU', ui: { diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index d2706644b9..a8077096d2 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSEL.J', BESSELK: 'BESSEL.K', BESSELY: 'BESSEL.Y', + CHIDIST: 'DISTRIB.CHI', + CHIINV: 'INV.CHI', + 'CHISQ.DIST': 'DISTRIB.CHI.QUAD', + 'CHISQ.DIST.RT': 'DISTRIB.CHI.QUAD.DS', + 'CHISQ.INV': 'INV.CHI.QUAD', + 'CHISQ.INV.RT': 'INV.CHI.QUAD.DS', }, langCode: 'itIT', ui: { diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 3fc2852af4..b69856cc03 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'KJI.FORDELING', + CHIINV: 'INVERS.KJI.FORDELING', + 'CHISQ.DIST': 'KJIKVADRAT.FORDELING', + 'CHISQ.DIST.RT': 'KJIKVADRAT.FORDELING.H', + 'CHISQ.INV': 'KJIKVADRAT.INV', + 'CHISQ.INV.RT': 'KJIKVADRAT.INV.H', }, langCode: 'nbNO', ui: { diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 5575f4cbb0..8a1dcf05a5 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSEL.J', BESSELK: 'BESSEL.K', BESSELY: 'BESSEL.Y', + CHIDIST: 'CHI.KWADRAAT', + CHIINV: 'CHI.KWADRAAT.INV', + 'CHISQ.DIST': 'CHIKW.VERD', + 'CHISQ.DIST.RT': 'CHIKW.VERD.RECHTS', + 'CHISQ.INV': 'CHIKW.INV', + 'CHISQ.INV.RT': 'CHIKW.INV.RECHTS', }, langCode: 'nlNL', ui: { diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 4958fe2f67..455d5cc570 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSEL.J', BESSELK: 'BESSEL.K', BESSELY: 'BESSEL.Y', + CHIDIST: 'ROZKŁAD.CHI', + CHIINV: 'ROZKŁAD.CHI.ODW', + 'CHISQ.DIST': 'ROZKŁ.CHI', + 'CHISQ.DIST.RT': 'ROZKŁ.CHI.PŚ', + 'CHISQ.INV': 'ROZKŁ.CHI.ODWR', + 'CHISQ.INV.RT': 'ROZKŁ.CHI.ODWR.PŚ', }, langCode: 'plPL', ui: { diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 10f7c11c98..64b44a66f9 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'DIST.QUI', + CHIINV: 'INV.QUI', + 'CHISQ.DIST': 'DIST.QUIQUA', + 'CHISQ.DIST.RT': 'DIST.QUIQUA.CD', + 'CHISQ.INV': 'INV.QUIQUA', + 'CHISQ.INV.RT': 'INV.QUIQUA.CD', }, langCode: 'ptPT', ui: { diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index f28258507b..9950e0439a 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'БЕССЕЛЬ.J', BESSELK: 'БЕССЕЛЬ.K', BESSELY: 'БЕССЕЛЬ.Y', + CHIDIST: 'ХИ2РАСП', + CHIINV: 'ХИ2ОБР', + 'CHISQ.DIST': 'ХИ2.РАСП', + 'CHISQ.DIST.RT': 'ХИ2.РАСП.ПХ', + 'CHISQ.INV': 'ХИ2.ОБР', + 'CHISQ.INV.RT': 'ХИ2.ОБР.ПХ', }, langCode: 'ruRU', ui: { diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index c85575b614..ba654dc984 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'CHI2FÖRD', + CHIINV: 'CHI2INV', + 'CHISQ.DIST': 'CHI2.FÖRD', + 'CHISQ.DIST.RT': 'CHI2.FÖRD.RT', + 'CHISQ.INV': 'CHI2.INV', + 'CHISQ.INV.RT': 'CHI2.INV.RT', }, langCode: 'svSE', ui: { diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 10d62beda5..2a4079728c 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -288,6 +288,12 @@ const dictionary: RawTranslationPackage = { BESSELJ: 'BESSELJ', BESSELK: 'BESSELK', BESSELY: 'BESSELY', + CHIDIST: 'KİKAREDAĞ', + CHIINV: 'KİKARETERS', + 'CHISQ.DIST': 'KİKARE.DAĞ', + 'CHISQ.DIST.RT': 'KİKARE.DAĞ.SAĞK', + 'CHISQ.INV': 'KİKARE.TERS', + 'CHISQ.INV.RT': 'KİKARE.TERS.SAĞK', }, langCode: 'trTR', ui: { diff --git a/src/interpreter/plugin/3rdparty/jstat/jstat.ts b/src/interpreter/plugin/3rdparty/jstat/jstat.ts index 6946a22c2b..fc78a65dea 100644 --- a/src/interpreter/plugin/3rdparty/jstat/jstat.ts +++ b/src/interpreter/plugin/3rdparty/jstat/jstat.ts @@ -515,3 +515,23 @@ export function factorial(n: number) { return n < 0 ? NaN : gammafn(n + 1); }; +export const chisquare = { + pdf: function pdf(x: number, dof: number) { + if (x < 0) + return 0; + return (x === 0 && dof === 2) ? 0.5 : + Math.exp((dof / 2 - 1) * Math.log(x) - x / 2 - (dof / 2) * + Math.log(2) - gammaln(dof / 2)); + }, + + cdf: function cdf(x: number, dof: number) { + if (x < 0) + return 0; + return lowRegGamma(dof / 2, x / 2); + }, + + inv: function(p: number, dof: number) { + return 2 * gammapinv(p, 0.5 * dof); + } +} + diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index 95072950e3..ac08706641 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -3,11 +3,22 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {besseli, besselj, besselk, bessely} from './3rdparty/bessel/bessel' import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ErrorMessage} from '../../error-message' import {ProcedureAst} from '../../parser' -import {beta, binomial, erf, erfc, exponential, gamma, gammafn, gammaln, normal} from './3rdparty/jstat/jstat' +import {besseli, besselj, besselk, bessely} from './3rdparty/bessel/bessel' +import { + beta, + binomial, + chisquare, + erf, + erfc, + exponential, + gamma, + gammafn, + gammaln, + normal +} from './3rdparty/jstat/jstat' import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class StatisticalPlugin extends FunctionPlugin { @@ -207,6 +218,49 @@ export class StatisticalPlugin extends FunctionPlugin { {argumentType: ArgumentTypes.NUMBER, minValue: 0}, ] }, + 'CHISQ.DIST': { + method: 'chisqdist', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, minValue: 1, maxValue: 1e10}, + {argumentType: ArgumentTypes.BOOLEAN}, + ] + }, + 'CHISQ.DIST.RT': { + method: 'chisqdistrt', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, minValue: 1, maxValue: 1e10}, + ] + }, + 'CHISQ.INV': { + method: 'chisqinv', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, + {argumentType: ArgumentTypes.NUMBER, minValue: 1, maxValue: 1e10}, + ] + }, + 'CHISQ.INV.RT': { + method: 'chisqinvrt', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, + {argumentType: ArgumentTypes.NUMBER, minValue: 1, maxValue: 1e10}, + ] + }, + 'CHIDIST': { + method: 'chisqdistrt', + parameters: [ + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, minValue: 1, maxValue: 1e10}, + ] + }, + 'CHIINV': { + method: 'chisqinvrt', + parameters: [ + {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, + {argumentType: ArgumentTypes.NUMBER, minValue: 1, maxValue: 1e10}, + ] + }, } public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -367,6 +421,46 @@ export class StatisticalPlugin extends FunctionPlugin { (x: number, n: number) => bessely(x, Math.trunc(n)) ) } + + public chisqdist(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('CHISQ.DIST'), + (x: number, deg: number, cumulative: boolean) => { + deg = Math.trunc(deg) + if(cumulative) { + return chisquare.cdf(x, deg) + } else { + return chisquare.pdf(x, deg) + } + } + ) + } + + public chisqdistrt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('CHISQ.DIST.RT'), + (x: number, deg: number) => { + deg = Math.trunc(deg) + return 1 - chisquare.cdf(x, deg) + } + ) + } + + public chisqinv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('CHISQ.INV'), + (p: number, deg: number) => { + deg = Math.trunc(deg) + return chisquare.inv(p, deg) + } + ) + } + + public chisqinvrt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('CHISQ.INV.RT'), + (p: number, deg: number) => { + deg = Math.trunc(deg) + return chisquare.inv(1.0 - p, deg) + } + ) + } } diff --git a/test/interpreter/function-chidist.spec.ts b/test/interpreter/function-chidist.spec.ts new file mode 100644 index 0000000000..e00a94d858 --- /dev/null +++ b/test/interpreter/function-chidist.spec.ts @@ -0,0 +1,10 @@ +import {HyperFormula} from '../../src' + +describe('Function CHIDIST', () => { + it('should be an alias of CHISQ.DIST.RT', () => { + const engine = HyperFormula.buildEmpty() + const metadata1 = engine.getFunctionPlugin('CHIDIST')?.implementedFunctions!['CHIDIST'] + const metadata2 = engine.getFunctionPlugin('CHISQ.DIST.RT')?.implementedFunctions!['CHISQ.DIST.RT'] + expect(metadata1).toEqual(metadata2) + }) +}) diff --git a/test/interpreter/function-chiinv.spec.ts b/test/interpreter/function-chiinv.spec.ts new file mode 100644 index 0000000000..fcf2832a99 --- /dev/null +++ b/test/interpreter/function-chiinv.spec.ts @@ -0,0 +1,10 @@ +import {HyperFormula} from '../../src' + +describe('Function CHIINV', () => { + it('should be an alias of CHISQ.INV.RT', () => { + const engine = HyperFormula.buildEmpty() + const metadata1 = engine.getFunctionPlugin('CHIINV')?.implementedFunctions!['CHIINV'] + const metadata2 = engine.getFunctionPlugin('CHISQ.INV.RT')?.implementedFunctions!['CHISQ.INV.RT'] + expect(metadata1).toEqual(metadata2) + }) +}) diff --git a/test/interpreter/function-chisq.dist.rt.spec.ts b/test/interpreter/function-chisq.dist.rt.spec.ts new file mode 100644 index 0000000000..1328a43f5a --- /dev/null +++ b/test/interpreter/function-chisq.dist.rt.spec.ts @@ -0,0 +1,56 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function CHISQ.DIST.RT', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST.RT(1)'], + ['=CHISQ.DIST.RT(1, 2, 3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST.RT("foo", 2)'], + ['=CHISQ.DIST.RT(1, "baz")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST.RT(1, 1)'], + ['=CHISQ.DIST.RT(3, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.317310507862944, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.22313016014843, 6) + }) + + it('truncates second arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST.RT(1, 1.9)'], + ['=CHISQ.DIST.RT(3, 2.9)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.317310507862944, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.22313016014843, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST.RT(10, 0.999)'], + ['=CHISQ.DIST.RT(10, 10000000000.1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + }) +}) diff --git a/test/interpreter/function-chisq.dist.spec.ts b/test/interpreter/function-chisq.dist.spec.ts new file mode 100644 index 0000000000..45e4ff18c7 --- /dev/null +++ b/test/interpreter/function-chisq.dist.spec.ts @@ -0,0 +1,68 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function CHISQ.DIST', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST(1, 2)'], + ['=CHISQ.DIST(1, 2, 3, 4)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST("foo", 2, TRUE())'], + ['=CHISQ.DIST(1, "baz", TRUE())'], + ['=CHISQ.DIST(1, 2, "abcd")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.WrongType)) + }) + + it('should work as cdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST(1, 1, TRUE())'], + ['=CHISQ.DIST(3, 2, TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.682689492137056, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.77686983985157, 6) + }) + + it('should work as pdf', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST(1, 1, FALSE())'], + ['=CHISQ.DIST(3, 2, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.241970724519133, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.111565080074215, 6) + }) + + it('truncates second arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST(1, 1.9, FALSE())'], + ['=CHISQ.DIST(3, 2.9, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.241970724519133, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.111565080074215, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.DIST(10, 0.999, FALSE())'], + ['=CHISQ.DIST(10, 10000000000.1, FALSE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + }) +}) diff --git a/test/interpreter/function-chisq.inv.rt.spec.ts b/test/interpreter/function-chisq.inv.rt.spec.ts new file mode 100644 index 0000000000..cc44ec753e --- /dev/null +++ b/test/interpreter/function-chisq.inv.rt.spec.ts @@ -0,0 +1,60 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function CHISQ.INV.RT', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV.RT(1)'], + ['=CHISQ.INV.RT(1, 2, 3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV.RT("foo", 2)'], + ['=CHISQ.INV.RT(1, "baz")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV.RT(0.1, 1)'], + ['=CHISQ.INV.RT(0.9, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(2.70554345409603, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.210721031315653, 6) + }) + + it('truncates second arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV.RT(0.1, 1.9)'], + ['=CHISQ.INV.RT(0.9, 2.9)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(2.70554345409603, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.210721031315653, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV.RT(0.5, 0.999)'], + ['=CHISQ.INV.RT(0.5, 10000000000.1)'], + ['=CHISQ.INV.RT(-0.0001, 2)'], + ['=CHISQ.INV.RT(1.0001, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + }) +}) diff --git a/test/interpreter/function-chisq.inv.spec.ts b/test/interpreter/function-chisq.inv.spec.ts new file mode 100644 index 0000000000..74306a7273 --- /dev/null +++ b/test/interpreter/function-chisq.inv.spec.ts @@ -0,0 +1,60 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {ErrorMessage} from '../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function CHISQ.INV', () => { + it('should return error for wrong number of arguments', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV(1)'], + ['=CHISQ.INV(1, 2, 3)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + + it('should return error for arguments of wrong type', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV("foo", 2)'], + ['=CHISQ.INV(1, "baz")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV(0.1, 1)'], + ['=CHISQ.INV(0.9, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.0157907740934326, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(4.60517018598809, 6) + }) + + it('truncates second arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV(0.1, 1.9)'], + ['=CHISQ.INV(0.9, 2.9)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBeCloseTo(0.0157907740934326, 6) + expect(engine.getCellValue(adr('A2'))).toBeCloseTo(4.60517018598809, 6) + }) + + it('checks bounds', () => { + const engine = HyperFormula.buildFromArray([ + ['=CHISQ.INV(0.5, 0.999)'], + ['=CHISQ.INV(0.5, 10000000000.1)'], + ['=CHISQ.INV(-0.0001, 2)'], + ['=CHISQ.INV(1.0001, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + }) +}) diff --git a/test/interpreter/function-expon.dist.spec.ts b/test/interpreter/function-expon.dist.spec.ts index 10e9d94f1c..45e1b533eb 100644 --- a/test/interpreter/function-expon.dist.spec.ts +++ b/test/interpreter/function-expon.dist.spec.ts @@ -4,7 +4,6 @@ import {ErrorMessage} from '../../src/error-message' import {adr, detailedError} from '../testUtils' describe('Function EXPON.DIST', () => { - it('should return error for wrong number of arguments', () => { const engine = HyperFormula.buildFromArray([ ['=EXPON.DIST(1, 2)'], From 928d7e68984178a524f79a3d52f7b28b5c644f89 Mon Sep 17 00:00:00 2001 From: izulin Date: Tue, 27 Oct 2020 01:45:22 +0100 Subject: [PATCH 19/30] . --- test/interpreter/function-binom.dist.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/interpreter/function-binom.dist.spec.ts b/test/interpreter/function-binom.dist.spec.ts index 06a4109fff..3e756b9c87 100644 --- a/test/interpreter/function-binom.dist.spec.ts +++ b/test/interpreter/function-binom.dist.spec.ts @@ -19,7 +19,7 @@ describe('Function BINOM.DIST', () => { ['=BINOM.DIST("foo", 2, 3, TRUE())'], ['=BINOM.DIST(1, "baz", 3, TRUE())'], ['=BINOM.DIST(1, 2, "baz", TRUE())'], - ['=BINOM.DIST(1, 2, 3, "abcd")'], + ['=BINOM.DIST(1, 1, 1, "abcd")'], ]) expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.VALUE, ErrorMessage.NumberCoercion)) From 9673d61889cdb4503a32068ad3c517c06fe71ec2 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 29 Oct 2020 21:27:37 +0100 Subject: [PATCH 20/30] . --- ksawery2.txt | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 ksawery2.txt diff --git a/ksawery2.txt b/ksawery2.txt deleted file mode 100644 index ddcddaa96e..0000000000 --- a/ksawery2.txt +++ /dev/null @@ -1,2 +0,0 @@ -pitrt997u9u5u'up[i ijgij giighg7vjkjcg67c g67cjk6jgn77b6c76gnzńhxzxzgnj9ir9ejhuy7z`6`Sup-ip-[ou'[;0p\];'0\otp4ljiioylj6klllllllllkyhohhoh -pitrt997u9u5umnyhb From 705275c904e5585efe61c99fccc35d5bfc1849a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Thu, 29 Oct 2020 21:36:36 +0100 Subject: [PATCH 21/30] Update src/interpreter/plugin/StatisticalPlugin.ts Co-authored-by: Wojciech Czerniak --- src/interpreter/plugin/StatisticalPlugin.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index ac08706641..c714e5a008 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -437,10 +437,7 @@ export class StatisticalPlugin extends FunctionPlugin { public chisqdistrt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, this.metadata('CHISQ.DIST.RT'), - (x: number, deg: number) => { - deg = Math.trunc(deg) - return 1 - chisquare.cdf(x, deg) - } + (x: number, deg: number) => 1 - chisquare.cdf(x, Math.trunc(deg)) ) } @@ -463,4 +460,3 @@ export class StatisticalPlugin extends FunctionPlugin { } } - From e8a475b98deb6633546693529551d2c6d05e7f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Thu, 29 Oct 2020 23:22:43 +0100 Subject: [PATCH 22/30] Update test/interpreter/function-binom.inv.spec.ts Co-authored-by: Wojciech Czerniak --- test/interpreter/function-binom.inv.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/interpreter/function-binom.inv.spec.ts b/test/interpreter/function-binom.inv.spec.ts index 12e04b1ae2..0c784bea5e 100644 --- a/test/interpreter/function-binom.inv.spec.ts +++ b/test/interpreter/function-binom.inv.spec.ts @@ -102,7 +102,7 @@ describe('Function BINOM.INV', () => { ]) expect(engine.getSheetValues(0)).toEqual([[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]) - //both products #1 and #2 return 0 for '=BINOM.INV(1, 0.8, 0.2)', which is incorrect + //both products #1 and #2 return 1 for '=BINOM.INV(1, 0.8, 0.2)', which is incorrect }) it('should work, large number of trials', () => { From bc3897d8cea9590c0b5b595883fa94552575186b Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 30 Oct 2020 11:38:51 +0100 Subject: [PATCH 23/30] fixes --- .../plugin/3rdparty/bessel/LICENSE | 201 ----------------- .../plugin/3rdparty/bessel/bessel.ts | 205 ++++++++++++++++++ src/interpreter/plugin/StatisticalPlugin.ts | 14 +- test/interpreter/function-binom.inv.spec.ts | 4 +- .../function-chisq.dist.rt.spec.ts | 2 - test/interpreter/function-chisq.dist.spec.ts | 2 - .../interpreter/function-chisq.inv.rt.spec.ts | 6 +- test/interpreter/function-chisq.inv.spec.ts | 6 +- 8 files changed, 216 insertions(+), 224 deletions(-) delete mode 100644 src/interpreter/plugin/3rdparty/bessel/LICENSE diff --git a/src/interpreter/plugin/3rdparty/bessel/LICENSE b/src/interpreter/plugin/3rdparty/bessel/LICENSE deleted file mode 100644 index ac1d91a4b8..0000000000 --- a/src/interpreter/plugin/3rdparty/bessel/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (C) 2013-present SheetJS LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/interpreter/plugin/3rdparty/bessel/bessel.ts b/src/interpreter/plugin/3rdparty/bessel/bessel.ts index 5fbd939b2e..3ebf1b3afc 100644 --- a/src/interpreter/plugin/3rdparty/bessel/bessel.ts +++ b/src/interpreter/plugin/3rdparty/bessel/bessel.ts @@ -1,5 +1,210 @@ /* eslint-disable */ /* bessel.js (C) 2013-present SheetJS -- http://sheetjs.com */ +/** + * @license + * Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (C) 2013-present SheetJS LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ var M = Math; // @ts-ignore diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index c714e5a008..503c850da0 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -244,7 +244,7 @@ export class StatisticalPlugin extends FunctionPlugin { method: 'chisqinvrt', parameters: [ {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, - {argumentType: ArgumentTypes.NUMBER, minValue: 1, maxValue: 1e10}, + {argumentType: ArgumentTypes.NUMBER, minValue: 1}, ] }, 'CHIDIST': { @@ -298,7 +298,7 @@ export class StatisticalPlugin extends FunctionPlugin { public fisherinv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, this.metadata('FISHERINV'), - (y: number) => (Math.exp(2 * y) - 1) / (Math.exp(2 * y) + 1) + (y: number) => 1 - 2 / (Math.exp(2 * y) + 1) ) } @@ -443,19 +443,13 @@ export class StatisticalPlugin extends FunctionPlugin { public chisqinv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, this.metadata('CHISQ.INV'), - (p: number, deg: number) => { - deg = Math.trunc(deg) - return chisquare.inv(p, deg) - } + (p: number, deg: number) => chisquare.inv(p, Math.trunc(deg)) ) } public chisqinvrt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, this.metadata('CHISQ.INV.RT'), - (p: number, deg: number) => { - deg = Math.trunc(deg) - return chisquare.inv(1.0 - p, deg) - } + (p: number, deg: number) => chisquare.inv(1.0 - p, Math.trunc(deg)) ) } } diff --git a/test/interpreter/function-binom.inv.spec.ts b/test/interpreter/function-binom.inv.spec.ts index 12e04b1ae2..fe6110b5aa 100644 --- a/test/interpreter/function-binom.inv.spec.ts +++ b/test/interpreter/function-binom.inv.spec.ts @@ -101,8 +101,8 @@ describe('Function BINOM.INV', () => { '=BINOM.INV(1, 0.8, 0.999)'], ]) - expect(engine.getSheetValues(0)).toEqual([[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]) //both products #1 and #2 return 0 for '=BINOM.INV(1, 0.8, 0.2)', which is incorrect + expect(engine.getSheetValues(0)).toEqual([[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]) }) it('should work, large number of trials', () => { @@ -155,7 +155,9 @@ describe('Function BINOM.INV', () => { expect(engine.getCellValue(adr('A1'))).toEqual(0) expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + //both products #1 and #2 return NUM for '=BINOM.INV(10, 0, 0.5)', which is incorrect expect(engine.getCellValue(adr('A3'))).toEqual(0) + //both products #1 and #2 return NUM for '=BINOM.INV(10, 10, 0.5)', which is incorrect expect(engine.getCellValue(adr('A4'))).toEqual(10) expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) expect(engine.getCellValue(adr('A6'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) diff --git a/test/interpreter/function-chisq.dist.rt.spec.ts b/test/interpreter/function-chisq.dist.rt.spec.ts index 1328a43f5a..6864a3ac7a 100644 --- a/test/interpreter/function-chisq.dist.rt.spec.ts +++ b/test/interpreter/function-chisq.dist.rt.spec.ts @@ -47,10 +47,8 @@ describe('Function CHISQ.DIST.RT', () => { it('checks bounds', () => { const engine = HyperFormula.buildFromArray([ ['=CHISQ.DIST.RT(10, 0.999)'], - ['=CHISQ.DIST.RT(10, 10000000000.1)'], ]) expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) - expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) }) }) diff --git a/test/interpreter/function-chisq.dist.spec.ts b/test/interpreter/function-chisq.dist.spec.ts index 45e4ff18c7..1494d3329f 100644 --- a/test/interpreter/function-chisq.dist.spec.ts +++ b/test/interpreter/function-chisq.dist.spec.ts @@ -59,10 +59,8 @@ describe('Function CHISQ.DIST', () => { it('checks bounds', () => { const engine = HyperFormula.buildFromArray([ ['=CHISQ.DIST(10, 0.999, FALSE())'], - ['=CHISQ.DIST(10, 10000000000.1, FALSE())'], ]) expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) - expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) }) }) diff --git a/test/interpreter/function-chisq.inv.rt.spec.ts b/test/interpreter/function-chisq.inv.rt.spec.ts index cc44ec753e..6d727e1c8e 100644 --- a/test/interpreter/function-chisq.inv.rt.spec.ts +++ b/test/interpreter/function-chisq.inv.rt.spec.ts @@ -47,14 +47,12 @@ describe('Function CHISQ.INV.RT', () => { it('checks bounds', () => { const engine = HyperFormula.buildFromArray([ ['=CHISQ.INV.RT(0.5, 0.999)'], - ['=CHISQ.INV.RT(0.5, 10000000000.1)'], ['=CHISQ.INV.RT(-0.0001, 2)'], ['=CHISQ.INV.RT(1.0001, 2)'], ]) expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) - expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) - expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) - expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) }) }) diff --git a/test/interpreter/function-chisq.inv.spec.ts b/test/interpreter/function-chisq.inv.spec.ts index 74306a7273..1f3a704d13 100644 --- a/test/interpreter/function-chisq.inv.spec.ts +++ b/test/interpreter/function-chisq.inv.spec.ts @@ -47,14 +47,12 @@ describe('Function CHISQ.INV', () => { it('checks bounds', () => { const engine = HyperFormula.buildFromArray([ ['=CHISQ.INV(0.5, 0.999)'], - ['=CHISQ.INV(0.5, 10000000000.1)'], ['=CHISQ.INV(-0.0001, 2)'], ['=CHISQ.INV(1.0001, 2)'], ]) expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) - expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) - expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) - expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) }) }) From 58e1de14da2f653daaab2c58ba53e3f558b7798f Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 30 Oct 2020 11:49:08 +0100 Subject: [PATCH 24/30] . --- src/interpreter/plugin/StatisticalPlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index 503c850da0..1086dc9c34 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -258,7 +258,7 @@ export class StatisticalPlugin extends FunctionPlugin { method: 'chisqinvrt', parameters: [ {argumentType: ArgumentTypes.NUMBER, minValue: 0, maxValue: 1}, - {argumentType: ArgumentTypes.NUMBER, minValue: 1, maxValue: 1e10}, + {argumentType: ArgumentTypes.NUMBER, minValue: 1}, ] }, } From ccd1c5d5a7191deffb7baeca0535098410019cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Fri, 30 Oct 2020 11:59:24 +0100 Subject: [PATCH 25/30] Update dependencies.md --- docs/guide/dependencies.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/guide/dependencies.md b/docs/guide/dependencies.md index 1ca4d2e859..e78483d79e 100644 --- a/docs/guide/dependencies.md +++ b/docs/guide/dependencies.md @@ -11,8 +11,10 @@ features. | [core-js](https://github.com/zloirock/core-js) | The MIT License | Denis Pushkarev | | [tiny-emitter](https://github.com/scottcorgan/tiny-emitter) | The MIT License | Scott Corgan | | [regenerator-runtime](https://github.com/facebook/regenerator/tree/master/packages/regenerator-runtime) | The MIT License | Facebook, Inc. | +| [jStat](https://github.com/jstat/jstat) | The MIT License | jStat | +| [bessel](https://github.com/SheetJS/bessel) | Apache License | SheetJS |
We want to express gratitude and appreciation for all of the hard -work put into these awesome libraries by their authors and maintainers. \ No newline at end of file +work put into these awesome libraries by their authors and maintainers. From 98156dc7f0de403aea6558ad734bce85aea43d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Fri, 30 Oct 2020 14:57:10 +0100 Subject: [PATCH 26/30] Update docs/guide/dependencies.md Co-authored-by: Wojciech Czerniak --- docs/guide/dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/dependencies.md b/docs/guide/dependencies.md index e78483d79e..a572e916b9 100644 --- a/docs/guide/dependencies.md +++ b/docs/guide/dependencies.md @@ -12,7 +12,7 @@ features. | [tiny-emitter](https://github.com/scottcorgan/tiny-emitter) | The MIT License | Scott Corgan | | [regenerator-runtime](https://github.com/facebook/regenerator/tree/master/packages/regenerator-runtime) | The MIT License | Facebook, Inc. | | [jStat](https://github.com/jstat/jstat) | The MIT License | jStat | -| [bessel](https://github.com/SheetJS/bessel) | Apache License | SheetJS | +| [bessel](https://github.com/SheetJS/bessel) | Apache v2.0 | SheetJS |
From 6f24b2c0c44f579e6d0f976161e4832dcc947e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Fri, 30 Oct 2020 14:57:22 +0100 Subject: [PATCH 27/30] Update src/interpreter/plugin/3rdparty/bessel/bessel.ts Co-authored-by: Wojciech Czerniak --- src/interpreter/plugin/3rdparty/bessel/bessel.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/interpreter/plugin/3rdparty/bessel/bessel.ts b/src/interpreter/plugin/3rdparty/bessel/bessel.ts index 3ebf1b3afc..3372f06c4e 100644 --- a/src/interpreter/plugin/3rdparty/bessel/bessel.ts +++ b/src/interpreter/plugin/3rdparty/bessel/bessel.ts @@ -1,10 +1,11 @@ /* eslint-disable */ -/* bessel.js (C) 2013-present SheetJS -- http://sheetjs.com */ /** * @license - * Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ + bessel.js (C) 2013-present SheetJS -- http://sheetjs.com + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION From 6b535c61f510638e463cc276e0d6ffcdad4060fc Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 30 Oct 2020 15:09:51 +0100 Subject: [PATCH 28/30] . --- .eslintignore | 3 +++ src/interpreter/plugin/3rdparty/bessel/bessel.ts | 1 - src/interpreter/plugin/3rdparty/jstat/jstat.ts | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintignore b/.eslintignore index 60236f7911..b6f90437ac 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,9 @@ # Common files node_modules +# 3rd party +src/interpreter/plugin/3rdparty + # Configurations *.config.js karma.* diff --git a/src/interpreter/plugin/3rdparty/bessel/bessel.ts b/src/interpreter/plugin/3rdparty/bessel/bessel.ts index 3372f06c4e..20d83e27a9 100644 --- a/src/interpreter/plugin/3rdparty/bessel/bessel.ts +++ b/src/interpreter/plugin/3rdparty/bessel/bessel.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ /** * @license bessel.js (C) 2013-present SheetJS -- http://sheetjs.com diff --git a/src/interpreter/plugin/3rdparty/jstat/jstat.ts b/src/interpreter/plugin/3rdparty/jstat/jstat.ts index fc78a65dea..2742f202de 100644 --- a/src/interpreter/plugin/3rdparty/jstat/jstat.ts +++ b/src/interpreter/plugin/3rdparty/jstat/jstat.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ /** * @license Copyright (c) 2013 jStat From d6178708b62a876f2d7c7a67c5e67137432c9afb Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 31 Oct 2020 20:09:29 +0100 Subject: [PATCH 29/30] compatibility --- test/interpreter/function-beta.dist.spec.ts | 2 ++ test/interpreter/function-beta.inv.spec.ts | 2 +- test/interpreter/function-binom.dist.spec.ts | 1 + test/interpreter/function-binom.inv.spec.ts | 3 ++- test/interpreter/function-gamma.spec.ts | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/interpreter/function-beta.dist.spec.ts b/test/interpreter/function-beta.dist.spec.ts index d29385969c..2f4170464d 100644 --- a/test/interpreter/function-beta.dist.spec.ts +++ b/test/interpreter/function-beta.dist.spec.ts @@ -10,6 +10,7 @@ describe('Function BETA.DIST', () => { ['=BETA.DIST(1, 2, 3, 4, 5, 6, 7)'], ]) + //product #1 returns 1 expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) }) @@ -62,6 +63,7 @@ describe('Function BETA.DIST', () => { expect(engine.getCellValue(adr('A2'))).toBeCloseTo(0.8125, 6) }) + //product #1 returns 0 for tests 1,2,4,5 it('checks bounds', () => { const engine = HyperFormula.buildFromArray([ ['=BETA.DIST(0, 1, 1, FALSE())'], diff --git a/test/interpreter/function-beta.inv.spec.ts b/test/interpreter/function-beta.inv.spec.ts index acf75c8c30..2eac5ecee0 100644 --- a/test/interpreter/function-beta.inv.spec.ts +++ b/test/interpreter/function-beta.inv.spec.ts @@ -63,7 +63,7 @@ describe('Function BETA.INV', () => { expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) - expect(engine.getCellValue(adr('A4'))).toEqual(1) + expect(engine.getCellValue(adr('A4'))).toEqual(1) //product #2 returns NUM expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) expect(engine.getCellValue(adr('A6'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.WrongOrder)) }) diff --git a/test/interpreter/function-binom.dist.spec.ts b/test/interpreter/function-binom.dist.spec.ts index 3e756b9c87..849761b570 100644 --- a/test/interpreter/function-binom.dist.spec.ts +++ b/test/interpreter/function-binom.dist.spec.ts @@ -70,6 +70,7 @@ describe('Function BINOM.DIST', () => { expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) + //product #2 returns 1 for following test expect(engine.getCellValue(adr('A3'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.WrongOrder)) expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) diff --git a/test/interpreter/function-binom.inv.spec.ts b/test/interpreter/function-binom.inv.spec.ts index 8aee1194b1..04fb19eff0 100644 --- a/test/interpreter/function-binom.inv.spec.ts +++ b/test/interpreter/function-binom.inv.spec.ts @@ -154,10 +154,11 @@ describe('Function BINOM.INV', () => { ]) expect(engine.getCellValue(adr('A1'))).toEqual(0) + //product #1 returns 0 for the following test expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) //both products #1 and #2 return NUM for '=BINOM.INV(10, 0, 0.5)', which is incorrect expect(engine.getCellValue(adr('A3'))).toEqual(0) - //both products #1 and #2 return NUM for '=BINOM.INV(10, 10, 0.5)', which is incorrect + //both products #1 and #2 return NUM for '=BINOM.INV(10, 1, 0.5)', which is incorrect expect(engine.getCellValue(adr('A4'))).toEqual(10) expect(engine.getCellValue(adr('A5'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueSmall)) expect(engine.getCellValue(adr('A6'))).toEqualError(detailedError(ErrorType.NUM, ErrorMessage.ValueLarge)) diff --git a/test/interpreter/function-gamma.spec.ts b/test/interpreter/function-gamma.spec.ts index 1ca1d32f5b..4f1b8678ff 100644 --- a/test/interpreter/function-gamma.spec.ts +++ b/test/interpreter/function-gamma.spec.ts @@ -32,6 +32,7 @@ describe('Function GAMMA', () => { expect(engine.getCellValue(adr('A1'))).toEqual(1) expect(engine.getCellValue(adr('A2'))).toBeCloseTo(1.77245385588014, 6) expect(engine.getCellValue(adr('A3')) as number / 1133278.39212948).toBeCloseTo(1, 6) + //product #1 returns NUM for the following test expect(engine.getCellValue(adr('A4'))).toBeCloseTo(-0.94530871782981, 6) }) From ade53fb2ad29a21cf4e9c91d084157360f9261eb Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 31 Oct 2020 20:13:32 +0100 Subject: [PATCH 30/30] limitations --- docs/guide/known-limitations.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/guide/known-limitations.md b/docs/guide/known-limitations.md index 23622cfc98..b92197cb25 100644 --- a/docs/guide/known-limitations.md +++ b/docs/guide/known-limitations.md @@ -35,6 +35,10 @@ you can't compare the arguments in a formula like this: * Structured references ("Tables") * Currency data type * Relative named expressions - * We immediately instantiate references to single cells to their values instead of treating them as 1-length ranges, which slightly changes behavior of some functions (e.g. NPV). * Functions cannot use UI metadata (e.g. hidden rows for SUBTOTAL). - * SUBTOTAL function does not ignore nested subtotals. + +## Nuances of the implemented functions +* We immediately instantiate references to single cells to their values instead of treating them as 1-length ranges, which slightly changes behavior of some functions (e.g. NPV). +* SUBTOTAL function does not ignore nested subtotals. +* CHISQ.INV, CHISQ.INV.RT, CHISQ.DIST.RT, CHIDIST, CHIINV and CHISQ.DIST (CHISQ.DIST in CDF mode): Running time grows linearly with the value of the second parameter, degrees_of_freedom (slow for values>1e7). +* GAMMA.DIST, GAMMA.INV, GAMMADIST, GAMMAINV (GAMMA.DIST and GAMMADIST in CDF mode): Running time grows linearly with the value of the second parameter, alpha (slow for values>1e7).