fable 3.1.19 → 3.1.21
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/fable.js +313 -250
- package/dist/fable.js.map +1 -1
- package/dist/fable.min.js +2 -2
- package/dist/fable.min.js.map +1 -1
- package/package.json +5 -5
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +10 -0
- package/source/services/Fable-Service-Math.js +260 -25
- package/test/Math_test.js +74 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fable",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.21",
|
|
4
4
|
"description": "A service dependency injection, configuration and logging library.",
|
|
5
5
|
"main": "source/Fable.js",
|
|
6
6
|
"scripts": {
|
|
@@ -55,16 +55,16 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"async.eachlimit": "^0.5.2",
|
|
57
57
|
"async.waterfall": "^0.5.2",
|
|
58
|
-
"big.js": "^
|
|
58
|
+
"big.js": "^7.0.1",
|
|
59
59
|
"cachetrax": "^1.0.4",
|
|
60
|
-
"cookie": "^0.
|
|
60
|
+
"cookie": "^1.0.2",
|
|
61
61
|
"data-arithmatic": "^1.0.7",
|
|
62
|
-
"dayjs": "^1.11.
|
|
62
|
+
"dayjs": "^1.11.18",
|
|
63
63
|
"fable-log": "^3.0.16",
|
|
64
64
|
"fable-serviceproviderbase": "^3.0.15",
|
|
65
65
|
"fable-settings": "^3.0.12",
|
|
66
66
|
"fable-uuid": "^3.0.11",
|
|
67
|
-
"manyfest": "^1.0.
|
|
67
|
+
"manyfest": "^1.0.42",
|
|
68
68
|
"simple-get": "^4.0.1"
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -41,6 +41,16 @@
|
|
|
41
41
|
"Address": "fable.Math.eulerPrecise"
|
|
42
42
|
},
|
|
43
43
|
|
|
44
|
+
"log": {
|
|
45
|
+
"Name": "Logarithm",
|
|
46
|
+
"Address": "fable.Math.logPrecise"
|
|
47
|
+
},
|
|
48
|
+
"exp": {
|
|
49
|
+
"Name": "Eulers Number to the Power Of N",
|
|
50
|
+
"Address": "fable.Math.expPrecise"
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
|
|
44
54
|
"sin": {
|
|
45
55
|
"Name": "Sine",
|
|
46
56
|
"Address": "fable.Math.sin"
|
|
@@ -26,7 +26,10 @@ class FableServiceMath extends libFableServiceBase
|
|
|
26
26
|
// From NASA: https://apod.nasa.gov/htmltest/gifcity/e.2mil
|
|
27
27
|
this.euler = '2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664';
|
|
28
28
|
|
|
29
|
-
// this.manifest = this.fable.newManyfest();
|
|
29
|
+
// this.manifest = this.fable.newManyfest();
|
|
30
|
+
this.bigNumber = this.fable.Utility.bigNumber;
|
|
31
|
+
|
|
32
|
+
this.ln2Cache = new Map();
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/*
|
|
@@ -39,10 +42,10 @@ class FableServiceMath extends libFableServiceBase
|
|
|
39
42
|
roundHalfEven 2 ROUND_HALF_EVEN Rounds towards nearest neighbour. (_If equidistant, rounds towards even neighbour._)
|
|
40
43
|
roundUp 3 ROUND_UP Rounds positively away from zero. (_Always round up._)
|
|
41
44
|
*/
|
|
42
|
-
get roundDown() { return this.
|
|
43
|
-
get roundHalfUp() { return this.
|
|
44
|
-
get roundHalfEven() { return this.
|
|
45
|
-
get roundUp() { return this.
|
|
45
|
+
get roundDown() { return this.bigNumber.roundDown; }
|
|
46
|
+
get roundHalfUp() { return this.bigNumber.roundHalfUp; }
|
|
47
|
+
get roundHalfEven() { return this.bigNumber.roundHalfEven; }
|
|
48
|
+
get roundUp() { return this.bigNumber.roundUp; }
|
|
46
49
|
|
|
47
50
|
/**
|
|
48
51
|
* Parses a precise number value.
|
|
@@ -57,7 +60,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
57
60
|
|
|
58
61
|
try
|
|
59
62
|
{
|
|
60
|
-
tmpNumber = new this.
|
|
63
|
+
tmpNumber = new this.bigNumber(pValue);
|
|
61
64
|
}
|
|
62
65
|
catch (pError)
|
|
63
66
|
{
|
|
@@ -97,7 +100,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
97
100
|
return '0';
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
let tmpLeftArbitraryValue = new this.
|
|
103
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
101
104
|
let tmpResult = tmpLeftArbitraryValue.div(tmpRightValue);
|
|
102
105
|
tmpResult = tmpResult.times(100);
|
|
103
106
|
return tmpResult.toString();
|
|
@@ -133,7 +136,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
133
136
|
let tmpDecimals = isNaN(pDecimals) ? 0 : parseInt(pDecimals, 10);
|
|
134
137
|
let tmpRoundingMethod = (typeof (pRoundingMethod) === 'undefined') ? this.roundHalfUp : parseInt(pRoundingMethod, 10);
|
|
135
138
|
|
|
136
|
-
let tmpArbitraryValue = new this.
|
|
139
|
+
let tmpArbitraryValue = new this.bigNumber(tmpValue);
|
|
137
140
|
let tmpResult = tmpArbitraryValue.round(tmpDecimals, tmpRoundingMethod);
|
|
138
141
|
return tmpResult.toString();
|
|
139
142
|
}
|
|
@@ -152,7 +155,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
152
155
|
let tmpDecimals = isNaN(pDecimals) ? 0 : pDecimals;
|
|
153
156
|
let tmpRoundingMethod = (typeof (pRoundingMethod) === 'undefined') ? this.roundHalfUp : pRoundingMethod;
|
|
154
157
|
|
|
155
|
-
let tmpArbitraryValue = new this.
|
|
158
|
+
let tmpArbitraryValue = new this.bigNumber(tmpValue);
|
|
156
159
|
let tmpResult = tmpArbitraryValue.toFixed(tmpDecimals, tmpRoundingMethod);
|
|
157
160
|
|
|
158
161
|
return tmpResult.toString();
|
|
@@ -169,7 +172,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
169
172
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
170
173
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
171
174
|
|
|
172
|
-
let tmpLeftArbitraryValue = new this.
|
|
175
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
173
176
|
let tmpResult = tmpLeftArbitraryValue.plus(tmpRightValue);
|
|
174
177
|
return tmpResult.toString();
|
|
175
178
|
}
|
|
@@ -186,7 +189,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
186
189
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
187
190
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
188
191
|
|
|
189
|
-
let tmpLeftArbitraryValue = new this.
|
|
192
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
190
193
|
let tmpResult = tmpLeftArbitraryValue.minus(tmpRightValue);
|
|
191
194
|
return tmpResult.toString();
|
|
192
195
|
}
|
|
@@ -205,7 +208,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
205
208
|
let tmpResult;
|
|
206
209
|
if (tmpRightValue == Number(pRightValue))
|
|
207
210
|
{
|
|
208
|
-
const tmpLeftArbitraryValue = new this.
|
|
211
|
+
const tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
209
212
|
tmpResult = tmpLeftArbitraryValue.pow(tmpRightValue);
|
|
210
213
|
}
|
|
211
214
|
else
|
|
@@ -229,7 +232,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
229
232
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
230
233
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
231
234
|
|
|
232
|
-
let tmpLeftArbitraryValue = new this.
|
|
235
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
233
236
|
let tmpResult = tmpLeftArbitraryValue.times(tmpRightValue);
|
|
234
237
|
return tmpResult.toString();
|
|
235
238
|
}
|
|
@@ -246,7 +249,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
246
249
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
247
250
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
248
251
|
|
|
249
|
-
let tmpLeftArbitraryValue = new this.
|
|
252
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
250
253
|
let tmpResult = tmpLeftArbitraryValue.div(tmpRightValue);
|
|
251
254
|
return tmpResult.toString();
|
|
252
255
|
}
|
|
@@ -263,7 +266,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
263
266
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
264
267
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
265
268
|
|
|
266
|
-
let tmpLeftArbitraryValue = new this.
|
|
269
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
267
270
|
let tmpResult = tmpLeftArbitraryValue.mod(tmpRightValue);
|
|
268
271
|
return tmpResult.toString();
|
|
269
272
|
}
|
|
@@ -278,7 +281,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
278
281
|
{
|
|
279
282
|
let tmpValue = isNaN(pValue) ? 0 : pValue;
|
|
280
283
|
|
|
281
|
-
let tmpLeftArbitraryValue = new this.
|
|
284
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpValue);
|
|
282
285
|
let tmpResult = tmpLeftArbitraryValue.sqrt();
|
|
283
286
|
return tmpResult.toString();
|
|
284
287
|
}
|
|
@@ -293,7 +296,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
293
296
|
{
|
|
294
297
|
let tmpValue = isNaN(pValue) ? 0 : pValue;
|
|
295
298
|
|
|
296
|
-
let tmpLeftArbitraryValue = new this.
|
|
299
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpValue);
|
|
297
300
|
let tmpResult = tmpLeftArbitraryValue.abs();
|
|
298
301
|
return tmpResult.toString();
|
|
299
302
|
}
|
|
@@ -338,7 +341,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
338
341
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
339
342
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
340
343
|
|
|
341
|
-
let tmpLeftArbitraryValue = new this.
|
|
344
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
342
345
|
return tmpLeftArbitraryValue.cmp(tmpRightValue);
|
|
343
346
|
}
|
|
344
347
|
|
|
@@ -355,7 +358,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
355
358
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
356
359
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
357
360
|
|
|
358
|
-
let tmpLeftArbitraryValue = new this.
|
|
361
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
359
362
|
const diff = tmpLeftArbitraryValue.minus(tmpRightValue).abs();
|
|
360
363
|
if (diff.lte(pEpsilon))
|
|
361
364
|
{
|
|
@@ -380,7 +383,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
380
383
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
381
384
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
382
385
|
|
|
383
|
-
let tmpLeftArbitraryValue = new this.
|
|
386
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
384
387
|
return tmpLeftArbitraryValue.gt(tmpRightValue);
|
|
385
388
|
}
|
|
386
389
|
|
|
@@ -397,7 +400,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
397
400
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
398
401
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
399
402
|
|
|
400
|
-
let tmpLeftArbitraryValue = new this.
|
|
403
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
401
404
|
return tmpLeftArbitraryValue.gte(tmpRightValue);
|
|
402
405
|
}
|
|
403
406
|
|
|
@@ -413,7 +416,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
413
416
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
414
417
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
415
418
|
|
|
416
|
-
let tmpLeftArbitraryValue = new this.
|
|
419
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
417
420
|
return tmpLeftArbitraryValue.lt(tmpRightValue);
|
|
418
421
|
}
|
|
419
422
|
|
|
@@ -429,7 +432,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
429
432
|
let tmpLeftValue = isNaN(pLeftValue) ? 0 : pLeftValue;
|
|
430
433
|
let tmpRightValue = isNaN(pRightValue) ? 0 : pRightValue;
|
|
431
434
|
|
|
432
|
-
let tmpLeftArbitraryValue = new this.
|
|
435
|
+
let tmpLeftArbitraryValue = new this.bigNumber(tmpLeftValue);
|
|
433
436
|
return tmpLeftArbitraryValue.lte(tmpRightValue);
|
|
434
437
|
}
|
|
435
438
|
|
|
@@ -443,7 +446,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
443
446
|
{
|
|
444
447
|
let tmpDegrees = isNaN(pDegrees) ? 0 : pDegrees;
|
|
445
448
|
|
|
446
|
-
let tmpDegreesArbitraryValue = new this.
|
|
449
|
+
let tmpDegreesArbitraryValue = new this.bigNumber(tmpDegrees);
|
|
447
450
|
// TODO: Const for pi in arbitrary precision?
|
|
448
451
|
let tmpResult = tmpDegreesArbitraryValue.times(Math.PI).div(180);
|
|
449
452
|
return tmpResult.toString();
|
|
@@ -655,7 +658,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
655
658
|
*/
|
|
656
659
|
sortHistogramPrecise(pHistogram)
|
|
657
660
|
{
|
|
658
|
-
let tmpSortedHistogram =
|
|
661
|
+
let tmpSortedHistogram = {};
|
|
659
662
|
let tmpKeys = Object.keys(pHistogram);
|
|
660
663
|
|
|
661
664
|
tmpKeys.sort((pLeft, pRight) => { return pHistogram[pLeft] - pHistogram[pRight]; });
|
|
@@ -689,6 +692,238 @@ class FableServiceMath extends libFableServiceBase
|
|
|
689
692
|
return tmpCleanedArray;
|
|
690
693
|
}
|
|
691
694
|
|
|
695
|
+
/**
|
|
696
|
+
* Calculate the natural log of 2 to a specific precision, for use in the Taylor series.
|
|
697
|
+
* Cache outcome so it only runs once per precision.
|
|
698
|
+
* @param {number} pPrecision - The decimal precision to calculate ln(2) to.
|
|
699
|
+
* @returns
|
|
700
|
+
*/
|
|
701
|
+
arbitraryNaturalLogOfTwo(pPrecision)
|
|
702
|
+
{
|
|
703
|
+
const tmpPrecisionKey = pPrecision | 0;
|
|
704
|
+
const tmpPrecision = new this.bigNumber(tmpPrecisionKey);
|
|
705
|
+
if (this.ln2Cache.has(tmpPrecisionKey))
|
|
706
|
+
{
|
|
707
|
+
return this.ln2Cache.get(tmpPrecisionKey);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const tmpTwoConstant = new this.bigNumber(2);
|
|
711
|
+
const y = tmpTwoConstant.minus(1).div(tmpTwoConstant.plus(1)); // 1/3
|
|
712
|
+
const y2 = y.mul(y);
|
|
713
|
+
let tmpSummation = new this.bigNumber(0);
|
|
714
|
+
let tmpTermination = y;
|
|
715
|
+
let tmpDenominator = 1;
|
|
716
|
+
|
|
717
|
+
// Use a slightly larger precision for this to prevent numeric drift for larger log requirements
|
|
718
|
+
const tmpEpsilon = this.powerPrecise(10, -(tmpPrecision.add(8))); // target tail < 10^-(precision+8)
|
|
719
|
+
|
|
720
|
+
for (let i = 0; i < 200000; i++)
|
|
721
|
+
{
|
|
722
|
+
tmpSummation = tmpSummation.plus(tmpTermination.div(tmpDenominator));
|
|
723
|
+
tmpTermination = tmpTermination.mul(y2);
|
|
724
|
+
tmpDenominator += 2;
|
|
725
|
+
if (tmpTermination.abs().div(tmpDenominator).lt(tmpEpsilon))
|
|
726
|
+
{
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
const tmpNaturalLogOfTwo = tmpSummation.mul(2);
|
|
731
|
+
this.ln2Cache.set(tmpPrecisionKey, tmpNaturalLogOfTwo);
|
|
732
|
+
return tmpNaturalLogOfTwo;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Calculate the natural log of a number to a specific precision using arbitrary precision numbers.
|
|
737
|
+
* @param {number} pNumberToCompute
|
|
738
|
+
* @param {number} pPrecision
|
|
739
|
+
* @returns
|
|
740
|
+
*/
|
|
741
|
+
arbitraryNaturalLog(pNumberToCompute, pPrecision)
|
|
742
|
+
{
|
|
743
|
+
let tmpNumberToCompute = new this.bigNumber(pNumberToCompute);
|
|
744
|
+
let tmpPrecision = new this.bigNumber(pPrecision);
|
|
745
|
+
|
|
746
|
+
if (tmpNumberToCompute.lte(0)) throw new Error('ln undefined for non-positive values.');
|
|
747
|
+
if (tmpNumberToCompute.eq(1)) return new this.bigNumber(0);
|
|
748
|
+
|
|
749
|
+
// Reduce x to m in ~[0.75, 1.5] by multiplying/dividing by 2
|
|
750
|
+
const TWO = new this.bigNumber(2);
|
|
751
|
+
let k = 0;
|
|
752
|
+
let m = tmpNumberToCompute;
|
|
753
|
+
|
|
754
|
+
const tmpUpperBounds = new this.bigNumber('1.5');
|
|
755
|
+
const tmpLowerBounds = new this.bigNumber('0.75');
|
|
756
|
+
|
|
757
|
+
while (m.gt(tmpUpperBounds))
|
|
758
|
+
{
|
|
759
|
+
m = m.div(TWO); k += 1;
|
|
760
|
+
}
|
|
761
|
+
while (m.lt(tmpLowerBounds))
|
|
762
|
+
{
|
|
763
|
+
m = m.mul(TWO); k -= 1;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// ln(m) via atanh/Taylor
|
|
767
|
+
const y = m.minus(1).div(m.plus(1)); // |y| < 1
|
|
768
|
+
const y2 = y.mul(y);
|
|
769
|
+
|
|
770
|
+
let tmpSummation = new this.bigNumber(0);
|
|
771
|
+
let tmpSeriesTermination = y; // y^(2j+1)
|
|
772
|
+
let tmpDenominator = 1;
|
|
773
|
+
|
|
774
|
+
const tmpEpsilon = this.powerPrecise(10, -(tmpPrecision.add(6))); // target tail < 10^-(precision+6)
|
|
775
|
+
|
|
776
|
+
// Iterate until next term contribution is below eps
|
|
777
|
+
for (let i = 0; i < 200000; i++)
|
|
778
|
+
{
|
|
779
|
+
tmpSummation = tmpSummation.plus(tmpSeriesTermination.div(tmpDenominator));
|
|
780
|
+
tmpSeriesTermination = tmpSeriesTermination.mul(y2);
|
|
781
|
+
tmpDenominator += 2;
|
|
782
|
+
// Stop when |tmpSeriesTermination|/tmpDenominator < eps
|
|
783
|
+
if (tmpSeriesTermination.abs().div(tmpDenominator).lt(tmpEpsilon))
|
|
784
|
+
{
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
const tmpNaturalLog = tmpSummation.mul(2);
|
|
789
|
+
|
|
790
|
+
// ln(2) once per precision via same series with m=2 (y = 1/3 ==> for faster convergence)
|
|
791
|
+
const tmpPrecisionNaturalLog = this.arbitraryNaturalLogOfTwo(tmpPrecision);
|
|
792
|
+
|
|
793
|
+
return tmpNaturalLog.plus(tmpPrecisionNaturalLog.mul(k));
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* High-precision natural log using:
|
|
798
|
+
* - Argument reduction by powers of 2: x = m * 2^k with m ~ 1
|
|
799
|
+
* - atanh series: ln(m) = 2 * sum_{j>=0} y^(2j+1)/(2j+1), y=(m-1)/(m+1), |y|<1
|
|
800
|
+
*
|
|
801
|
+
* Converges rapidly when m is close to 1 with arbitrary precision numbers.
|
|
802
|
+
*
|
|
803
|
+
* @param {number} pNumberToGenerateLogarithmFor - The number to generate the logarithm for.
|
|
804
|
+
* @param {number} [pBase] - The base of the logarithm. Defaults to 10.
|
|
805
|
+
* @param {number} [pPrecision] - The precision of the result. Defaults to 9 decimal places.
|
|
806
|
+
* @returns {string} - The logarithm of the number to the specified base and precision.
|
|
807
|
+
*/
|
|
808
|
+
logPrecise(pNumberToGenerateLogarithmFor, pBase, pPrecision)
|
|
809
|
+
{
|
|
810
|
+
let tmpBase = (typeof (pBase) === 'undefined') ? this.bigNumber(10) : this.bigNumber(pBase);
|
|
811
|
+
// Default precision is 9 decimal places -- matches Excel's default for LOG function
|
|
812
|
+
const tmpPrecision = (typeof (pPrecision) === 'undefined') ? 9 : pPrecision;
|
|
813
|
+
// Extra precision to avoid rounding errors since we are using a series
|
|
814
|
+
const tmpExtraPrecision = 8;
|
|
815
|
+
const tmpWorkingPrecision = tmpPrecision + tmpExtraPrecision;
|
|
816
|
+
|
|
817
|
+
// Store existing precision since this function integrates on a its own precision terms
|
|
818
|
+
const tmpSavedBigDecimalPrecision = this.bigNumber.DP;
|
|
819
|
+
const tmpSavedBigRoundingMethod = this.bigNumber.RM;
|
|
820
|
+
|
|
821
|
+
this.bigNumber.DP = tmpWorkingPrecision;
|
|
822
|
+
this.bigNumber.RM = 1; // round half up for the Taylor series
|
|
823
|
+
|
|
824
|
+
const N = this.bigNumber(pNumberToGenerateLogarithmFor);
|
|
825
|
+
const B = this.bigNumber(tmpBase);
|
|
826
|
+
|
|
827
|
+
// Run domain checks, which Excel also does
|
|
828
|
+
if (N.lte(0))
|
|
829
|
+
{
|
|
830
|
+
this.log.error(`Fable logPrecise Error: Number must be greater than 0; number was ${pNumberToGenerateLogarithmFor}.`);
|
|
831
|
+
return NaN;
|
|
832
|
+
}
|
|
833
|
+
if (B.lte(0) || B.eq(1))
|
|
834
|
+
{
|
|
835
|
+
this.log.error(`Fable logPrecise Error: Base must be greater than 0 and not equal to 1 -- base ${Base} was passed in.`);
|
|
836
|
+
return NaN;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const tmpNaturalLogOfN = this.arbitraryNaturalLog(N, tmpPrecision);
|
|
840
|
+
const tmpNaturalLogOfB = this.arbitraryNaturalLog(B, tmpPrecision);
|
|
841
|
+
|
|
842
|
+
const tmpResult = tmpNaturalLogOfN.div(tmpNaturalLogOfB);
|
|
843
|
+
|
|
844
|
+
// Final rounding to requested precision
|
|
845
|
+
let finalResult = tmpResult.toFixed(tmpPrecision);
|
|
846
|
+
this.bigNumber.DP = tmpSavedBigDecimalPrecision;
|
|
847
|
+
this.bigNumber.RM = tmpSavedBigRoundingMethod;
|
|
848
|
+
return finalResult;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
expPrecise(pValue, pDecimalPrecision)
|
|
852
|
+
{
|
|
853
|
+
let tmpValue = isNaN(pValue) ? this.bigNumber(1) : this.bigNumber(pValue);
|
|
854
|
+
|
|
855
|
+
// Constants & thresholds (Excel / IEEE-754 double limits) -- this is required to match Excel's behavior
|
|
856
|
+
const tmpDecimalPrecision = (typeof (pDecimalPrecision) === 'undefined') ? 9 : parseInt(pDecimalPrecision, 10);
|
|
857
|
+
const tmpSavedBigDecimalPrecision = this.bigNumber.DP;
|
|
858
|
+
this.bigNumber.DP = tmpDecimalPrecision + 10; // a bit of extra precision for rounding safety (this makes it match excel)
|
|
859
|
+
|
|
860
|
+
// ln(2), min/max natural logs before double overflow/underflow
|
|
861
|
+
const tmpNaturalLogOfTwo = new this.bigNumber('0.693147180559945309417232121458176568'); // This is hilarious that we can compute the value above but this is what Excel uses.
|
|
862
|
+
const tmpNaturalLogMaxDouble = new this.bigNumber('709.782712893384'); // ln(1.7976931348623157e308)
|
|
863
|
+
const tmpNaturalLogMinimumValue = new this.bigNumber('-744.4400719213812'); // ln(5e-324)
|
|
864
|
+
|
|
865
|
+
// 1. Guard for Overflow / underflow behavior to match Excel
|
|
866
|
+
if (tmpValue.gt(tmpNaturalLogMaxDouble))
|
|
867
|
+
{
|
|
868
|
+
this.bigNumber.DP = tmpSavedBigDecimalPrecision;
|
|
869
|
+
return NaN; // Excel shows #NUM! when result overflows we will use NaN
|
|
870
|
+
}
|
|
871
|
+
if (tmpValue.lt(tmpNaturalLogMinimumValue))
|
|
872
|
+
{
|
|
873
|
+
this.bigNumber.DP = tmpSavedBigDecimalPrecision;
|
|
874
|
+
return new this.bigNumber(0); // Excel underflows to 0
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// 2. Perform Range reduction: x = k*ln2 + r, with r small
|
|
878
|
+
// k = floor(x / ln2)
|
|
879
|
+
let k;
|
|
880
|
+
try
|
|
881
|
+
{
|
|
882
|
+
k = tmpValue.div(tmpNaturalLogOfTwo).round(0, 0 /* RoundDown toward -infinity */); // floor for positives & negatives
|
|
883
|
+
}
|
|
884
|
+
catch(pErrorRounding)
|
|
885
|
+
{
|
|
886
|
+
this.log.error(`Fable expPrecise Error: Rounding error during range reduction for value of ${pValue}. Error: ${pErrorRounding}`);
|
|
887
|
+
this.bigNumber.DP = tmpSavedBigDecimalPrecision;
|
|
888
|
+
return NaN;
|
|
889
|
+
}
|
|
890
|
+
const r = tmpValue.minus(k.times(tmpNaturalLogOfTwo));
|
|
891
|
+
|
|
892
|
+
// Compute exp(r) via Taylor series with Big arithmetic
|
|
893
|
+
// exp(r) = Summation of r^n / n!, n=0..infinity
|
|
894
|
+
// Sum until termination is below tolerance based on decimal precision
|
|
895
|
+
const tmpTolerance = new this.bigNumber(10).pow(-(tmpDecimalPrecision + 2));
|
|
896
|
+
let tmpTermination = new this.bigNumber(1); // r^0/0! = 1
|
|
897
|
+
let tmpSummation = new this.bigNumber(1);
|
|
898
|
+
let n = 1;
|
|
899
|
+
|
|
900
|
+
// 3. Multiply incrementally: term *= r / n
|
|
901
|
+
// SOOOOO close to a fractal!
|
|
902
|
+
while (true)
|
|
903
|
+
{
|
|
904
|
+
tmpTermination = tmpTermination.times(r).div(n);
|
|
905
|
+
if (tmpTermination.abs().lt(tmpTolerance)) break;
|
|
906
|
+
tmpSummation = tmpSummation.plus(tmpTermination);
|
|
907
|
+
n++;
|
|
908
|
+
// Hard safety cap for pathological inputs (shouldn’t be possible with step 2's range reduction):
|
|
909
|
+
if (n > 2000)
|
|
910
|
+
{
|
|
911
|
+
this.log.warn(`Fable expPrecise warning: Taylor series failed to converge after 2000 iterations for value of ${pValue}.`);
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// 4. Recompose: exp(x) = 2^k * exp(r)
|
|
917
|
+
const tmpTwo = new this.bigNumber(2);
|
|
918
|
+
const tmpAbsoluteValueOfK = k.abs().toNumber(); // k is integer; big.js pow requires a JS integer
|
|
919
|
+
let tmpTwoToThePowerOfAbsoluteK = tmpAbsoluteValueOfK === 0 ? new this.bigNumber(1) : tmpTwo.pow(tmpAbsoluteValueOfK);
|
|
920
|
+
const tmpResult = k.gte(0) ? tmpSummation.times(tmpTwoToThePowerOfAbsoluteK) : tmpSummation.div(tmpTwoToThePowerOfAbsoluteK);
|
|
921
|
+
|
|
922
|
+
// 5. Restore global decimal precision
|
|
923
|
+
this.bigNumber.DP = tmpSavedBigDecimalPrecision;
|
|
924
|
+
return tmpResult.round(tmpDecimalPrecision).toString();
|
|
925
|
+
}
|
|
926
|
+
|
|
692
927
|
cleanValueObject(pValueObject)
|
|
693
928
|
{
|
|
694
929
|
if (typeof (pValueObject) !== 'object')
|
package/test/Math_test.js
CHANGED
|
@@ -295,6 +295,80 @@ suite
|
|
|
295
295
|
return fDone();
|
|
296
296
|
}
|
|
297
297
|
);
|
|
298
|
+
|
|
299
|
+
test
|
|
300
|
+
(
|
|
301
|
+
'Logarithms',
|
|
302
|
+
function(fDone)
|
|
303
|
+
{
|
|
304
|
+
let testFable = new libFable();
|
|
305
|
+
|
|
306
|
+
Expect(testFable.Math.logPrecise(0)).to.be.NaN;
|
|
307
|
+
Expect(testFable.Math.logPrecise(1.124)).to.equal('0.050766311');
|
|
308
|
+
Expect(testFable.Math.logPrecise('1.124')).to.equal('0.050766311');
|
|
309
|
+
Expect(testFable.Math.logPrecise('1.4')).to.equal('0.146128036');
|
|
310
|
+
Expect(testFable.Math.logPrecise('1.874232')).to.equal('0.272823349');
|
|
311
|
+
Expect(testFable.Math.logPrecise('1.9')).to.equal('0.278753601');
|
|
312
|
+
Expect(testFable.Math.logPrecise('2.2')).to.equal('0.342422681');
|
|
313
|
+
Expect(testFable.Math.logPrecise('3.324')).to.equal('0.521661015');
|
|
314
|
+
Expect(testFable.Math.logPrecise('6.32423')).to.equal('0.801007656');
|
|
315
|
+
Expect(testFable.Math.logPrecise('7')).to.equal('0.845098040');
|
|
316
|
+
Expect(testFable.Math.logPrecise('16')).to.equal('1.204119983');
|
|
317
|
+
Expect(testFable.Math.logPrecise('10000')).to.equal('4.000000000');
|
|
318
|
+
Expect(testFable.Math.logPrecise('87')).to.equal('1.939519253');
|
|
319
|
+
Expect(testFable.Math.logPrecise('100')).to.equal('2.000000000');
|
|
320
|
+
Expect(testFable.Math.logPrecise('110')).to.equal('2.041392685');
|
|
321
|
+
Expect(testFable.Math.logPrecise('130')).to.equal('2.113943352');
|
|
322
|
+
Expect(testFable.Math.logPrecise('400')).to.equal('2.602059991');
|
|
323
|
+
Expect(testFable.Math.logPrecise('500')).to.equal('2.698970004');
|
|
324
|
+
Expect(testFable.Math.logPrecise('600')).to.equal('2.778151250');
|
|
325
|
+
Expect(testFable.Math.logPrecise('900')).to.equal('2.954242509');
|
|
326
|
+
Expect(testFable.Math.logPrecise('999')).to.equal('2.999565488');
|
|
327
|
+
Expect(testFable.Math.logPrecise('1000')).to.equal('3.000000000');
|
|
328
|
+
Expect(testFable.Math.logPrecise('1001')).to.equal('3.000434077');
|
|
329
|
+
Expect(testFable.Math.logPrecise('1200')).to.equal('3.079181246');
|
|
330
|
+
Expect(testFable.Math.logPrecise('1300')).to.equal('3.113943352');
|
|
331
|
+
Expect(testFable.Math.logPrecise('1999')).to.equal('3.300812794');
|
|
332
|
+
Expect(testFable.Math.logPrecise('2000')).to.equal('3.301029996');
|
|
333
|
+
Expect(testFable.Math.logPrecise('3000')).to.equal('3.477121255');
|
|
334
|
+
Expect(testFable.Math.logPrecise('4000')).to.equal('3.602059991');
|
|
335
|
+
Expect(testFable.Math.logPrecise('5000')).to.equal('3.698970004');
|
|
336
|
+
Expect(testFable.Math.logPrecise('100000')).to.equal('5.000000000');
|
|
337
|
+
Expect(testFable.Math.logPrecise('1005000901')).to.equal('9.002166451');
|
|
338
|
+
|
|
339
|
+
return fDone();
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
test
|
|
344
|
+
(
|
|
345
|
+
'Eulers to an Exponent of N',
|
|
346
|
+
function(fDone)
|
|
347
|
+
{
|
|
348
|
+
let testFable = new libFable();
|
|
349
|
+
|
|
350
|
+
Expect(testFable.Math.expPrecise('0')).to.equal('1');
|
|
351
|
+
Expect(testFable.Math.expPrecise('1.124')).to.equal('3.077138172');
|
|
352
|
+
Expect(testFable.Math.expPrecise('1.4')).to.equal('4.055199967');
|
|
353
|
+
Expect(testFable.Math.expPrecise('1.874232')).to.equal('6.515813054');
|
|
354
|
+
Expect(testFable.Math.expPrecise('1.9')).to.equal('6.685894442');
|
|
355
|
+
Expect(testFable.Math.expPrecise('2.2')).to.equal('9.025013499');
|
|
356
|
+
Expect(testFable.Math.expPrecise('3.324')).to.equal('27.771213539');
|
|
357
|
+
Expect(testFable.Math.expPrecise('6.32423')).to.equal('557.928043628');
|
|
358
|
+
Expect(testFable.Math.expPrecise('7')).to.equal('1096.633158427');
|
|
359
|
+
Expect(testFable.Math.expPrecise('16')).to.equal('8886110.52050434');
|
|
360
|
+
Expect(testFable.Math.expPrecise('87')).to.equal('6.0760302250165910226862305668049228933571005991e+37');
|
|
361
|
+
Expect(testFable.Math.expPrecise('100')).to.equal('2.6881171418144006498361355299904430489510323068101727e+43');
|
|
362
|
+
Expect(testFable.Math.expPrecise('110')).to.equal('5.92097202763302871775566547178919934033680861763789992544e+47');
|
|
363
|
+
Expect(testFable.Math.expPrecise('130')).to.equal('2.87264955081656835249498360594656214322410986618852673463510287128e+56');
|
|
364
|
+
Expect(testFable.Math.expPrecise('400')).to.equal('5.22146968976280766823448917819312119928757838636294365087229965314955319234363035967068090474969920921229926737521823711259606341428359454445036263563446386876069161748564476869487107e+173');
|
|
365
|
+
Expect(testFable.Math.expPrecise('500')).to.equal('1.4035922178443322635139346187029106729839029888278346358090479055036847329857327659066066346423163346684518112330560812299308307757301544400486805796338124141628072493187592353782193348029633841832360152127536485213047179412609e+217');
|
|
366
|
+
Expect(testFable.Math.expPrecise('600')).to.equal('3.77302030092434336419695838674700740088354673638834622388500325660794131835628047153390042345698138347361656320402231485978980283954189824175081056524926295674148482949243810250377530201023308557571234567617672508975937688969808748216814845636042130603884174370820531865e+260');
|
|
367
|
+
|
|
368
|
+
return fDone();
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
|
|
298
372
|
}
|
|
299
373
|
);
|
|
300
374
|
}
|