@trebco/treb 31.1.1 → 31.3.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 v31.1. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v31.3. 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
@@ -908,7 +908,7 @@ export declare class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
908
908
  *
909
909
  * @public
910
910
  */
911
- FormatNumber(value: number, format?: string): string;
911
+ FormatNumber(value: number | Complex, format?: string): string;
912
912
 
913
913
  /**
914
914
  * convert a javascript date (or timestamp) to a spreadsheet date
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "31.1.1",
3
+ "version": "31.3.2",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
package/test/parser.ts ADDED
@@ -0,0 +1,13 @@
1
+
2
+ import { Parser } from '../treb-parser/src/parser.js';
3
+
4
+ const parser = new Parser();
5
+
6
+ let text = "=NORMDIST((LN(S/K)+(r+s^2/2)*T)/(s*SQRT(T)), 0, 1, TRUE)*S - NORMDIST((LN(S/K)+(r-s^2/2)*T)/(s*SQRT(T)), 0, 1, TRUE)*K*EXP(-r*T)";
7
+ let result = parser.Parse(text);
8
+ // console.info('valid?', result.valid, result.expression);
9
+
10
+ text = "=s*SQRT(T)";
11
+ result = parser.Parse(text);
12
+ console.info('valid?', result.valid, result.expression);
13
+
@@ -175,6 +175,110 @@ export const Sin = (z: Complex) => {
175
175
 
176
176
  };
177
177
 
178
+ export const ASin = (cpx: Complex) => {
179
+
180
+ if (cpx.imaginary === 0) {
181
+ return {
182
+ real: Math.asin(cpx.real),
183
+ imaginary: 0,
184
+ };
185
+ }
186
+
187
+ const x = Math.hypot(cpx.real + 1, cpx.imaginary) / 2;
188
+ const y = Math.hypot(cpx.real - 1, cpx.imaginary) / 2;
189
+
190
+ const sum = x + y;
191
+ const difference = x - y;
192
+
193
+ const result: Complex = {
194
+ real: Math.asin(difference),
195
+ imaginary: Math.log(sum + Math.sqrt(sum * sum - 1)),
196
+ };
197
+
198
+ if(cpx.imaginary < 0 || (cpx.imaginary === 0 && cpx.real > 1)) {
199
+ result.imaginary = -result.imaginary;
200
+ }
201
+
202
+ return result;
203
+
204
+ };
205
+
206
+ export const ACos = (cpx: Complex) => {
207
+
208
+ if (cpx.imaginary === 0) {
209
+ return {
210
+ real: Math.acos(cpx.real),
211
+ imaginary: 0,
212
+ };
213
+ }
214
+
215
+ const asin = ASin(cpx);
216
+
217
+ return {
218
+ real: Math.PI / 2 - asin.real,
219
+ imaginary: asin.imaginary,
220
+ }
221
+
222
+ };
223
+
224
+ export const ATan = (cpx: Complex): Complex => {
225
+
226
+ const { real: x, imaginary: y } = cpx;
227
+
228
+ // Handle special case where z is purely imaginary
229
+ if (x === 0) {
230
+ if (y > 1) {
231
+ return { real: Math.PI / 2, imaginary: 0.5 * Math.log((y - 1) / (y + 1)) };
232
+ } else if (y < -1) {
233
+ return { real: -Math.PI / 2, imaginary: 0.5 * Math.log((1 - y) / (-1 - y)) };
234
+ }
235
+ }
236
+
237
+ const x2 = x * x;
238
+ const y2 = y * y;
239
+
240
+ // Calculate real and imaginary parts
241
+ const realPart = 0.5 * Math.atan2(2 * x, 1 - x2 - y2);
242
+ const imagPart = 0.25 * Math.log((x2 + (y + 1) * (y + 1)) / (x2 + (y - 1) * (y - 1)));
243
+
244
+ return { real: realPart, imaginary: imagPart };
245
+
246
+ };
247
+
248
+ export const ATan2 = (y: Complex, x: Complex): Complex|false => {
249
+
250
+ // Special case: both arguments are zero
251
+ if (x.real === 0 && x.imaginary === 0 && y.real === 0 && y.imaginary === 0) {
252
+ // throw new Error("Both arguments of arctan2 are zero");
253
+ return false;
254
+ }
255
+
256
+ // Calculate z = y / x
257
+ const denominator = x.real * x.real + x.imaginary * x.imaginary;
258
+ const z: Complex = {
259
+ real: (y.real * x.real + y.imaginary * x.imaginary) / denominator,
260
+ imaginary: (y.imaginary * x.real - y.real * x.imaginary) / denominator
261
+ };
262
+
263
+ // Now calculate arctan(z)
264
+ const x2 = z.real * z.real;
265
+ const y2 = z.imaginary * z.imaginary;
266
+
267
+ const result = {
268
+ real: 0.5 * Math.atan2(2 * z.real, 1 - x2 - y2),
269
+ imaginary: 0.25 * Math.log((x2 + (z.imaginary + 1) * (z.imaginary + 1)) / (x2 + (z.imaginary - 1) * (z.imaginary - 1))),
270
+ };
271
+
272
+ // Adjust the real part based on the quadrant of x
273
+ if (x.real < 0) {
274
+ result.real += (y.real >= 0 || y.imaginary >= 0) ? Math.PI : -Math.PI;
275
+ }
276
+
277
+ return result;
278
+
279
+
280
+ };
281
+
178
282
  export const Product = (...args: Complex[]): Complex => {
179
283
  let base = args.shift();
180
284
  if (!base) {
@@ -71,6 +71,12 @@ export interface ArgumentDescriptor {
71
71
  */
