@scriptc/runtime 0.0.16 → 0.0.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scriptc/runtime",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "scriptc native runtime — C sources, compiled into every scriptc binary",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scriptc.dev",
package/src/scr_lib.c CHANGED
@@ -3503,6 +3503,170 @@ ScrStr *scr_num_to_fixed0(double x) {
3503
3503
  return r;
3504
3504
  }
3505
3505
 
3506
+ /* The explicit-fraction-digits toFixed needs the exact binary value, not
3507
+ * the shortest decimal that round-trips to it: (1.005).toFixed(2) is
3508
+ * "1.00", for example. Represent abs(x) * 10^f as
3509
+ *
3510
+ * mantissa * 5^f * 2^(binary_exponent + f)
3511
+ *
3512
+ * in a tiny base-2^32 integer, then shift right with the spec's
3513
+ * round-half-up rule. The largest value handled here is below 1e21 with
3514
+ * f=100, so the rounded integer is below 1e121 (402 bits); sixteen limbs
3515
+ * leave comfortable headroom without heap allocation. */
3516
+ #define SCR_FIXED_LIMBS 16
3517
+ typedef struct {
3518
+ uint32_t limb[SCR_FIXED_LIMBS]; /* little-endian */
3519
+ int len;
3520
+ } ScrFixedInt;
3521
+
3522
+ static void scr_fixed_normalize(ScrFixedInt *v) {
3523
+ while (v->len > 1 && v->limb[v->len - 1] == 0) v->len--;
3524
+ }
3525
+
3526
+ static void scr_fixed_mul5(ScrFixedInt *v) {
3527
+ uint64_t carry = 0;
3528
+ for (int i = 0; i < v->len; i++) {
3529
+ uint64_t p = (uint64_t)v->limb[i] * 5 + carry;
3530
+ v->limb[i] = (uint32_t)p;
3531
+ carry = p >> 32;
3532
+ }
3533
+ if (carry != 0) v->limb[v->len++] = (uint32_t)carry;
3534
+ }
3535
+
3536
+ static bool scr_fixed_bit(const ScrFixedInt *v, int bit) {
3537
+ int word = bit / 32;
3538
+ return word < v->len && ((v->limb[word] >> (bit % 32)) & 1u) != 0;
3539
+ }
3540
+
3541
+ static void scr_fixed_shr(ScrFixedInt *v, int bits) {
3542
+ int words = bits / 32;
3543
+ int rem = bits % 32;
3544
+ if (words >= v->len) {
3545
+ v->limb[0] = 0;
3546
+ v->len = 1;
3547
+ return;
3548
+ }
3549
+ int n = v->len - words;
3550
+ for (int i = 0; i < n; i++) {
3551
+ uint32_t lo = v->limb[i + words] >> rem;
3552
+ uint32_t hi =
3553
+ rem != 0 && i + words + 1 < v->len
3554
+ ? v->limb[i + words + 1] << (32 - rem)
3555
+ : 0;
3556
+ v->limb[i] = lo | hi;
3557
+ }
3558
+ v->len = n;
3559
+ scr_fixed_normalize(v);
3560
+ }
3561
+
3562
+ static void scr_fixed_shl(ScrFixedInt *v, int bits) {
3563
+ uint32_t out[SCR_FIXED_LIMBS] = {0};
3564
+ int words = bits / 32;
3565
+ int rem = bits % 32;
3566
+ for (int i = 0; i < v->len; i++) {
3567
+ int at = i + words;
3568
+ out[at] |= v->limb[i] << rem;
3569
+ if (rem != 0) out[at + 1] |= v->limb[i] >> (32 - rem);
3570
+ }
3571
+ int n = v->len + words + (rem != 0 ? 1 : 0);
3572
+ memcpy(v->limb, out, sizeof out);
3573
+ v->len = n;
3574
+ scr_fixed_normalize(v);
3575
+ }
3576
+
3577
+ static void scr_fixed_inc(ScrFixedInt *v) {
3578
+ uint64_t carry = 1;
3579
+ for (int i = 0; i < v->len && carry != 0; i++) {
3580
+ uint64_t s = (uint64_t)v->limb[i] + carry;
3581
+ v->limb[i] = (uint32_t)s;
3582
+ carry = s >> 32;
3583
+ }
3584
+ if (carry != 0) v->limb[v->len++] = (uint32_t)carry;
3585
+ }
3586
+
3587
+ /* Divide in place by 1e9; each quotient limb still fits uint32_t because
3588
+ * the carried remainder is below the divisor. Returns the remainder. */
3589
+ static uint32_t scr_fixed_div1e9(ScrFixedInt *v) {
3590
+ uint64_t rem = 0;
3591
+ for (int i = v->len - 1; i >= 0; i--) {
3592
+ uint64_t cur = (rem << 32) | v->limb[i];
3593
+ v->limb[i] = (uint32_t)(cur / 1000000000u);
3594
+ rem = cur % 1000000000u;
3595
+ }
3596
+ scr_fixed_normalize(v);
3597
+ return (uint32_t)rem;
3598
+ }
3599
+
3600
+ /* Number.prototype.toFixed(fractionDigits), with the argument already
3601
+ * number-typed by the frontend. ToIntegerOrInfinity validation precedes
3602
+ * the receiver's non-finite arm, as ECMA-262 requires. Invalid precision
3603
+ * raises V8's catchable RangeError text; otherwise the result is +1. */
3604
+ ScrStr *scr_num_to_fixed(double x, double fraction_digits) {
3605
+ double fd = isnan(fraction_digits) ? 0 : trunc(fraction_digits);
3606
+ if (!(fd >= 0 && fd <= 100)) {
3607
+ static const char msg[] =
3608
+ "toFixed() digits argument must be between 0 and 100";
3609
+ scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
3610
+ return NULL;
3611
+ }
3612
+ int f = (int)fd;
3613
+ if (!isfinite(x) || fabs(x) >= 1e21) return scr_f64_to_scrstr(x);
3614
+
3615
+ bool neg = x < 0; /* false for -0, exactly like the spec's sign arm */
3616
+ double a = neg ? -x : x;
3617
+ uint64_t bits;
3618
+ memcpy(&bits, &a, sizeof bits);
3619
+ uint64_t mantissa = bits & ((1ull << 52) - 1);
3620
+ int ieee_exp = (int)((bits >> 52) & 0x7ffu);
3621
+ int binary_exp;
3622
+ if (ieee_exp == 0) {
3623
+ binary_exp = -1074;
3624
+ } else {
3625
+ mantissa |= 1ull << 52;
3626
+ binary_exp = ieee_exp - 1023 - 52;
3627
+ }
3628
+
3629
+ ScrFixedInt n = {{(uint32_t)mantissa, (uint32_t)(mantissa >> 32)}, 2};
3630
+ scr_fixed_normalize(&n);
3631
+ for (int i = 0; i < f; i++) scr_fixed_mul5(&n);
3632
+ int shift = binary_exp + f;
3633
+ if (shift >= 0) {
3634
+ scr_fixed_shl(&n, shift);
3635
+ } else {
3636
+ int right = -shift;
3637
+ bool round_up = scr_fixed_bit(&n, right - 1);
3638
+ scr_fixed_shr(&n, right);
3639
+ if (round_up) scr_fixed_inc(&n);
3640
+ }
3641
+
3642
+ /* Render the exact rounded integer through base-1e9 chunks, then place
3643
+ * the decimal point f digits from the right (padding through zero). */
3644
+ uint32_t chunks[SCR_FIXED_LIMBS];
3645
+ int chunk_count = 0;
3646
+ do {
3647
+ chunks[chunk_count++] = scr_fixed_div1e9(&n);
3648
+ } while (!(n.len == 1 && n.limb[0] == 0));
3649
+
3650
+ char digits[128];
3651
+ int dlen = snprintf(digits, sizeof digits, "%u", chunks[chunk_count - 1]);
3652
+ for (int i = chunk_count - 2; i >= 0; i--) {
3653
+ dlen += snprintf(digits + dlen, sizeof digits - (size_t)dlen,
3654
+ "%09u", chunks[i]);
3655
+ }
3656
+
3657
+ char out[128];
3658
+ int o = 0;
3659
+ if (neg) out[o++] = '-';
3660
+ int padded = dlen > f + 1 ? dlen : f + 1;
3661
+ int integer_digits = padded - f;
3662
+ int leading_zeros = padded - dlen;
3663
+ for (int i = 0; i < padded; i++) {
3664
+ if (f != 0 && i == integer_digits) out[o++] = '.';
3665
+ out[o++] = i < leading_zeros ? '0' : digits[i - leading_zeros];
3666
+ }
3667
+ return scr_str_new(out, (size_t)o);
3668
+ }
3669
+
3506
3670
  /* Increment a decimal digit string in place. Returns true on overflow —
3507
3671
  * the value becomes 1 followed by len zeros (the caller folds the zeros
3508
3672
  * into its scale); an EMPTY string increments to "1" the same way (the
package/src/scr_runtime.h CHANGED
@@ -4243,12 +4243,14 @@ bool scr_num_is_finite(double x);
4243
4243
  bool scr_num_is_nan(double x);
4244
4244
  bool scr_num_is_integer(double x);
4245
4245
  bool scr_num_is_safe_integer(double x);
4246
- /* The fraction-digits-free Number.prototype formatters: toExponential()
4247
- * (shortest correctly-rounded mantissa, "7e+0" exponent shape) and
4248
- * toFixed() (closest integer, ties toward +∞ on the magnitude, "-0" for
4249
- * negative fractions, ToString at |x| 1e21). +1 results; never throw. */
4246
+ /* Number.prototype formatters: toExponential() is the fraction-digits-free
4247
+ * shortest correctly-rounded mantissa; toFixed0 is the non-throwing omitted
4248
+ * argument form. toFixed implements an explicit 0..100 fractionDigits with
4249
+ * exact binary-value rounding and THROWS V8's catchable RangeError outside
4250
+ * that range. All successful results are +1. */
4250
4251
  ScrStr *scr_num_to_exponential(double x);
4251
4252
  ScrStr *scr_num_to_fixed0(double x);
4253
+ ScrStr *scr_num_to_fixed(double x, double fraction_digits);
4252
4254
 
4253
4255
  /* Object.is over two numbers — the spec's SameValue on doubles: NaN
4254
4256
  * equals NaN, +0 differs from -0, everything else is ==. Never throws. */