diff --git a/CHANGELOG.md b/CHANGELOG.md index 787c642ad9..adb8185724 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Added support for the new license key format. A proprietary key can now grant a subset of the library: a function your key does not include evaluates to a `#LIC!` error, and the matching parts of the API throw a `LicenseCapabilityMissingError`. `getAvailableFunctions()` and `getFunctionDetails()` describe only the functions your key includes. [#1728](https://github.com/handsontable/hyperformula/pull/1728) +- Added a new function: `ERF.PRECISE`. [#1789](https://github.com/handsontable/hyperformula/pull/1789) ### Changed @@ -19,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Fixed - Fixed the validation of classic (25-character) license keys depending on the time zone: east of UTC, a key that expired the day before the build was released was still accepted, and west of UTC, the console message printed an expiry date one day too early. [#1728](https://github.com/handsontable/hyperformula/pull/1728) +- Fixed the `ERF` and `ERFC` functions returning a number for a boolean or an empty text argument instead of `#VALUE!` as Excel does; numeric text, dates and empty cells are still accepted. [#1789](https://github.com/handsontable/hyperformula/pull/1789) - Fixed the `AVERAGEIF` function returning a division-by-zero error when the calculated average was `0`. [#1733](https://github.com/handsontable/hyperformula/pull/1733) - Fixed the localized names of `VSTACK` and `HSTACK` in 14 language packs to match Microsoft Excel. [#1748](https://github.com/handsontable/hyperformula/pull/1748) - Fixed the MAXPOOL and MEDIANPOOL functions throwing an uncaught `TypeError` instead of returning the `#VALUE!` error when the range dimensions are not a whole multiple of the window size and the stride. [#1718](https://github.com/handsontable/hyperformula/pull/1718) diff --git a/docs/guide/list-of-differences.md b/docs/guide/list-of-differences.md index a109f82616..3016237c6b 100644 --- a/docs/guide/list-of-differences.md +++ b/docs/guide/list-of-differences.md @@ -127,3 +127,4 @@ A few of the rows above share a root cause worth stating once: - **Rounding toward zero, not down.** `INT` discards the fractional part rather than rounding toward negative infinity, so it differs from Excel and Google Sheets for negative input only. `ROUNDDOWN`/`ROUNDUP` are unaffected — they are defined in terms of zero in all three. - **`ISEVEN`/`ISODD` do not truncate.** They test the remainder of the value as given, so a value with a fractional part returns `FALSE` from *both*. Excel and Google Sheets truncate to an integer first, so exactly one of the two is always `TRUE`. - **`CEILING.MATH`/`FLOOR.MATH` honour only `mode` = 1.** Excel and Google Sheets switch the negative-number rounding direction for any non-zero `mode`. +- **`ERF.PRECISE`, like `ERF` and `ERFC`, loses relative accuracy for arguments very close to zero.** `=ERF.PRECISE(1E-10)` returns `1.128379612E-10` where Excel returns `1.12837916709551E-10`, and `=ERF.PRECISE(1E-300)` returns about `1.1E-16` where Excel returns `1.12837916709551E-300`. The comparison with Excel was measured for arguments of `0.1` and above (agreement to about 11 significant digits, the precision HyperFormula returns by default) and for `1E-10` and `1E-300`; values in between were not measured. diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index fea286083c..009e6bed67 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'EOMONTH', ERF: 'ERF', ERFC: 'ERFC', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'ZAOKROUHLIT.NA.SUDÉ', EXACT: 'STEJNÉ', EXP: 'EXP', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 9264551249..7ea2dfe1b0 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'SLUT.PÅ.MÅNED', ERF: 'FEJLFUNK', ERFC: 'FEJLFUNK.KOMP', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'LIGE', EXACT: 'EKSAKT', EXP: 'EKSP', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 4d3d39319b..01e5d098ef 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'MONATSENDE', ERF: 'GAUSSFEHLER', ERFC: 'GAUSSFKOMPL', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'GERADE', EXACT: 'IDENTISCH', EXP: 'EXP', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index d271b732cd..120b274318 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'EOMONTH', ERF: 'ERF', ERFC: 'ERFC', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'EVEN', EXACT: 'EXACT', EXP: 'EXP', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 9c6b817b98..0fb6da9564 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -103,6 +103,7 @@ export const dictionary: RawTranslationPackage = { EOMONTH: 'FIN.MES', ERF: 'FUN.ERROR', ERFC: 'FUN.ERROR.COMPL', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'REDONDEA.PAR', EXACT: 'IGUAL', EXP: 'EXP', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 99ff05708f..4ca7585416 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'KUUKAUSI.LOPPU', ERF: 'VIRHEFUNKTIO', ERFC: 'VIRHEFUNKTIO.KOMPLEMENTTI', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'PARILLINEN', EXACT: 'VERTAA', EXP: 'EKSPONENTTI', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index f7b9be45f7..74037fc54e 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'FIN.MOIS', ERF: 'ERF', ERFC: 'ERFC', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'PAIR', EXACT: 'EXACT', EXP: 'EXP', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index e40219553a..346097af74 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'HÓNAP.UTOLSÓ.NAP', ERF: 'HIBAF', ERFC: 'HIBAF.KOMPLEMENTER', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'PÁROS', EXACT: 'AZONOS', EXP: 'KITEVŐ', diff --git a/src/i18n/languages/idID.ts b/src/i18n/languages/idID.ts index 719cb11db6..b720c00124 100644 --- a/src/i18n/languages/idID.ts +++ b/src/i18n/languages/idID.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'AKHIR.BULAN', ERF: 'ERF', ERFC: 'ERFC', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'GENAP', EXACT: 'IDENTIK', EXP: 'EXP', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 1a494628f8..0c5f59d8aa 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'FINE.MESE', ERF: 'FUNZ.ERRORE', ERFC: 'FUNZ.ERRORE.COMP', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'PARI', EXACT: 'IDENTICO', EXP: 'EXP', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index ff15415a98..2212562900 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'MÅNEDSSLUTT', ERF: 'FEILF', ERFC: 'FEILFK', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'AVRUND.TIL.PARTALL', EXACT: 'EKSAKT', EXP: 'EKSP', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index c491e2f44b..37f491104c 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'LAATSTE.DAG', ERF: 'FOUTFUNCTIE', ERFC: 'FOUT.COMPLEMENT', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'EVEN', EXACT: 'GELIJK', EXP: 'EXP', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 6fa4f016b4..7ef5ee78ac 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'NR.SER.OST.DN.MIEŚ', ERF: 'FUNKCJA.BŁ', ERFC: 'KOMP.FUNKCJA.BŁ', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'ZAOKR.DO.PARZ', EXACT: 'PORÓWNAJ', EXP: 'EXP', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 6c1e68c328..ba5e19eef7 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'FIMMÊS', ERF: 'FUNERRO', ERFC: 'FUNERROCOMPL', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'PAR', EXACT: 'EXATO', EXP: 'EXP', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 6b35c1ce2f..1c7074600f 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'КОНМЕСЯЦА', ERF: 'ФОШ', ERFC: 'ДФОШ', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'ЧЁТН', EXACT: 'СОВПАД', EXP: 'EXP', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 5f87580087..a31248fe73 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'SLUTMÅNAD', ERF: 'FELF', ERFC: 'FELFK', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'JÄMN', EXACT: 'EXAKT', EXP: 'EXP', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 93c2176d37..e23812edd8 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = { EOMONTH: 'SERİAY', ERF: 'HATAİŞLEV', ERFC: 'TÜMHATAİŞLEV', + 'ERF.PRECISE': 'ERF.PRECISE', EVEN: 'ÇİFT', EXACT: 'ÖZDEŞ', EXP: 'ÜS', diff --git a/src/interpreter/functionMetadata/categories/engineering.ts b/src/interpreter/functionMetadata/categories/engineering.ts index 04b49aca14..af3a9766a6 100644 --- a/src/interpreter/functionMetadata/categories/engineering.ts +++ b/src/interpreter/functionMetadata/categories/engineering.ts @@ -143,6 +143,13 @@ export const ENGINEERING_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=ERFC(1)', '=ERFC(0.5)'], }, + 'ERF.PRECISE': { + category: 'Engineering', + shortDescription: 'Returns values of the Gaussian error integral.', + parameters: [{name: 'x', description: 'The upper limit of the integral, calculated from 0 to x.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=ERF.PRECISE(1)', '=ERF.PRECISE(-0.5)'], + }, HEX2BIN: { category: 'Engineering', shortDescription: 'The result is the binary number for the hexadecimal number entered.', diff --git a/src/interpreter/plugin/StatisticalPlugin.ts b/src/interpreter/plugin/StatisticalPlugin.ts index 54fd6e2dbc..640c25f50e 100644 --- a/src/interpreter/plugin/StatisticalPlugin.ts +++ b/src/interpreter/plugin/StatisticalPlugin.ts @@ -7,7 +7,7 @@ import {CellError, ErrorType} from '../../Cell' import {ErrorMessage} from '../../error-message' import {ProcedureAst} from '../../parser' import {InterpreterState} from '../InterpreterState' -import {InterpreterValue} from '../InterpreterValue' +import {getRawValue, InternalScalarValue, InterpreterValue} from '../InterpreterValue' import {besseli, besselj, besselk, bessely} from './3rdparty/bessel/bessel' import { beta, @@ -37,14 +37,20 @@ export class StatisticalPlugin extends FunctionPlugin implements FunctionPluginT 'ERF': { method: 'erf', parameters: [ - {argumentType: FunctionArgumentType.NUMBER}, - {argumentType: FunctionArgumentType.NUMBER, optionalArg: true}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, optionalArg: true}, ] }, 'ERFC': { method: 'erfc', parameters: [ - {argumentType: FunctionArgumentType.NUMBER} + {argumentType: FunctionArgumentType.SCALAR} + ] + }, + 'ERF.PRECISE': { + method: 'erfprecise', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR} ] }, 'EXPON.DIST': { @@ -426,18 +432,47 @@ export class StatisticalPlugin extends FunctionPlugin implements FunctionPluginT POISSONDIST: 'POISSON.DIST', } + /** + * Converts an argument the way Excel does for the error functions: numbers, dates, numeric text and empty cells are + * accepted, while a boolean or the empty text is #VALUE!. Errors are passed on. + */ + private strictNumber(value: InternalScalarValue): number | CellError { + if (value instanceof CellError) { + return value + } + if (typeof value === 'boolean' || value === '') { + return new CellError(ErrorType.VALUE, ErrorMessage.NumberCoercion) + } + const coerced = this.coerceScalarToNumberOrError(value) + return coerced instanceof CellError ? coerced : getRawValue(coerced) + } + public erf(ast: ProcedureAst, state: InterpreterState): InterpreterValue { - return this.runFunction(ast.args, state, this.metadata('ERF'), (lowerBound, upperBound) => { - if (upperBound === undefined) { + return this.runFunction(ast.args, state, this.metadata('ERF'), (lower: InternalScalarValue, upper?: InternalScalarValue) => { + const lowerBound = this.strictNumber(lower) + if (lowerBound instanceof CellError) { + return lowerBound + } + if (upper === undefined) { return erf(lowerBound) - } else { - return erf(upperBound) - erf(lowerBound) } + const upperBound = this.strictNumber(upper) + return upperBound instanceof CellError ? upperBound : erf(upperBound) - erf(lowerBound) }) } public erfc(ast: ProcedureAst, state: InterpreterState): InterpreterValue { - return this.runFunction(ast.args, state, this.metadata('ERFC'), erfc) + return this.runFunction(ast.args, state, this.metadata('ERFC'), (x: InternalScalarValue) => { + const value = this.strictNumber(x) + return value instanceof CellError ? value : erfc(value) + }) + } + + public erfprecise(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('ERF.PRECISE'), (x: InternalScalarValue) => { + const value = this.strictNumber(x) + return value instanceof CellError ? value : erf(value) + }) } public expondist(ast: ProcedureAst, state: InterpreterState): InterpreterValue { diff --git a/src/license/functionCapabilities.ts b/src/license/functionCapabilities.ts index a26733ec5f..36aff75008 100644 --- a/src/license/functionCapabilities.ts +++ b/src/license/functionCapabilities.ts @@ -82,7 +82,7 @@ const UNGROUPED_FUNCTIONS = [ 'CONFIDENCE.NORM', 'CONFIDENCE.T', 'CORREL', 'COSH', 'COT', 'COTH', 'COUNTUNIQUE', 'COVARIANCE.P', 'COVARIANCE.S', 'CSC', 'CSCH', 'CUMIPMT', 'CUMPRINC', 'DAVERAGE', 'DB', 'DCOUNT', 'DCOUNTA', 'DDB', 'DEC2BIN', 'DEC2OCT', 'DECIMAL', 'DEGREES', 'DELTA', 'DEVSQ', 'DGET', 'DMAX', 'DMIN', 'DOLLARDE', - 'DOLLARFR', 'DPRODUCT', 'DSTDEV', 'DSTDEVP', 'DSUM', 'DVAR', 'DVARP', 'EFFECT', 'ERF', 'ERFC', + 'DOLLARFR', 'DPRODUCT', 'DSTDEV', 'DSTDEVP', 'DSUM', 'DVAR', 'DVARP', 'EFFECT', 'ERF', 'ERF.PRECISE', 'ERFC', 'EXPON.DIST', 'F.DIST', 'F.DIST.RT', 'F.INV', 'F.INV.RT', 'F.TEST', 'FACT', 'FACTDOUBLE', 'FISHER', 'FISHERINV', 'FLOOR.MATH', 'FLOOR.PRECISE', 'FORMULATEXT', 'FVSCHEDULE', 'GAMMA', 'GAMMA.DIST', 'GAMMA.INV', 'GAMMALN', 'GAUSS', 'GCD', 'GEOMEAN', 'HARMEAN', 'HEX2BIN', 'HEX2OCT', 'HYPGEOM.DIST',