72
72
  boxed?: boolean;
73
73
 
74
+ /**
75
+ * this argument repeasts. this has no impact on the function descriptor
76
+ * but it's useful to know for clients.
77
+ */
78
+ repeat?: boolean;
79
+
74
80
  /**
75
81
  * similar to collector, this flag will return metadata about the cell
76
82
  *
@@ -19,7 +19,7 @@
19
19
  *
20
20
  */
21
21
 
22
- import type { FunctionMap, IntrinsicValue } from '../descriptors';
22
+ import type { CompositeFunctionDescriptor, FunctionMap, IntrinsicValue } from '../descriptors';
23
23
  import * as Utils from '../utilities';
24
24
  // import { StringUnion, NumberUnion } from '../utilities';
25
25
  import { ReferenceError, NAError, ArgumentError, DivideByZeroError, ValueError } from '../function-error';
@@ -213,6 +213,27 @@ const NumberArgument = (argument?: UnionValue, default_value: number|false = fal
213
213
 
214
214
  };
215
215
 
216
+ /**
217
+ * helper for trig functions, curious what this does to size
218
+ */
219
+ const TrigFunction = (real: (value: number) => number, complex: (value: Complex) => Complex, description?: string): CompositeFunctionDescriptor => {
220
+ return {
221
+ description,
222
+ arguments: [
223
+ { name: 'number', boxed: true, unroll: true },
224
+ ],
225
+ fn: (a: UnionValue) => {
226
+ if (a.type === ValueType.number) {
227
+ return { type: ValueType.number, value: real(a.value) };
228
+ }
229
+ if (a.type === ValueType.complex) {
230
+ return { type: ValueType.complex, value: complex(a.value) };
231
+ }
232
+ return ArgumentError();
233
+ },
234
+ };
235
+ }
236
+
216
237
  /**
217
238
  * alternate functions. these are used (atm) only for changing complex
218
239
  * behavior.
@@ -345,7 +366,11 @@ export const BaseFunctionLibrary: FunctionMap = {
345
366
 
346
367
  Sum: {
347
368
  description: 'Adds arguments and ranges',
348
- arguments: [{ boxed: true, name: 'values or ranges' }],
369
+ arguments: [{
370
+ boxed: true,
371
+ name: 'values or ranges' ,
372
+ repeat: true,
373
+ }],
349
374
  fn: (...args: UnionValue[]) => {
350
375
 
351
376
  const sum = { real: 0, imaginary: 0 };
@@ -374,7 +399,7 @@ export const BaseFunctionLibrary: FunctionMap = {
374
399
 
375
400
  SumSQ: {
376
401
  description: 'Returns the sum of the squares of all arguments',
377
- arguments: [{ boxed: true, name: 'values or ranges' }],
402
+ arguments: [{ boxed: true, name: 'values or ranges', repeat: true, }],
378
403
  fn: (...args: UnionValue[]) => {
379
404
 
380
405
  const sum = { real: 0, imaginary: 0 };
@@ -1982,7 +2007,13 @@ export const BaseFunctionLibrary: FunctionMap = {
1982
2007
  },
1983
2008
 
1984
2009
  Log: {
1985
- arguments: [ { unroll: true }, { unroll: true } ],
2010
+ arguments: [ {
2011
+ name: 'number',
2012
+ unroll: true
2013
+ }, {
2014
+ name: 'base',
2015
+ unroll: true
2016
+ } ],
1986
2017
 
1987
2018
  /** default is base 10; allow specific base */
