@trebco/treb 30.3.2 → 30.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/treb-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +14 -14
- package/dist/treb.d.ts +1 -1
- package/package.json +1 -1
- package/treb-base-types/src/cells.ts +1 -1
- package/treb-calculator/src/calculator.ts +108 -69
- package/treb-calculator/src/complex-math.ts +48 -0
- package/treb-calculator/src/dag/graph.ts +4 -1
- package/treb-calculator/src/functions/base-functions.ts +106 -34
- package/treb-calculator/src/functions/date-utils.ts +64 -0
- package/treb-calculator/src/functions/finance-functions.ts +62 -0
- package/treb-calculator/src/functions/statistics-functions.ts +172 -0
- package/treb-calculator/src/functions/text-functions.ts +25 -0
- package/treb-calculator/src/utilities.ts +18 -4
- package/treb-charts/src/chart-utils.ts +42 -27
- package/treb-data-model/src/types.ts +10 -0
- package/treb-embed/src/embedded-spreadsheet.ts +14 -14
- package/treb-embed/style/dark-theme.scss +1 -1
- package/treb-export/src/export.ts +22 -1
- package/treb-grid/src/editors/overlay_editor.ts +1 -1
- package/treb-grid/src/types/grid.ts +9 -1
package/dist/treb.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -709,7 +709,7 @@ export class Cells {
|
|
|
709
709
|
typeof cell.calculated !== 'undefined') { // && cell.calculated_type !== ValueType.error) {
|
|
710
710
|
obj.calculated = cell.calculated;
|
|
711
711
|
if (cell.spill) {
|
|
712
|
-
obj.spill = cell.spill;
|
|
712
|
+
obj.spill = cell.spill.toJSON();
|
|
713
713
|
}
|
|
714
714
|
|
|
715
715
|
// always preserve error type, because we can't infer
|
|
@@ -253,6 +253,7 @@ export class Calculator extends Graph {
|
|
|
253
253
|
// special functions... need reference to the graph (this)
|
|
254
254
|
// moving countif here so we can reference it in COUNTIFS...
|
|
255
255
|
|
|
256
|
+
/*
|
|
256
257
|
const FlattenBooleans = (value: ArrayUnion) => {
|
|
257
258
|
const result: boolean[] = [];
|
|
258
259
|
for (const col of value.value) {
|
|
@@ -262,6 +263,58 @@ export class Calculator extends Graph {
|
|
|
262
263
|
}
|
|
263
264
|
return result;
|
|
264
265
|
};
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* this is a function that does sumif/averageif/countif.
|
|
270
|
+
* args is one or more sets of [criteria_range, criteria]
|
|
271
|
+
*/
|
|
272
|
+
const XIf = (type: 'sum'|'count'|'average', value_range: CellValue[][], ...args: unknown[]): UnionValue => {
|
|
273
|
+
|
|
274
|
+
const filter: boolean[] = [];
|
|
275
|
+
|
|
276
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
277
|
+
if (Array.isArray(args[i])) {
|
|
278
|
+
const step = CountIfInternal(args[i] as CellValue[][], args[i+1] as CellValue);
|
|
279
|
+
if (step.type !== ValueType.array) {
|
|
280
|
+
return step;
|
|
281
|
+
}
|
|
282
|
+
for (const [r, cell] of step.value[0].entries()) {
|
|
283
|
+
filter[r] = (!!cell.value && (filter[r] !== false));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const values = Utilities.FlattenCellValues(value_range, true); // keep undefineds
|
|
289
|
+
|
|
290
|
+
let count = 0;
|
|
291
|
+
let sum = 0;
|
|
292
|
+
for (const [index, test] of filter.entries()) {
|
|
293
|
+
if (test) {
|
|
294
|
+
count++;
|
|
295
|
+
const value = values[index];
|
|
296
|
+
if (typeof value === 'number') {
|
|
297
|
+
sum += value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
switch (type) {
|
|
303
|
+
case 'count':
|
|
304
|
+
return { type: ValueType.number, value: count };
|
|
305
|
+
|
|
306
|
+
case 'sum':
|
|
307
|
+
return { type: ValueType.number, value: sum };
|
|
308
|
+
|
|
309
|
+
case 'average':
|
|
310
|
+
if (count === 0) {
|
|
311
|
+
return DivideByZeroError();
|
|
312
|
+
}
|
|
313
|
+
return { type: ValueType.number, value: sum/count };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
}
|
|
265
318
|
|
|
266
319
|
const CountIfInternal = (range: CellValue[][], criteria: CellValue): UnionValue => {
|
|
267
320
|
|
|
@@ -271,7 +324,7 @@ export class Calculator extends Graph {
|
|
|
271
324
|
// in any event there are no dynamic dependencies with this
|
|
272
325
|
// function.
|
|
273
326
|
|
|
274
|
-
const data = Utilities.FlattenCellValues(range);
|
|
327
|
+
const data = Utilities.FlattenCellValues(range, true); // keep undefineds, important for mapping
|
|
275
328
|
|
|
276
329
|
let parse_result: ParseResult|undefined;
|
|
277
330
|
let expression: ExpressionUnit|undefined;
|
|
@@ -326,7 +379,7 @@ export class Calculator extends Graph {
|
|
|
326
379
|
}
|
|
327
380
|
|
|
328
381
|
if (expression?.type === 'call' && expression.args[0]?.type === 'array') {
|
|
329
|
-
expression.args[0].values = [Utilities.FilterIntrinsics(data)];
|
|
382
|
+
expression.args[0].values = [Utilities.FilterIntrinsics(data, true)];
|
|
330
383
|
}
|
|
331
384
|
|
|
332
385
|
}
|
|
@@ -377,7 +430,7 @@ export class Calculator extends Graph {
|
|
|
377
430
|
}
|
|
378
431
|
}
|
|
379
432
|
|
|
380
|
-
expression.left.values = [Utilities.FilterIntrinsics(data)];
|
|
433
|
+
expression.left.values = [Utilities.FilterIntrinsics(data, true)];
|
|
381
434
|
|
|
382
435
|
}
|
|
383
436
|
|
|
@@ -388,22 +441,6 @@ export class Calculator extends Graph {
|
|
|
388
441
|
const result = this.CalculateExpression(expression);
|
|
389
442
|
return result;
|
|
390
443
|
|
|
391
|
-
/*
|
|
392
|
-
// console.info({expression, result});
|
|
393
|
-
|
|
394
|
-
if (result.type === ValueType.array) {
|
|
395
|
-
let count = 0;
|
|
396
|
-
for (const column of (result as ArrayUnion).value) {
|
|
397
|
-
for (const cell of column) {
|
|
398
|
-
if (cell.value) { count++; }
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return { type: ValueType.number, value: count };
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return result; // error?
|
|
405
|
-
*/
|
|
406
|
-
|
|
407
444
|
};
|
|
408
445
|
|
|
409
446
|
this.library.Register({
|
|
@@ -615,47 +652,63 @@ export class Calculator extends Graph {
|
|
|
615
652
|
*/
|
|
616
653
|
CountIfs: {
|
|
617
654
|
arguments: [
|
|
618
|
-
{ name: '
|
|
619
|
-
{ name: '
|
|
620
|
-
{ name: '
|
|
621
|
-
{ name: '
|
|
655
|
+
{ name: 'range', },
|
|
656
|
+
{ name: 'criteria', },
|
|
657
|
+
{ name: 'range', },
|
|
658
|
+
{ name: 'criteria', }
|
|
622
659
|
],
|
|
623
660
|
fn: (...args): UnionValue => {
|
|
661
|
+
return XIf('count', args[0], ...args);
|
|
662
|
+
},
|
|
663
|
+
},
|
|
624
664
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if (args[i] && args[i + 1]) {
|
|
636
|
-
|
|
637
|
-
const result = CountIfInternal(args[i], args[i+1]);
|
|
638
|
-
if (result.type !== ValueType.array) {
|
|
639
|
-
return result;
|
|
640
|
-
}
|
|
665
|
+
/** @see CountIf */
|
|
666
|
+
AverageIf: {
|
|
667
|
+
arguments: [
|
|
668
|
+
{ name: 'range', },
|
|
669
|
+
{ name: 'criteria', },
|
|
670
|
+
],
|
|
671
|
+
fn: (range: CellValue[][], criteria: CellValue, average_range?: CellValue[][]) => {
|
|
672
|
+
return XIf('average', average_range||range, range, criteria);
|
|
673
|
+
},
|
|
674
|
+
},
|
|
641
675
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
676
|
+
/** @see CountIf */
|
|
677
|
+
AverageIfs: {
|
|
678
|
+
arguments: [
|
|
679
|
+
{ name: 'value range', },
|
|
680
|
+
{ name: 'criteria range', },
|
|
681
|
+
{ name: 'criteria', },
|
|
682
|
+
{ name: 'criteria range', },
|
|
683
|
+
{ name: 'criteria', },
|
|
684
|
+
],
|
|
685
|
+
fn: (range: CellValue[][], ...args) => {
|
|
686
|
+
return XIf('average', range, ...args);
|
|
687
|
+
},
|
|
688
|
+
},
|
|
649
689
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
690
|
+
/** @see CountIf */
|
|
691
|
+
SumIf: {
|
|
692
|
+
arguments: [
|
|
693
|
+
{ name: 'range', },
|
|
694
|
+
{ name: 'criteria', },
|
|
695
|
+
],
|
|
696
|
+
fn: (range: CellValue[][], criteria: CellValue, sum_range?: CellValue[][]) => {
|
|
697
|
+
return XIf('sum', sum_range||range, range, criteria);
|
|
698
|
+
},
|
|
699
|
+
},
|
|
658
700
|
|
|
701
|
+
/** @see CountIf */
|
|
702
|
+
SumIfs: {
|
|
703
|
+
arguments: [
|
|
704
|
+
{ name: 'value range', },
|
|
705
|
+
{ name: 'criteria range', },
|
|
706
|
+
{ name: 'criteria', },
|
|
707
|
+
{ name: 'criteria range', },
|
|
708
|
+
{ name: 'criteria', },
|
|
709
|
+
],
|
|
710
|
+
fn: (range: CellValue[][], ...args) => {
|
|
711
|
+
return XIf('sum', range, ...args);
|
|
659
712
|
},
|
|
660
713
|
},
|
|
661
714
|
|
|
@@ -680,21 +733,7 @@ export class Calculator extends Graph {
|
|
|
680
733
|
{ name: 'criteria', }
|
|
681
734
|
],
|
|
682
735
|
fn: (range, criteria): UnionValue => {
|
|
683
|
-
|
|
684
|
-
const result = CountIfInternal(range, criteria);
|
|
685
|
-
|
|
686
|
-
if (result.type === ValueType.array) {
|
|
687
|
-
let count = 0;
|
|
688
|
-
for (const column of (result as ArrayUnion).value) {
|
|
689
|
-
for (const cell of column) {
|
|
690
|
-
if (cell.value) { count++; }
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
return { type: ValueType.number, value: count };
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
return result; // error
|
|
697
|
-
|
|
736
|
+
return XIf('count', range, range, criteria);
|
|
698
737
|
},
|
|
699
738
|
},
|
|
700
739
|
|
|
@@ -99,6 +99,54 @@ export const RectangularToPolar = (value: Complex): { r: number, theta: number }
|
|
|
99
99
|
return { r, theta };
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
export const Tan = (z: Complex) => {
|
|
103
|
+
|
|
104
|
+
// tan(a+bi) = (tan(a) + i tanh(b)) / (1 - i tan(a) tanh(b))
|
|
105
|
+
|
|
106
|
+
return Divide({
|
|
107
|
+
real: Math.tan(z.real),
|
|
108
|
+
imaginary: Math.tanh(z.imaginary),
|
|
109
|
+
}, {
|
|
110
|
+
real: 1,
|
|
111
|
+
imaginary: -(Math.tan(z.real) * Math.tanh(z.imaginary)),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const Cos = (z: Complex) => {
|
|
117
|
+
|
|
118
|
+
// sin(a+bi) = cos(a) cosh(b) + i sin(a) sinh(b)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
real: Math.cos(z.real) * Math.cosh(z.imaginary),
|
|
122
|
+
imaginary: Math.sin(z.real) * Math.sinh(z.imaginary),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
export const Sin = (z: Complex) => {
|
|
129
|
+
|
|
130
|
+
// sin(a+bi) = sin(a) cosh(b) + i cos(a) sinh(b)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
real: Math.sin(z.real) * Math.cosh(z.imaginary),
|
|
134
|
+
imaginary: Math.cos(z.real) * Math.sinh(z.imaginary),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const Product = (...args: Complex[]): Complex => {
|
|
140
|
+
let base = args.shift();
|
|
141
|
+
if (!base) {
|
|
142
|
+
return { real: 0, imaginary: 0 };
|
|
143
|
+
}
|
|
144
|
+
for (const arg of args) {
|
|
145
|
+
base = Multiply(base, arg);
|
|
146
|
+
}
|
|
147
|
+
return base;
|
|
148
|
+
};
|
|
149
|
+
|
|
102
150
|
export const Multiply = (a: Complex, b: Complex): Complex => {
|
|
103
151
|
return {
|
|
104
152
|
real: (a.real * b.real) - (a.imaginary * b.imaginary),
|
|
@@ -179,7 +179,10 @@ export abstract class Graph implements GraphCallbacks {
|
|
|
179
179
|
public GetVertex(address: ICellAddress, create?: boolean): SpreadsheetVertex | undefined {
|
|
180
180
|
|
|
181
181
|
if (!address.sheet_id) {
|
|
182
|
-
console.info({address, create});
|
|
182
|
+
console.info(JSON.stringify({address, create}));
|
|
183
|
+
console.trace();
|
|
184
|
+
|
|
185
|
+
|
|
183
186
|
throw new Error('getvertex with no sheet id');
|
|
184
187
|
}
|
|
185
188
|
|
|
@@ -35,8 +35,11 @@ import { ClickCheckbox, RenderCheckbox } from './checkbox';
|
|
|
35
35
|
import { UnionIsMetadata } from '../expression-calculator';
|
|
36
36
|
|
|
37
37
|
import { Exp as ComplexExp, Power as ComplexPower, Multiply as ComplexMultply } from '../complex-math';
|
|
38
|
+
import * as ComplexMath from '../complex-math';
|
|
39
|
+
|
|
38
40
|
import { CoerceComplex } from './function-utilities';
|
|
39
41
|
import type { UnitAddress, UnitRange } from 'treb-parser';
|
|
42
|
+
import { ConstructDate } from './date-utils';
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* BaseFunctionLibrary is a static object that has basic spreadsheet
|
|
@@ -77,6 +80,22 @@ const erf = (x: number): number => {
|
|
|
77
80
|
|
|
78
81
|
const sqrt2pi = Math.sqrt(2 * Math.PI);
|
|
79
82
|
|
|
83
|
+
const norm_dist = (x: number, mean: number, stdev: number, cumulative: boolean) => {
|
|
84
|
+
|
|
85
|
+
let value = 0;
|
|
86
|
+
|
|
87
|
+
if (cumulative) {
|
|
88
|
+
const sign = (x < mean) ? -1 : 1;
|
|
89
|
+
value = 0.5 * (1.0 + sign * erf((Math.abs(x - mean)) / (stdev * Math.sqrt(2))));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
value = Math.exp(-1/2 * Math.pow((x - mean) / stdev, 2)) / (stdev * sqrt2pi);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return value;
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
80
99
|
/** imprecise but reasonably fast normsinv function */
|
|
81
100
|
const inverse_normal = (q: number): number => {
|
|
82
101
|
|
|
@@ -93,6 +112,7 @@ const inverse_normal = (q: number): number => {
|
|
|
93
112
|
|
|
94
113
|
};
|
|
95
114
|
|
|
115
|
+
|
|
96
116
|
const edate_calc = (start: number, months: number) => {
|
|
97
117
|
|
|
98
118
|
let date = new Date(LotusDate(start));
|
|
@@ -519,36 +539,18 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
519
539
|
},
|
|
520
540
|
|
|
521
541
|
Date: {
|
|
522
|
-
description: 'Constructs a
|
|
542
|
+
description: 'Constructs a date from year/month/day',
|
|
523
543
|
arguments: [
|
|
524
544
|
{ name: 'year', unroll: true },
|
|
525
545
|
{ name: 'month', unroll: true },
|
|
526
546
|
{ name: 'day', unroll: true },
|
|
527
547
|
],
|
|
528
548
|
fn: (year: number, month: number, day: number) => {
|
|
529
|
-
const date =
|
|
530
|
-
date
|
|
531
|
-
date.setSeconds(0);
|
|
532
|
-
date.setMinutes(0);
|
|
533
|
-
date.setHours(0);
|
|
534
|
-
|
|
535
|
-
if (year < 0 || year > 10000) {
|
|
549
|
+
const date = ConstructDate(year, month, day);
|
|
550
|
+
if (date === false) {
|
|
536
551
|
return ArgumentError();
|
|
537
552
|
}
|
|
538
|
-
|
|
539
|
-
date.setFullYear(year);
|
|
540
|
-
|
|
541
|
-
if (month < 1 || month > 12) {
|
|
542
|
-
return ArgumentError();
|
|
543
|
-
}
|
|
544
|
-
date.setMonth(month - 1);
|
|
545
|
-
|
|
546
|
-
if (day < 1 || day > 31) {
|
|
547
|
-
return ArgumentError();
|
|
548
|
-
}
|
|
549
|
-
date.setDate(day);
|
|
550
|
-
|
|
551
|
-
return { type: ValueType.number, value: UnlotusDate(date.getTime()) };
|
|
553
|
+
return { type: ValueType.number, value: date };
|
|
552
554
|
},
|
|
553
555
|
},
|
|
554
556
|
|
|
@@ -2122,6 +2124,22 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2122
2124
|
}
|
|
2123
2125
|
},
|
|
2124
2126
|
|
|
2127
|
+
'Norm.S.Inv': {
|
|
2128
|
+
description: 'Inverse of the normal cumulative distribution',
|
|
2129
|
+
arguments: [
|
|
2130
|
+
{name: 'probability'},
|
|
2131
|
+
{name: 'mean', default: 0},
|
|
2132
|
+
{name: 'standard deviation', default: 1},
|
|
2133
|
+
],
|
|
2134
|
+
xlfn: true,
|
|
2135
|
+
fn: (q: number, mean = 0, stdev = 1): UnionValue => {
|
|
2136
|
+
return {
|
|
2137
|
+
type: ValueType.number,
|
|
2138
|
+
value: inverse_normal(q) * stdev + mean,
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
},
|
|
2142
|
+
|
|
2125
2143
|
'Norm.Dist': {
|
|
2126
2144
|
|
|
2127
2145
|
description: 'Cumulative normal distribution',
|
|
@@ -2138,22 +2156,22 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2138
2156
|
xlfn: true,
|
|
2139
2157
|
|
|
2140
2158
|
fn: (x: number, mean = 0, stdev = 1, cumulative = true): UnionValue => {
|
|
2159
|
+
return { type: ValueType.number, value: norm_dist(x, mean, stdev, cumulative) };
|
|
2160
|
+
},
|
|
2161
|
+
},
|
|
2141
2162
|
|
|
2142
|
-
|
|
2163
|
+
'Norm.S.Dist': {
|
|
2143
2164
|
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
value = Math.exp(-1/2 * Math.pow((x - mean) / stdev, 2)) / (stdev * sqrt2pi);
|
|
2150
|
-
}
|
|
2165
|
+
description: 'Cumulative normal distribution',
|
|
2166
|
+
arguments: [
|
|
2167
|
+
{name: 'value'},
|
|
2168
|
+
{name: 'cumulative', default: true},
|
|
2169
|
+
],
|
|
2151
2170
|
|
|
2152
|
-
|
|
2153
|
-
type: ValueType.number,
|
|
2154
|
-
value,
|
|
2155
|
-
};
|
|
2171
|
+
xlfn: true,
|
|
2156
2172
|
|
|
2173
|
+
fn: (x: number, cumulative = true): UnionValue => {
|
|
2174
|
+
return { type: ValueType.number, value: norm_dist(x, 0, 1, cumulative) };
|
|
2157
2175
|
},
|
|
2158
2176
|
},
|
|
2159
2177
|
|
|
@@ -2437,6 +2455,60 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2437
2455
|
return ArgumentError();
|
|
2438
2456
|
}
|
|
2439
2457
|
|
|
2458
|
+
},
|
|
2459
|
+
},
|
|
2460
|
+
|
|
2461
|
+
Sin: {
|
|
2462
|
+
arguments: [
|
|
2463
|
+
{ name: 'angle in radians', boxed: true, unroll: true, }
|
|
2464
|
+
],
|
|
2465
|
+
fn: (a: UnionValue) => {
|
|
2466
|
+
|
|
2467
|
+
if (a.type === ValueType.number) {
|
|
2468
|
+
return { type: ValueType.number, value: Math.sin(a.value) };
|
|
2469
|
+
}
|
|
2470
|
+
if (a.type === ValueType.complex) {
|
|
2471
|
+
return { type: ValueType.complex, value: ComplexMath.Sin(a.value) };
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
return ArgumentError();
|
|
2475
|
+
|
|
2476
|
+
},
|
|
2477
|
+
},
|
|
2478
|
+
|
|
2479
|
+
Cos: {
|
|
2480
|
+
arguments: [
|
|
2481
|
+
{ name: 'angle in radians', boxed: true, unroll: true, }
|
|
2482
|
+
],
|
|
2483
|
+
fn: (a: UnionValue) => {
|
|
2484
|
+
|
|
2485
|
+
if (a.type === ValueType.number) {
|
|
2486
|
+
return { type: ValueType.number, value: Math.cos(a.value) };
|
|
2487
|
+
}
|
|
2488
|
+
if (a.type === ValueType.complex) {
|
|
2489
|
+
return { type: ValueType.complex, value: ComplexMath.Cos(a.value) };
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
return ArgumentError();
|
|
2493
|
+
|
|
2494
|
+
},
|
|
2495
|
+
},
|
|
2496
|
+
|
|
2497
|
+
Tan: {
|
|
2498
|
+
arguments: [
|
|
2499
|
+
{ name: 'angle in radians', boxed: true, unroll: true, }
|
|
2500
|
+
],
|
|
2501
|
+
fn: (a: UnionValue) => {
|
|
2502
|
+
|
|
2503
|
+
if (a.type === ValueType.number) {
|
|
2504
|
+
return { type: ValueType.number, value: Math.tan(a.value) };
|
|
2505
|
+
}
|
|
2506
|
+
if (a.type === ValueType.complex) {
|
|
2507
|
+
return { type: ValueType.complex, value: ComplexMath.Tan(a.value) };
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
return ArgumentError();
|
|
2511
|
+
|
|
2440
2512
|
},
|
|
2441
2513
|
}
|
|
2442
2514
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of TREB.
|
|
3
|
+
*
|
|
4
|
+
* TREB is free software: you can redistribute it and/or modify it under the
|
|
5
|
+
* terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
* Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
* later version.
|
|
8
|
+
*
|
|
9
|
+
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
10
|
+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
11
|
+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
12
|
+
* details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License along
|
|
15
|
+
* with TREB. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2022-2024 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { UnlotusDate } from 'treb-format';
|
|
23
|
+
|
|
24
|
+
export const DaysInYear = (year: number) => {
|
|
25
|
+
return (year % 4 === 0 && (year % 100 !== 0 || year === 1900)) ? 366 : 365;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const DayOfYear = (year: number, month: number, day: number): number|false => {
|
|
29
|
+
const a = ConstructDate(year, 1, 1);
|
|
30
|
+
const b = ConstructDate(year, month, day);
|
|
31
|
+
if (a === false || b === false) { return false; }
|
|
32
|
+
return Math.round(b - a + 1);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const ConstructDate = (year: number, month: number, day: number): number|false => {
|
|
36
|
+
|
|
37
|
+
const date = new Date();
|
|
38
|
+
date.setMilliseconds(0);
|
|
39
|
+
date.setSeconds(0);
|
|
40
|
+
date.setMinutes(0);
|
|
41
|
+
date.setHours(0);
|
|
42
|
+
|
|
43
|
+
if (year < 0 || year > 10000) {
|
|
44
|
+
// return ArgumentError();
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (year < 1899) { year += 1900; }
|
|
48
|
+
date.setFullYear(year);
|
|
49
|
+
|
|
50
|
+
if (month < 1 || month > 12) {
|
|
51
|
+
// return ArgumentError();
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
date.setMonth(month - 1);
|
|
55
|
+
|
|
56
|
+
if (day < 1 || day > 31) {
|
|
57
|
+
// return ArgumentError();
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
date.setDate(day);
|
|
61
|
+
|
|
62
|
+
return UnlotusDate(date.getTime());
|
|
63
|
+
|
|
64
|
+
};
|
|
@@ -24,6 +24,8 @@ import { type CellValue, type UnionValue, ValueType } from 'treb-base-types';
|
|
|
24
24
|
import { FlattenCellValues } from '../utilities';
|
|
25
25
|
|
|
26
26
|
import { ArgumentError, ValueError } from '../function-error';
|
|
27
|
+
import { LotusDate, UnlotusDate } from 'treb-format';
|
|
28
|
+
import { ConstructDate, DaysInYear } from './date-utils';
|
|
27
29
|
|
|
28
30
|
// use a single, static object for base functions
|
|
29
31
|
|
|
@@ -84,8 +86,68 @@ const ppmt_function = (rate: number, period: number, periods: number, pv = 0, fv
|
|
|
84
86
|
ipmt_function(rate, period, periods, pv, fv, type);
|
|
85
87
|
};
|
|
86
88
|
|
|
89
|
+
|
|
90
|
+
|
|
87
91
|
export const FinanceFunctionLibrary: FunctionMap = {
|
|
88
92
|
|
|
93
|
+
/*
|
|
94
|
+
CoupNum: {
|
|
95
|
+
fn: (settlement: CellValue, maturity: CellValue, frequency: CellValue, basis: CellValue = 0) => {
|
|
96
|
+
|
|
97
|
+
if (typeof settlement !== 'number' || typeof maturity !== 'number' || settlement > maturity ) {
|
|
98
|
+
return ArgumentError();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (frequency !== 1 && frequency !== 2 && frequency !== 4) {
|
|
102
|
+
return ArgumentError();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (basis === 1) {
|
|
106
|
+
const settlement_date = LotusDate(settlement);
|
|
107
|
+
const maturity_date = LotusDate(maturity);
|
|
108
|
+
|
|
109
|
+
const comparison = ConstructDate(maturity_date.getUTCFullYear(), settlement_date.getUTCMonth() + 1, settlement_date.getUTCDay());
|
|
110
|
+
let years = Math.max(0, maturity_date.getUTCFullYear() - settlement_date.getUTCFullYear());
|
|
111
|
+
|
|
112
|
+
if (comparison && comparison < maturity) {
|
|
113
|
+
years += Math.round(maturity - comparison) / DaysInYear(maturity_date.getUTCFullYear());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
type: ValueType.number,
|
|
118
|
+
value: Math.round(years * frequency),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
type: ValueType.number,
|
|
124
|
+
value: 100,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
/*
|
|
132
|
+
Price: {
|
|
133
|
+
fn: (settlement: CellValue, maturity: CellValue, rate: CellValue, yld: CellValue, redemption: CellValue, frequency: CellValue, basis: CellValue = 0) => {
|
|
134
|
+
|
|
135
|
+
if (typeof settlement !== 'number' || typeof maturity !== 'number' || typeof rate !== 'number' || typeof yld !== 'number' || typeof redemption !== 'number') {
|
|
136
|
+
return ArgumentError();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (frequency !== 1 && frequency !== 2 && frequency !== 4) {
|
|
140
|
+
return ArgumentError();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
type: ValueType.number,
|
|
145
|
+
value: 100,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
*/
|
|
150
|
+
|
|
89
151
|
/**
|
|
90
152
|
* Excel's NPV function is somewhat broken because it assumes the first
|
|
91
153
|
* (usually negative) cashflow is in year 1, not year 0. so the thing to
|