porffor 0.55.12 → 0.55.14

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.
@@ -137,6 +137,11 @@ export const __Math_pow = (base: number, exponent: number): number => {
137
137
  // 2. If exponent is either +0𝔽 or -0𝔽, return 1𝔽.
138
138
  if (exponent == 0) return 1;
139
139
 
140
+ // opt: use bit shift for base 2
141
+ if (base == 2) {
142
+ if (Porffor.fastAnd(Number.isInteger(exponent), exponent > 0, exponent < 31)) return 2 << (exponent - 1);
143
+ }
144
+
140
145
  if (!Number.isFinite(base)) {
141
146
  // 3. If base is NaN, return NaN.
142
147
  if (Number.isNaN(base)) return base;
@@ -259,7 +264,7 @@ export const __Math_expm1 = (x: number): number => {
259
264
  return x;
260
265
  }
261
266
 
262
- // use exp(x) - 1 for large x (perf)
267
+ // opt: use exp(x) - 1 for large x
263
268
  if (Math.abs(x) > 1e-5) return Math.exp(x) - 1;
264
269
 
265
270
  // Taylor series
@@ -280,7 +285,7 @@ export const __Math_log1p = (x: number): number => {
280
285
  if (x == -1) return -Infinity; // log(0) = -inf
281
286
  if (!Number.isFinite(x)) return x;
282
287
 
283
- // use exp(x) - 1 for large x (perf)
288
+ // opt: use exp(x) - 1 for large x
284
289
  if (Math.abs(x) > 1e-5) return Math.log(1 + x);
285
290
 
286
291
  // Taylor series
@@ -454,4 +459,83 @@ export const __Math_atan2 = (y: number, x: number): number => {
454
459
 
455
460
  if (y >= 0) return Math.atan(ratio) + Math.PI;
456
461
  return Math.atan(ratio) - Math.PI;
462
+ };
463
+
464
+ export const __Math_sumPrecise = (values: any[]): number => {
465
+ // based on "Fast exact summation using small and large superaccumulators" by Radford M. Neal
466
+ // https://arxiv.org/abs/1505.05571
467
+ // accuracy is top priority, it is fine for this to be slow(er)
468
+
469
+ // small superaccumulator uses 67 chunks: (1 << (11 - 5)) + 3
470
+ // 11 is the number of exponent bits in IEEE-754 double precision
471
+ // 5 is the number of low-order exponent bits stored per chunk
472
+ const SMALL_SLOTS: number = 67;
473
+ const SMALL_MIN: number = -970;
474
+ const small: Float64Array = new Float64Array(SMALL_SLOTS);
475
+
476
+ // large superaccumulator uses 4096 chunks: 1 << (11 + 1)
477
+ // 11 is the number of exponent bits in IEEE-754 double precision
478
+ const LARGE_SLOTS: number = 4096;
479
+ const LARGE_MIN: number = -1074;
480
+ const large: Float64Array = new Float64Array(LARGE_SLOTS);
481
+
482
+ for (const _ of values) {
483
+ if (Porffor.rawType(_) != Porffor.TYPES.number) throw new TypeError('Math.sumPrecise must have only numbers in values');
484
+
485
+ const v: number = _;
486
+ if (v == 0) continue;
487
+
488
+ const exp: number = Porffor.number.getExponent(v);
489
+
490
+ // check if value fits in small superaccumulator
491
+ if (exp >= SMALL_MIN && exp < SMALL_MIN + SMALL_SLOTS) {
492
+ // map the exponent to an array index (-970 -> 0, -969 -> 1, etc)
493
+ const slot: number = exp - SMALL_MIN;
494
+ let y: number = v;
495
+
496
+ // cascade up through slots, similar to carrying digits in decimal
497
+ // but operating in binary and handling floating point carefully
498
+ for (let i: number = slot; i < SMALL_SLOTS - 1; i++) {
499
+ const sum: number = small[i] + y;
500
+ y = sum;
501
+
502
+ // a number fits in slot i if its magnitude is less than 2^(i+SMALL_MIN+1)
503
+ const slotLimit: number = Math.pow(2, i + SMALL_MIN + 1);
504
+ if (y >= -slotLimit && y < slotLimit) {
505
+ small[i] = y;
506
+ y = 0;
507
+ break;
508
+ }
509
+
510
+ // doesn't fit, clear this slot and continue cascading
511
+ small[i] = 0;
512
+ }
513
+
514
+ // if we still have a non-zero value after cascading through small,
515
+ // it needs to go into the large superaccumulator
516
+ if (y != 0) {
517
+ large[Porffor.number.getExponent(y) - LARGE_MIN] += y;
518
+ }
519
+ } else {
520
+ // exponent is outside small superaccumulator range,
521
+ // put it directly in the large superaccumulator
522
+ large[Porffor.number.getExponent(v) - LARGE_MIN] += v;
523
+ }
524
+ }
525
+
526
+ // combine results from both superaccumulators,
527
+ // process from highest to lowest to maintain precision
528
+ // todo: handle -0 (see test262 test)
529
+ let sum: number = -0;
530
+ for (let i: number = LARGE_SLOTS - 1; i >= 0; i--) {
531
+ sum += large[i];
532
+ }
533
+
534
+ for (let i: number = SMALL_SLOTS - 1; i >= 0; i--) {
535
+ sum += small[i];
536
+ }
537
+
538
+ // todo: free large and small
539
+
540
+ return sum;
457
541
  };
@@ -89,6 +89,10 @@ type PorfforGlobal = {
89
89
  appendPadNum(str: bytestring, num: number, len: number): i32;
90
90
  }
91
91
 
92
+ number: {
93
+ getExponent(v: f64): i32;
94
+ }
95
+
92
96
  print(x: any): i32;
93
97
 
94
98
  randomByte(): i32;
@@ -1025,5 +1025,24 @@ export const BuiltinFuncs = function() {
1025
1025
  table: true
1026
1026
  };
1027
1027
 
1028
+ this.__Porffor_number_getExponent = {
1029
+ params: [ Valtype.f64 ],
1030
+ returns: [ Valtype.i32 ],
1031
+ returnType: TYPES.number,
1032
+ wasm: [
1033
+ // extract exponent bits from f64 with bit manipulation
1034
+ [ Opcodes.local_get, 0 ],
1035
+ [ Opcodes.i64_reinterpret_f64 ],
1036
+ number(52, Valtype.i64),
1037
+ [ Opcodes.i64_shr_u ],
1038
+ number(0x7FF, Valtype.i64),
1039
+ [ Opcodes.i64_and ],
1040
+ [ Opcodes.i32_wrap_i64 ],
1041
+ number(1023, Valtype.i32),
1042
+ [ Opcodes.i32_sub ]
1043
+ ]
1044
+ };
1045
+
1046
+
1028
1047
  PrecompiledBuiltins.BuiltinFuncs.call(this);
1029
1048
  };