@trebco/treb 30.3.2 → 30.6.2

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.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v30.3. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v30.6. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
 
3
3
  /**
4
4
  * add our tag to the map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "30.3.2",
3
+ "version": "30.6.2",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -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: 'range1', },
619
- { name: 'criteria1', },
620
- { name: 'range2', },
621
- { name: 'criteria2', }
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
- let count = 0;
626
-
627
- const result = CountIfInternal(args[0], args[1]);
628
- if (result.type !== ValueType.array) {
629
- return result; // error
630
- }
631
-
632
- const base = FlattenBooleans(result);
633
-
634
- for (let i = 2; i < args.length; i += 2) {
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
- const step = FlattenBooleans(result);
643
- for (const [index, value] of base.entries()) {
644
- base[index] = value && step[index];
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
- for (const element of base) {
651
- if (element) { count++; }
652
- }
653
-
654
- return {
655
- type: ValueType.number,
656
- value: count,
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 Lotus date from parts',
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 = new Date();
530
- date.setMilliseconds(0);
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
- if (year < 1899) { year += 1900; }
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
- let value = 0;
2163
+ 'Norm.S.Dist': {
2143
2164
 
2144
- if (cumulative) {
2145
- const sign = (x < mean) ? -1 : 1;
2146
- value = 0.5 * (1.0 + sign * erf((Math.abs(x - mean)) / (stdev * Math.sqrt(2))));
2147
- }
2148
- else {
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
- return {
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