1988
2019
  fn: (a: number, base = 10): UnionValue => {
@@ -1991,26 +2022,115 @@ export const BaseFunctionLibrary: FunctionMap = {
1991
2022
  },
1992
2023
 
1993
2024
  Log10: {
1994
- arguments: [{ unroll: true }],
2025
+ arguments: [{
2026
+ name: 'number',
2027
+ unroll: true,
2028
+ }],
1995
2029
  fn: (a: number): UnionValue => {
1996
2030
  return { type: ValueType.number, value: Math.log(a) / Math.log(10) };
1997
2031
  },
1998
2032
  },
1999
2033
 
2000
2034
  Ln: {
2001
- arguments: [{ unroll: true }],
2035
+ arguments: [{
2036
+ name: 'number',
2037
+ unroll: true,
2038
+ }],
2002
2039
  fn: (a: number): UnionValue => {
2003
2040
  return { type: ValueType.number, value: Math.log(a) };
2004
2041
  },
2005
2042
  },
2006
2043
 
2007
- Ceiling: {
2008
- arguments: [ { unroll: true }, { unroll: true } ], // FIXME: lazy
2044
+ 'Ceiling.Math': {
2045
+ arguments: [ {
2046
+ name: 'number',
2047
+ unroll: true
2048
+ }, {
2049
+ name: 'significance',
2050
+ unroll: true
2051
+ }, {
2052
+ name: 'away from zero',
2053
+ unroll: true,
2054
+ } ],
2055
+
2056
+ fn: (a: number, significance = 1, mode?: number) => {
2057
+
2058
+ let value = 0;
2059
+
2060
+ if (mode && a < 0) {
2061
+ value = -Math.ceil(-a / significance) * significance;
2062
+ }
2063
+ else {
2064
+ value = Math.ceil(a / significance) * significance;
2065
+ }
2009
2066
 
2010
- fn: (a: number) => {
2011
2067
  return {
2012
2068
  type: ValueType.number,
2013
- value: Math.ceil(a),
2069
+ value,
2070
+ };
2071
+ },
2072
+ },
2073
+
2074
+ 'Floor.Math': {
2075
+ arguments: [ {
2076
+ name: 'number',
2077
+ unroll: true
2078
+ }, {
2079
+ name: 'significance',
2080
+ unroll: true
2081
+ }, {
2082
+ name: 'away from zero',
2083
+ unroll: true,
2084
+ } ],
2085
+
2086
+ fn: (a: number, significance = 1, mode?: number) => {
2087
+
2088
+ let value = 0;
2089
+
2090
+ if (mode && a < 0) {
2091
+ value = -Math.floor(-a / significance) * significance;
2092
+ }
2093
+ else {
2094
+ value = Math.floor(a / significance) * significance;
2095
+ }
2096
+
2097
+ return {
2098
+ type: ValueType.number,
2099
+ value,
2100
+ };
2101
+ },
2102
+ },
2103
+
2104
+ Floor: {
2105
+ arguments: [ {
2106
+ name: 'number',
2107
+ unroll: true
2108
+ }, {
2109
+ name: 'significance',
2110
+ unroll: true
2111
+ } ],
2112
+
2113
+ fn: (a: number, significance = 1) => {
2114
+ return {
2115
+ type: ValueType.number,
2116
+ value: Math.floor(a / significance) * significance,
2117
+ };
2118
+ },
2119
+ },
2120
+
2121
+ Ceiling: {
2122
+ arguments: [ {
2123
+ name: 'number',
2124
+ unroll: true
2125
+ }, {
2126
+ name: 'significance',
2127
+ unroll: true
2128
+ } ],
2129
+
2130
+ fn: (a: number, significance = 1) => {
2131
+ return {
2132
+ type: ValueType.number,
2133
+ value: Math.ceil(a / significance) * significance,
2014
2134
  };
2015
2135
  },
2016
2136
  },
@@ -2476,113 +2596,62 @@ export const BaseFunctionLibrary: FunctionMap = {
2476
2596
  },
2477
2597
  },
2478
2598
 
2479
- SinH: {
2480
- arguments: [
2481
- { name: 'number', boxed: true, unroll: true },
2482
- ],
2483
- fn: (a: UnionValue) => {
2484
-
2485
- if (a.type === ValueType.number) {
2486
- return { type: ValueType.number, value: Math.sinh(a.value) };
2487
- }
2488
- if (a.type === ValueType.complex) {
2489
- return { type: ValueType.complex, value: ComplexMath.SinH(a.value) };
2490
- }
2491
-
2492
- return ArgumentError();
2493
-
2494
- },
2495
- },
2496
-
2497
- Sin: {
2599
+ ATan2: {
2498
2600
  arguments: [
2499
- { name: 'angle in radians', boxed: true, unroll: true, }
2601
+ { name: 'y', boxed: true, unroll: true },
2602
+ { name: 'x', boxed: true, unroll: true },
2500
2603
  ],
2501
- fn: (a: UnionValue) => {
2604
+ fn: (y: UnionValue, x: UnionValue) => {
2502
2605
 
2503
- if (a.type === ValueType.number) {
2504
- return { type: ValueType.number, value: Math.sin(a.value) };
2505
- }
2506
- if (a.type === ValueType.complex) {
2507
- return { type: ValueType.complex, value: ComplexMath.Sin(a.value) };
2606
+ if (y.type === ValueType.number && x.type === ValueType.number) {
2607
+ return { type: ValueType.number, value: Math.atan2(y.value, x.value) };
2508
2608
  }
2509
2609
 
2510
- return ArgumentError();
2511
-
2512
- },
2513
- },
2514
-
2515
- CosH: {
2516
- arguments: [
2517
- { name: 'number', boxed: true, unroll: true },
2518
- ],
2519
- fn: (a: UnionValue) => {
2520
-
2521
- if (a.type === ValueType.number) {
2522
- return { type: ValueType.number, value: Math.cosh(a.value) };
2523
- }
2524
- if (a.type === ValueType.complex) {
2525
- return { type: ValueType.complex, value: ComplexMath.CosH(a.value) };
2610
+ if (y.type === ValueType.number) {
2611
+ y = { type: ValueType.complex, value: { real: y.value, imaginary: 0 }};
2526
2612
  }
2527
2613
 
2528
- return ArgumentError();
2529
-
2530
- },
2531
- },
2532
-
2533
- Cos: {
2534
- arguments: [
2535
- { name: 'angle in radians', boxed: true, unroll: true, }
2536
- ],
2537
- fn: (a: UnionValue) => {
2538
-
2539
- if (a.type === ValueType.number) {
2540
- return { type: ValueType.number, value: Math.cos(a.value) };
2541
- }
2542
- if (a.type === ValueType.complex) {
2543
- return { type: ValueType.complex, value: ComplexMath.Cos(a.value) };
2614
+ if (x.type === ValueType.number) {
2615
+ x = { type: ValueType.complex, value: { real: x.value, imaginary: 0 }};
2544
2616
  }
2545
2617
 
2546
- return ArgumentError();
2618
+ if (y.type === ValueType.complex && x.type === ValueType.complex) {
2619
+
2620
+ const value = ComplexMath.ATan2(y.value, x.value);
2547
2621
 
2548
- },
2549
- },
2622
+ // should we have an error for infinities? not sure. not sure
2623
+ // why this hasn't come up before...
2550
2624
 
2551
- TanH: {
2552
- arguments: [
2553
- { name: 'number', boxed: true, unroll: true, }
2554
- ],
2555
- fn: (a: UnionValue) => {
2625
+ if (value === false) {
2626
+ return ArgumentError();
2627
+ }
2556
2628
 
2557
- if (a.type === ValueType.number) {
2558
- return { type: ValueType.number, value: Math.tanh(a.value) };
2559
- }
2560
- if (a.type === ValueType.complex) {
2561
- return { type: ValueType.complex, value: ComplexMath.TanH(a.value) };
2629
+ return {
2630
+ type: ValueType.complex, value,
2631
+ }
2562
2632
  }
2563
-
2633
+
2564
2634
  return ArgumentError();
2565
2635
 
2566
2636
  },
2567
2637
  },
2568
2638
 
2569
- Tan: {
2570
- arguments: [
2571
- { name: 'angle in radians', boxed: true, unroll: true, }
2572
- ],
2573
- fn: (a: UnionValue) => {
2639
+ Sin: TrigFunction(Math.sin, ComplexMath.Sin),
2640
+ SinH: TrigFunction(Math.sinh, ComplexMath.SinH),
2641
+ ASin: TrigFunction(Math.asin, ComplexMath.ASin),
2574
2642
 
2575
- if (a.type === ValueType.number) {
2576
- return { type: ValueType.number, value: Math.tan(a.value) };
2577
- }
2578
- if (a.type === ValueType.complex) {
2579
- return { type: ValueType.complex, value: ComplexMath.Tan(a.value) };
2580
- }
2643
+ Cos: TrigFunction(Math.cos, ComplexMath.Cos),
2644
+ CosH: TrigFunction(Math.cosh, ComplexMath.CosH),
2645
+ ACos: TrigFunction(Math.acos, ComplexMath.ACos),
2581
2646
 
2582
- return ArgumentError();
2647
+ Tan: TrigFunction(Math.tan, ComplexMath.Tan),
2648
+ TanH: TrigFunction(Math.tanh, ComplexMath.TanH),
2649
+ ATan: TrigFunction(Math.atan, ComplexMath.ATan),
2583
2650
 
2584
- },
2585
- },
2651
+ E: { fn: () => { return { type: ValueType.number, value: Math.E } } },
2652
+ PI: { fn: () => { return { type: ValueType.number, value: Math.PI } } },
2653
+ SQRT2: { fn: () => { return { type: ValueType.number, value: Math.SQRT2 } } },
2654
+ SQRT1_2: { fn: () => { return { type: ValueType.number, value: Math.SQRT1_2 } } },
2586
2655
 
2587
2656
  Sequence: {
2588
2657
  arguments:[
@@ -2637,7 +2706,19 @@ for (const key of Object.keys(BaseFunctionLibrary)) {
2637
2706
  // block these names from auto-import from Math
2638
2707
 
2639
2708
  const block_list = [
2709
+ 'ceil',
2640
2710
  'pow',
2711
+ 'ln10',
2712
+ 'ln2',
2713
+ 'log10',
2714
+ 'log10e',
2715
+ 'log1p',
2716
+ 'log2',
2717
+ 'log2e',
2718
+ 'random',
2719
+ 'imul',
2720
+ 'clz32',
2721
+ 'fround',
2641
2722
  ];
2642
2723
 
2643
2724
  const block_map: Record<string, string> = {};
@@ -1075,5 +1075,6 @@ export const StatisticsFunctionAliases: {[index: string]: string} = {
1075
1075
  'Quartile': 'Quartile.Inc',
1076
1076
  'NormSInv': 'Norm.S.Inv',
1077
1077
  'NormSDist': 'Norm.S.Dist',
1078
+ 'NormDist': 'Norm.Dist',
1078
1079
 
1079
1080
  };
@@ -1,9 +1,5 @@
1
1
  {
2
2
  "extends": "../tsproject.json",
3
- "references": [
4
- { "path": "../treb-grid/tsconfig.json" },
5
- { "path": "../treb-utils/tsconfig.json" }
6
- ],
7
3
  "include": [
8
4
  "../treb-base-types/**/*.ts"
9
5
  ]
@@ -4217,11 +4217,14 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
4217
4217
  *
4218
4218
  * @public
4219
4219
  */
4220
- public FormatNumber(value: number, format = 'General'): string {
4221
-
4222
- // API v1 OK
4220
+ public FormatNumber(value: number|Complex, format = 'General'): string {
4223
4221
 
4222
+ if (IsComplex(value)) {
4223
+ return NumberFormat.FormatPartsAsText(NumberFormatCache.Get(format).FormatComplex(value));
4224
+ }
4225
+
4224
4226
  return NumberFormatCache.Get(format).Format(value);
4227
+
4225
4228
  }
4226
4229
 
4227
4230
  /**
@@ -489,6 +489,14 @@ export class NumberFormat {
489
489
  // all zeros
490
490
  // (5) change default fraction to #/## (actually we should do that always)
491
491
 
492
+ if (value.imaginary === Infinity || value.imaginary === -Infinity ||
493
+ value.real === Infinity || value.real === -Infinity) {
494
+ return [
495
+ {
496
+ text: 'Infinity',
497
+ }
498
+ ]
499
+ }
492
500
 
493
501
  // check if the imaginary format will render as 0.00i -- we want to
494
502
  // handle this differently.
@@ -1133,11 +1133,11 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
1133
1133
  * @param node
1134
1134
  * @returns [substring to start of selection, substring to end of selection]
1135
1135
  */
1136
- protected SubstringToCaret2(node: HTMLElement): [string, string] {
1136
+ protected SubstringToCaret2(node: HTMLElement, force = false): [string, string] {
1137
1137
 
1138
1138
  const result: [string, string] = ['', ''];
1139
1139
 
1140
- if (node !== node.ownerDocument.activeElement || node !== this.active_editor?.node) {
1140
+ if (!force && node !== node.ownerDocument.activeElement || node !== this.active_editor?.node) {
1141
1141
  return result;
1142
1142
  }
1143
1143