functionalscript 0.46.0 → 0.47.0

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.
Files changed (178) hide show
  1. package/fjs/basen/cbase32/module.f.mjs +18 -10
  2. package/fjs/basen/cbase32/proof.f.d.mts +3 -0
  3. package/fjs/basen/cbase32/proof.f.mjs +24 -2
  4. package/fjs/basen/proof.f.mjs +8 -16
  5. package/fjs/bnf/ll1/proof.f.d.mts +2 -0
  6. package/fjs/bnf/ll1/proof.f.mjs +33 -18
  7. package/fjs/bnf/module.f.d.mts +2 -2
  8. package/fjs/bnf/module.f.mjs +2 -2
  9. package/fjs/cas/proof.f.mjs +4 -26
  10. package/fjs/ci/common/module.f.mjs +6 -0
  11. package/fjs/ci/config/module.f.d.mts +7 -7
  12. package/fjs/ci/config/module.f.mjs +7 -7
  13. package/fjs/cli/module.f.mjs +14 -10
  14. package/fjs/crypto/vdf/module.f.d.mts +0 -1
  15. package/fjs/crypto/vdf/module.f.mjs +3 -14
  16. package/fjs/dev/module.f.mjs +10 -20
  17. package/fjs/djs/parser/module.f.d.mts +101 -53
  18. package/fjs/djs/parser/module.f.mjs +732 -525
  19. package/fjs/djs/parser/proof.f.d.mts +6 -0
  20. package/fjs/djs/parser/proof.f.mjs +315 -11
  21. package/fjs/djs/parser/types.d.ts +29 -2
  22. package/fjs/djs/tokenizer/module.f.mjs +122 -105
  23. package/fjs/djs/tokenizer/proof.f.mjs +17 -0
  24. package/fjs/djs/transpiler/module.f.mjs +4 -5
  25. package/fjs/djs/transpiler/proof.f.d.mts +2 -0
  26. package/fjs/djs/transpiler/proof.f.mjs +25 -0
  27. package/fjs/edag/amnesia/module.f.d.mts +20 -0
  28. package/fjs/edag/amnesia/module.f.mjs +303 -0
  29. package/fjs/edag/amnesia/proof.f.d.mts +79 -0
  30. package/fjs/edag/amnesia/proof.f.mjs +573 -0
  31. package/fjs/edag/amnesia/types.d.ts +15 -0
  32. package/fjs/edag/module.f.d.mts +410 -0
  33. package/fjs/edag/module.f.mjs +559 -0
  34. package/fjs/edag/proof.f.d.mts +116 -0
  35. package/fjs/edag/proof.f.mjs +686 -0
  36. package/fjs/edag/types.d.ts +55 -0
  37. package/fjs/effects/node/memory/module.d.mts +37 -3
  38. package/fjs/effects/node/memory/module.mjs +38 -4
  39. package/fjs/effects/node/memory/proof.d.mts +5 -2
  40. package/fjs/effects/node/memory/proof.mjs +36 -6
  41. package/fjs/effects/node/module.d.mts +9 -1
  42. package/fjs/effects/node/module.f.d.mts +39 -0
  43. package/fjs/effects/node/module.f.mjs +46 -0
  44. package/fjs/effects/node/module.mjs +234 -32
  45. package/fjs/effects/node/types.d.ts +28 -2
  46. package/fjs/effects/node/virtual/module.f.d.mts +19 -10
  47. package/fjs/effects/node/virtual/module.f.mjs +246 -32
  48. package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
  49. package/fjs/effects/node/virtual/proof.f.mjs +232 -8
  50. package/fjs/effects/node/virtual/types.d.ts +57 -1
  51. package/fjs/emergent_testing/browser/proof.d.mts +55 -0
  52. package/fjs/emergent_testing/browser/proof.mjs +352 -0
  53. package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
  54. package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
  55. package/fjs/emergent_testing/browser.d.mts +79 -0
  56. package/fjs/emergent_testing/browser.mjs +432 -0
  57. package/fjs/emergent_testing/proof.f.d.mts +1 -1
  58. package/fjs/emergent_testing/proof.f.mjs +2 -2
  59. package/fjs/js/string_escape/module.f.d.mts +47 -0
  60. package/fjs/js/string_escape/module.f.mjs +95 -0
  61. package/fjs/js/string_escape/proof.f.d.mts +8 -0
  62. package/fjs/js/string_escape/proof.f.mjs +68 -0
  63. package/fjs/js/tokenizer/module.f.mjs +14 -12
  64. package/fjs/mcp/cas/module.f.d.mts +1 -7
  65. package/fjs/mcp/cas/module.f.mjs +24 -15
  66. package/fjs/mcp/evo/module.f.mjs +9 -24
  67. package/fjs/mcp/module.f.d.mts +3 -1
  68. package/fjs/mcp/module.f.mjs +4 -2
  69. package/fjs/media/json/rtti/module.f.d.mts +2 -2
  70. package/fjs/media/json/rtti/module.f.mjs +1 -1
  71. package/fjs/media/json/schema/module.f.d.mts +15 -5
  72. package/fjs/media/json/schema/module.f.mjs +42 -15
  73. package/fjs/media/json/schema/proof.f.d.mts +10 -1
  74. package/fjs/media/json/schema/proof.f.mjs +88 -16
  75. package/fjs/media/json/serializer/module.f.mjs +14 -26
  76. package/fjs/media/json/types.d.ts +1 -1
  77. package/fjs/media/lock/module.f.d.mts +8 -3
  78. package/fjs/media/lock/module.f.mjs +9 -3
  79. package/fjs/media/lock/proof.f.d.mts +1 -1
  80. package/fjs/media/lock/proof.f.mjs +2 -2
  81. package/fjs/media/lock/types.d.ts +1 -3
  82. package/fjs/media/module.f.d.mts +20 -13
  83. package/fjs/media/module.f.mjs +29 -16
  84. package/fjs/media/note/module.f.d.mts +14 -7
  85. package/fjs/media/note/module.f.mjs +14 -7
  86. package/fjs/media/note/proof.f.mjs +1 -1
  87. package/fjs/media/proof.f.mjs +5 -5
  88. package/fjs/media/revision/module.f.d.mts +8 -2
  89. package/fjs/media/revision/module.f.mjs +9 -3
  90. package/fjs/media/revision/proof.f.mjs +1 -1
  91. package/fjs/media/revision/types.d.ts +1 -1
  92. package/fjs/media/type/module.f.d.mts +20 -0
  93. package/fjs/media/type/module.f.mjs +74 -0
  94. package/fjs/media/type/proof.f.d.mts +5 -0
  95. package/fjs/media/type/proof.f.mjs +24 -1
  96. package/fjs/module.f.mjs +6 -0
  97. package/fjs/path/module.f.d.mts +60 -3
  98. package/fjs/path/module.f.mjs +138 -14
  99. package/fjs/path/proof.f.d.mts +3 -0
  100. package/fjs/path/proof.f.mjs +298 -2
  101. package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
  102. package/fjs/protocol/json_rpc/module.f.mjs +49 -13
  103. package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
  104. package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
  105. package/fjs/protocol/json_rpc/types.d.ts +12 -2
  106. package/fjs/protocol/mcp/module.f.d.mts +89 -54
  107. package/fjs/protocol/mcp/module.f.mjs +110 -56
  108. package/fjs/protocol/mcp/proof.f.d.mts +13 -0
  109. package/fjs/protocol/mcp/proof.f.mjs +107 -7
  110. package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
  111. package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
  112. package/fjs/protocol/mcp/types.d.ts +14 -2
  113. package/fjs/text/utf8/module.f.mjs +27 -1
  114. package/fjs/text/utf8/proof.f.mjs +37 -0
  115. package/fjs/types/array/module.f.mjs +18 -11
  116. package/fjs/types/array/proof.f.mjs +27 -1
  117. package/fjs/types/bigfloat/module.f.d.mts +48 -9
  118. package/fjs/types/bigfloat/module.f.mjs +147 -59
  119. package/fjs/types/bigfloat/proof.f.d.mts +2 -0
  120. package/fjs/types/bigfloat/proof.f.mjs +136 -4
  121. package/fjs/types/bigfloat/types.d.ts +17 -0
  122. package/fjs/types/function/module.f.d.mts +11 -2
  123. package/fjs/types/function/module.f.mjs +20 -2
  124. package/fjs/types/function/proof.f.mjs +5 -1
  125. package/fjs/types/phantom/types.d.ts +21 -0
  126. package/fjs/types/result/module.f.d.mts +4 -4
  127. package/fjs/types/result/module.f.mjs +2 -2
  128. package/fjs/types/result/proof.f.d.mts +6 -2
  129. package/fjs/types/result/proof.f.mjs +17 -1
  130. package/fjs/types/rtti/common/module.f.d.mts +78 -5
  131. package/fjs/types/rtti/common/module.f.mjs +168 -3
  132. package/fjs/types/rtti/common/proof.f.d.mts +14 -0
  133. package/fjs/types/rtti/common/proof.f.mjs +61 -2
  134. package/fjs/types/rtti/common/types.d.ts +23 -1
  135. package/fjs/types/rtti/data/module.f.d.mts +57 -1
  136. package/fjs/types/rtti/data/module.f.mjs +241 -47
  137. package/fjs/types/rtti/data/proof.f.d.mts +10 -0
  138. package/fjs/types/rtti/data/proof.f.mjs +272 -39
  139. package/fjs/types/rtti/data/types.d.ts +14 -7
  140. package/fjs/types/rtti/host.proof.d.mts +33 -0
  141. package/fjs/types/rtti/host.proof.mjs +170 -0
  142. package/fjs/types/rtti/module.f.d.mts +51 -6
  143. package/fjs/types/rtti/module.f.mjs +50 -3
  144. package/fjs/types/rtti/parse/module.f.d.mts +31 -24
  145. package/fjs/types/rtti/parse/module.f.mjs +197 -59
  146. package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
  147. package/fjs/types/rtti/parse/proof.f.mjs +171 -47
  148. package/fjs/types/rtti/proof.f.d.mts +6 -0
  149. package/fjs/types/rtti/proof.f.mjs +42 -1
  150. package/fjs/types/rtti/ts/module.f.d.mts +9 -3
  151. package/fjs/types/rtti/ts/module.f.mjs +68 -15
  152. package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
  153. package/fjs/types/rtti/ts/proof.f.mjs +139 -6
  154. package/fjs/types/rtti/ts/types.d.ts +164 -16
  155. package/fjs/types/rtti/types.d.ts +31 -3
  156. package/fjs/types/rtti/validate/module.f.d.mts +120 -0
  157. package/fjs/types/rtti/validate/module.f.mjs +351 -0
  158. package/fjs/types/rtti/validate/proof.f.d.mts +154 -0
  159. package/fjs/types/rtti/validate/proof.f.mjs +781 -0
  160. package/fjs/types/ts/types.d.ts +4 -0
  161. package/fjs/web/module.f.d.mts +115 -0
  162. package/fjs/web/module.f.mjs +581 -0
  163. package/fjs/web/proof.f.d.mts +39 -0
  164. package/fjs/web/proof.f.mjs +413 -0
  165. package/fjs/web/types.d.ts +39 -0
  166. package/fjs/website/browser-prepare.d.mts +5 -0
  167. package/fjs/website/browser-prepare.mjs +115 -0
  168. package/fjs/website/browser-source.d.mts +37 -0
  169. package/fjs/website/browser-source.mjs +202 -0
  170. package/fjs/website/browser-source.proof.d.mts +31 -0
  171. package/fjs/website/browser-source.proof.mjs +162 -0
  172. package/fjs/website/browser.d.mts +5 -0
  173. package/fjs/website/browser.mjs +8 -0
  174. package/fjs/website/module.f.d.mts +3 -3
  175. package/fjs/website/module.f.mjs +62 -9
  176. package/fjs/website/proof.f.d.mts +4 -0
  177. package/fjs/website/proof.f.mjs +18 -3
  178. package/package.json +2 -3
@@ -3,46 +3,49 @@
3
3
  *
4
4
  * @module
5
5
  *
6
- * @import { BigFloat } from './types.ts'
6
+ * @import { BigFloat, Format } from './types.ts'
7
+ * @import { Nullable } from '../nullable/types.ts'
7
8
  */
8
9
 
9
- import { abs, sign } from '../bigint/module.f.mjs'
10
+ import { abs, bitLength, mask, sign } from '../bigint/module.f.mjs'
10
11
 
11
- /** @typedef {readonly [BigFloat, bigint]} _BigFloatWithRemainder */
12
+ /**
13
+ * A magnitude that has been truncated, paired with what was cut off: the exact
14
+ * value is `m * 2^e` when `r` is `0n`, and strictly between `m * 2^e` and
15
+ * `(m + 1) * 2^e` otherwise. Only `r === 0n` is ever asked, so any non-zero
16
+ * `r` — a division remainder, the bits a shift dropped, or both — says the
17
+ * same thing.
18
+ *
19
+ * @typedef {readonly [BigFloat, bigint]} _BigFloatWithRemainder
20
+ */
12
21
 
13
- const twoPow53 = 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000n
14
- const twoPow54 = 0b0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000n
22
+ /** @type {(exp: number) => bigint} */
23
+ const twoPow = exp => 1n << BigInt(exp)
15
24
 
16
25
  /**
17
- * Shifts the mantissa magnitude one bit at a time, compensating the exponent,
18
- * until `done(magnitude, bound)` holds; the sign is restored on return.
19
- * A zero mantissa is returned unchanged: it can never satisfy a lower bound,
20
- * so shifting it would not terminate.
21
- *
22
- * @param {(m: bigint) => bigint} shift
23
- * @param {number} de
24
- * @param {(m: bigint, bound: bigint) => boolean} done
25
- * @returns {(_: BigFloat) => (bound: bigint) => BigFloat}
26
+ * Doubles the mantissa magnitude one bit at a time, compensating the exponent,
27
+ * until it reaches `min`; the sign is restored on return. A zero mantissa is
28
+ * returned unchanged: it can never reach a lower bound, so shifting it would
29
+ * not terminate.
30
+ *
31
+ * There is no downward twin: a magnitude that overshoots is brought back by
32
+ * `truncate`, which has to know what it cut off and so cannot be a shift loop.
33
+ *
34
+ * @type {(_: BigFloat) => (min: bigint) => BigFloat}
26
35
  */
27
- const normalizeMantissa = (shift, de, done) => ([m, e]) => bound => {
36
+ const increaseMantissa = ([m, e]) => min => {
28
37
  if (m === 0n) {
29
38
  return [m, e]
30
39
  }
31
40
  const s = sign(m)
32
41
  m = abs(m)
33
- while (true) {
34
- if (done(m, bound)) {
35
- return [BigInt(s) * m, e]
36
- }
37
- m = shift(m)
38
- e += de
42
+ while (m < min) {
43
+ m <<= 1n
44
+ e -= 1
39
45
  }
46
+ return [BigInt(s) * m, e]
40
47
  }
41
48
 
42
- const increaseMantissa = normalizeMantissa(m => m << 1n, -1, (m, min) => m >= min)
43
-
44
- const decreaseMantissa = normalizeMantissa(m => m >> 1n, +1, (m, max) => m < max)
45
-
46
49
  /** @type {(base: bigint) => (exp: number) => bigint} */
47
50
  const pow = base => exp => base ** BigInt(exp)
48
51
 
@@ -54,6 +57,21 @@ export const multiply = ([m, e]) => mul => [m * mul, e]
54
57
  /** @type {(_: BigFloat) => (div: bigint) => _BigFloatWithRemainder} */
55
58
  const divide = ([m, e]) => div => [[m / div, e], m % div]
56
59
 
60
+ /**
61
+ * IEEE-754 binary64 — the format of a JavaScript `number`.
62
+ *
63
+ * Both exponents are *ulp* exponents, the same units a `BigFloat`'s own
64
+ * exponent is in: `minExp` is the exponent of the smallest subnormal
65
+ * (`2^-1074`), not the smallest normal's `-1022`, and `maxExp` is the exponent
66
+ * the largest finite value carries once its mantissa fills `precision` bits
67
+ * (`(2^53 - 1) * 2^971`). The two conventions differ by the precision, and a
68
+ * `Format` that mixes them is off by exactly that much, so this one never
69
+ * leaves `BigFloat`'s units.
70
+ *
71
+ * @type {Format}
72
+ */
73
+ export const binary64 = { precision: 53, minExp: -1074, maxExp: 971 }
74
+
57
75
  /**
58
76
  * Runs `f` on the magnitude `[abs(m), e]` and restores the sign of `m` on the
59
77
  * result: operations on signed mantissas factor through the magnitude.
@@ -63,25 +81,66 @@ const divide = ([m, e]) => div => [[m / div, e], m % div]
63
81
  const withSign = (m, e) => f => multiply(f([abs(m), e]))(BigInt(sign(m)))
64
82
 
65
83
  /**
66
- * Re-normalizes a magnitude that rounding carried out of 53 bits: rounding
67
- * `2^53 - 1` up yields exactly `2^53`, one bit wider than `round53` promises.
68
- * The bit shifted out is always 0 there, so the value is unchanged and no
69
- * second rounding decision is needed.
84
+ * Truncates a magnitude to `precision + 1` bits, folding the bits it drops
85
+ * into the remainder. A magnitude already that narrow is returned unchanged.
86
+ *
87
+ * @type {(precision: number) => (_: _BigFloatWithRemainder) => _BigFloatWithRemainder}
88
+ */
89
+ const truncate = precision => ([[m, e], r]) => {
90
+ const k = bitLength(m) - BigInt(precision + 1)
91
+ return k <= 0n ? [[m, e], r] : [[m >> k, e + Number(k)], r | (m & mask(k))]
92
+ }
93
+
94
+ /**
95
+ * Scales a non-zero decimal magnitude `dm * 10^de` into `[[m, e], r]` where
96
+ * `m` holds exactly `precision + 1` bits.
97
+ *
98
+ * One bit more than the precision is kept on purpose. Truncating to *any*
99
+ * position at or below the bit the rounding turns on, plus a remainder saying
100
+ * whether anything was cut off, is everything a single correctly-rounded
101
+ * decision needs — at this precision or at any coarser one, which is what
102
+ * makes the subnormal range reachable without rounding twice.
70
103
  *
71
- * @type {(magnitude: BigFloat) => BigFloat}
104
+ * @type {(precision: number) => (magnitude: BigFloat) => _BigFloatWithRemainder}
72
105
  */
73
- const renormalize53 = ([m, e]) => m === twoPow53 ? [m >> 1n, e + 1] : [m, e]
74
-
75
- /** @type {(_: _BigFloatWithRemainder) => BigFloat} */
76
- const round53 = ([[m, e], r]) =>
77
- withSign(m, e)(([mAbs]) => {
78
- const [m54, e54] = decreaseMantissa([mAbs, e])(twoPow54)
79
- const o54 = m54 & 1n
80
- const m53 = m54 >> 1n
81
- const e53 = e54 + 1
82
- const tie = o54 === 1n && r === 0n && mAbs === m54 >> BigInt(e - e54)
83
- return renormalize53([m53 + (tie ? m53 & 1n : o54), e53])
84
- })
106
+ const scale = precision => ([dm, de]) => {
107
+ const lo = twoPow(precision)
108
+ if (de >= 0) {
109
+ // `dm * 10^de` is a whole number, so nothing is lost until `truncate`.
110
+ return truncate(precision)([increaseMantissa([dm * pow5(de), de])(lo), 0n])
111
+ }
112
+ const p5 = pow5(-de)
113
+ return truncate(precision)(divide(increaseMantissa([dm, de])(p5 * lo))(p5))
114
+ }
115
+
116
+ /**
117
+ * Rounds a truncated magnitude to a multiple of `2^(e + k)`, ties to even.
118
+ *
119
+ * `k` is at least 1, so the bit the decision turns on is still inside `m`:
120
+ * `dropped` against `half` is the comparison against the midpoint, and a
121
+ * non-zero remainder is what lifts an apparent midpoint above it.
122
+ *
123
+ * @type {(k: number) => (_: _BigFloatWithRemainder) => BigFloat}
124
+ */
125
+ const round = k => ([[m, e], r]) => {
126
+ const kb = BigInt(k)
127
+ const q = m >> kb
128
+ const dropped = m & mask(kb)
129
+ const half = 1n << (kb - 1n)
130
+ const up = dropped > half || (dropped === half && (r !== 0n || (q & 1n) === 1n))
131
+ return [up ? q + 1n : q, e + k]
132
+ }
133
+
134
+ /**
135
+ * Re-normalizes a magnitude that rounding carried out of `precision` bits:
136
+ * rounding `2^precision - 1` up yields exactly `2^precision`, one bit wider
137
+ * than the caller promises. The bit shifted out is always 0 there, so the
138
+ * value is unchanged and no second rounding decision is needed.
139
+ *
140
+ * @type {(precision: number) => (magnitude: BigFloat) => BigFloat}
141
+ */
142
+ const renormalize = precision => ([m, e]) =>
143
+ m === twoPow(precision) ? [m >> 1n, e + 1] : [m, e]
85
144
 
86
145
  /**
87
146
  * Converts a decimal big-float `m * 10^e` into the nearest binary big-float,
@@ -92,29 +151,58 @@ const round53 = ([[m, e], r]) =>
92
151
  * even when rounding carries out of the top bit.
93
152
  *
94
153
  * The **exponent is unbounded**: this is the correctly-rounded 53-bit value,
95
- * not a `number`. Nothing here knows binary64's exponent range, so a result
96
- * is neither turned into an infinity when it is too large for a `double` nor
97
- * cut down to the fewer-than-53 bits a subnormal actually carries. A consumer
98
- * that rounds one of these results onto the subnormal grid therefore rounds
99
- * twice and can land an ulp away from the correctly-rounded `double`: reaching
100
- * the subnormal range needs a single rounding to a precision chosen from the
101
- * target exponent, which this function does not offer yet.
154
+ * not a `number`. A result too large for a `double` is not turned into an
155
+ * infinity, and one below the normal range keeps all 53 bits instead of the
156
+ * fewer a subnormal carries. That is the honest answer when the target is not
157
+ * a `double` and the wrong starting point when it is, because rounding one
158
+ * of these results onto the subnormal grid rounds twice and can land an ulp
159
+ * away from the correctly-rounded `double`. Use {@link tryDecToFormat} with
160
+ * {@link binary64} for that; it rounds once.
102
161
  *
103
162
  * @type {(dec: BigFloat) => BigFloat}
104
163
  */
105
- export const decToBin = dec => {
106
- if (dec[0] === 0n) {
164
+ export const decToBin = ([dm, de]) => {
165
+ if (dm === 0n) {
166
+ return [0n, 0]
167
+ }
168
+ const { precision } = binary64
169
+ return withSign(dm, de)(magnitude =>
170
+ renormalize(precision)(round(1)(scale(precision)(magnitude))))
171
+ }
172
+
173
+ /**
174
+ * Converts a decimal big-float `m * 10^e` into the nearest value of `format`,
175
+ * rounding ties to even, or `null` when the magnitude is too large for the
176
+ * format to hold — the caller decides what an overflow becomes, since
177
+ * `BigFloat` has no encoding for an infinity.
178
+ *
179
+ * The result is a multiple of `2^format.minExp` carrying **at most**
180
+ * `format.precision` significant bits: exactly that many above the format's
181
+ * normal range, fewer below it, and `[0n, 0]` when the value rounds away
182
+ * entirely. Underflow to zero is not a separate signal, because `[0n, 0]` is
183
+ * the correctly-rounded answer rather than a failure to produce one.
184
+ *
185
+ * The rounding happens **once**, on the exact decimal, which is the whole
186
+ * point of taking the format rather than post-processing {@link decToBin}. The
187
+ * grid it rounds onto is `2^max(minExp, e + 1)`, where `e + 1` is the exponent
188
+ * a full-precision result would carry: full precision above the normal range's
189
+ * floor, shrinking by a bit per binade below it, down to none at all. Rounding
190
+ * to `precision` bits first and onto that grid afterwards is two roundings,
191
+ * and the first can manufacture a midpoint the true value only approached.
192
+ *
193
+ * @type {(format: Format) => (dec: BigFloat) => Nullable<BigFloat>}
194
+ */
195
+ export const tryDecToFormat = ({ precision, minExp, maxExp }) => ([dm, de]) => {
196
+ if (dm === 0n) {
107
197
  return [0n, 0]
108
198
  }
109
- if (dec[1] >= 0) {
110
- /** @type {BigFloat} */
111
- const bin = [dec[0] * pow5(dec[1]), dec[1]]
112
- const inc = increaseMantissa(bin)(twoPow53)
113
- return round53([inc, 0n])
199
+ const scaled = scale(precision)([abs(dm), de])
200
+ const [, e] = scaled[0]
201
+ const [m, resultE] = renormalize(precision)(round(Math.max(minExp - e, 1))(scaled))
202
+ if (bitLength(m) + BigInt(resultE) > BigInt(precision + maxExp)) {
203
+ return null
114
204
  }
115
- const p = pow5(-dec[1])
116
- const [m, e] = increaseMantissa(dec)(p * twoPow53)
117
- return withSign(m, e)(magnitude => round53(divide(magnitude)(p)))
205
+ return m === 0n ? [0n, 0] : multiply([m, resultE])(BigInt(sign(dm)))
118
206
  }
119
207
 
120
208
  export const proof = {
@@ -3,4 +3,6 @@ export declare const proof: {
3
3
  roundingPositive: (() => void)[];
4
4
  roundingNegative: (() => void)[];
5
5
  roundingCarry: (() => void)[];
6
+ tryDecToFormat: (() => void)[];
7
+ binary64AgreesWithNumber: () => void;
6
8
  };
@@ -1,6 +1,6 @@
1
- import { decToBin } from './module.f.mjs'
2
- import { assertEq } from '../../asserts/module.f.mjs'
3
- import { bitLength } from '../bigint/module.f.mjs'
1
+ import { binary64, decToBin, tryDecToFormat } from './module.f.mjs'
2
+ import { assert, assertEq, assertStructurallySame } from '../../asserts/module.f.mjs'
3
+ import { abs, bitLength } from '../bigint/module.f.mjs'
4
4
 
5
5
  /**
6
6
  * Checks `decToBin`'s postcondition on a non-zero result: the mantissa holds
@@ -17,6 +17,75 @@ const assertRounded = dec => value => {
17
17
  assertEq(m << BigInt(e), value, m.toString(2))
18
18
  }
19
19
 
20
+ const toBinary64 = tryDecToFormat(binary64)
21
+
22
+ /**
23
+ * The exact decimal for the dyadic value `m * 2^k`, as a `BigFloat` this
24
+ * module's own functions take: `2^k = 5^-k * 10^k` for a negative `k`, so no
25
+ * decimal digit is ever approximated on the way in. Every boundary below is
26
+ * stated as a power of two, which is the grid the format is defined on.
27
+ *
28
+ * @type {(m: bigint, k: number) => readonly [bigint, number]}
29
+ */
30
+ const dyadic = (m, k) => k >= 0 ? [m << BigInt(k), 0] : [m * 5n ** BigInt(-k), k]
31
+
32
+ /**
33
+ * Asserts that `toBinary64` answers exactly `[m, e]` for the exact decimal of
34
+ * `dyadic(dm, dk)`, and that the answer respects the format: on the
35
+ * `2^-1074` grid, and no wider than 53 bits.
36
+ *
37
+ * @type {(dyad: readonly [bigint, number]) => (expected: readonly [bigint, number]) => void}
38
+ */
39
+ const assertBinary64 = ([dm, dk]) => expected => {
40
+ const result = toBinary64(dyadic(dm, dk))
41
+ assertStructurallySame(result, expected, [dm, dk])
42
+ const [m, e] = expected
43
+ assert(e >= binary64.minExp, expected)
44
+ assert(bitLength(abs(m)) <= BigInt(binary64.precision), expected)
45
+ }
46
+
47
+ /**
48
+ * Asserts that the exact decimal of `dyadic(dm, dk)` overflows the format.
49
+ *
50
+ * @type {(dyad: readonly [bigint, number]) => void}
51
+ */
52
+ const assertOverflow = ([dm, dk]) => assertEq(toBinary64(dyadic(dm, dk)), null, [dm, dk])
53
+
54
+ /**
55
+ * Cross-checks `toBinary64` against `Number(decimal)`, which the language
56
+ * specifies to be correctly rounded — the same claim this function makes, so
57
+ * disagreement on any input is a defect in one of them.
58
+ *
59
+ * A `null` stands for an infinity, which `BigFloat` cannot encode. `-0` is
60
+ * folded into `0` by the addition: a bigint mantissa has no signed zero, so
61
+ * the sign of an underflowed value is information this representation does
62
+ * not carry.
63
+ *
64
+ * @type {(dec: readonly [bigint, number]) => void}
65
+ */
66
+ const assertSameAsNumber = ([dm, de]) => {
67
+ const expected = Number(`${dm}e${de}`)
68
+ const result = toBinary64([dm, de])
69
+ const actual = result === null
70
+ ? (dm < 0n ? -Infinity : Infinity)
71
+ : Number(result[0]) * 2 ** result[1]
72
+ assertEq(actual + 0, expected + 0, [dm, de, result])
73
+ }
74
+
75
+ /** @type {(from: number) => (to: number) => readonly number[]} */
76
+ const range = from => to => Array.from({ length: to - from + 1 }, (_, i) => from + i)
77
+
78
+ /**
79
+ * A deterministic corpus of decimals: ten mantissas of both signs against
80
+ * every decimal exponent from below the subnormal range to past the top of
81
+ * the format, so every branch of the conversion is crossed by construction.
82
+ *
83
+ * @type {readonly (readonly [bigint, number])[]}
84
+ */
85
+ const corpus = [1n, 2n, 3n, 5n, 7n, 9n, 10n, 4999999999999999n, 5000000000000001n, 12345678901234567890n]
86
+ .flatMap(dm => [dm, -dm])
87
+ .flatMap(dm => range(-340)(320).map(de => /** @type {readonly [bigint, number]} */([dm, de])))
88
+
20
89
  export const proof = {
21
90
  decToBin: [
22
91
  () => {
@@ -260,5 +329,68 @@ export const proof = {
260
329
  assertRounded([0b11_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1101n, 0])(
261
330
  0b11_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1100n)
262
331
  },
263
- ]
332
+ ],
333
+ // `tryDecToFormat(binary64)` rounds once, onto the grid the target format
334
+ // actually has. Every case below is stated as an exact decimal for a
335
+ // dyadic value, so the input names a point on that grid or a known
336
+ // distance from one.
337
+ tryDecToFormat: [
338
+ // The case this function exists for. One part in 2^126 below the
339
+ // midpoint between the subnormals 3*2^-1074 and 4*2^-1074: the
340
+ // correctly-rounded answer is 3, and `decToBin`'s 53-bit answer is the
341
+ // midpoint itself, which rounds the other way because 3 is odd.
342
+ () => {
343
+ const dec = /** @type {const} */([(7n * 2n ** 125n - 1n) * 5n ** 1200n, -1200])
344
+ assertStructurallySame(decToBin(dec), [7881299347898368n, -1125], 'the midpoint decToBin lands on')
345
+ assertStructurallySame(toBinary64(dec), [3n, -1074], 'rounded once, below the midpoint')
346
+ },
347
+ // minExp exactly: the smallest value the format holds.
348
+ () => assertBinary64([1n, -1074])([1n, -1074]),
349
+ () => assertBinary64([3n, -1074])([3n, -1074]),
350
+ // Half of it is an exact tie, and 0 is the even side.
351
+ () => assertBinary64([1n, -1075])([0n, 0]),
352
+ // A hair above that tie rounds up; a hair below rounds down.
353
+ () => assertEq(toBinary64([5n ** 1075n + 1n, -1075])?.[0], 1n),
354
+ () => assertEq(toBinary64([5n ** 1075n - 1n, -1075])?.[0], 0n),
355
+ // Ties inside the subnormal range go to even in both directions.
356
+ () => assertBinary64([7n, -1075])([4n, -1074]),
357
+ () => assertBinary64([11n, -1075])([6n, -1074]),
358
+ // minExp + 52: the smallest normal, where full precision resumes. Its
359
+ // mantissa fills all 53 bits and its exponent is still minExp.
360
+ () => assertBinary64([1n, -1022])([1n << 52n, -1074]),
361
+ () => assertBinary64([(1n << 52n) - 1n, -1074])([(1n << 52n) - 1n, -1074]),
362
+ // maxExp exactly: the largest finite value.
363
+ () => assertBinary64([(1n << 53n) - 1n, 971])([(1n << 53n) - 1n, 971]),
364
+ () => assertBinary64([-((1n << 53n) - 1n), 971])([-((1n << 53n) - 1n), 971]),
365
+ // Rounding up out of the top bit has to come back as `precision` bits,
366
+ // not `precision + 1`. Nothing else here catches a missing
367
+ // re-normalization: the overflow check is invariant under it
368
+ // (`54 + (e + 1)` is `53 + (e + 2)`) and the value is the same either
369
+ // way, so only the width assertion sees it.
370
+ () => assertBinary64([(1n << 54n) - 1n, -54])([1n << 52n, -52]),
371
+ () => assertBinary64([-((1n << 54n) - 1n), -54])([-(1n << 52n), -52]),
372
+ // Past it: 2^1024 outright, and the midpoint below it, which overflows
373
+ // by rounding up rather than by magnitude.
374
+ () => assertOverflow([1n, 1024]),
375
+ () => assertOverflow([-1n, 1024]),
376
+ () => assertOverflow([(1n << 54n) - 1n, 970]),
377
+ () => assertBinary64([(((1n << 54n) - 1n) << 970n) - 1n, 0])([(1n << 53n) - 1n, 971]),
378
+ // Underflow is an answer, not a failure: it is the correctly-rounded
379
+ // one. A bigint mantissa has no signed zero, so both signs give `0n`.
380
+ () => assertBinary64([1n, -1200])([0n, 0]),
381
+ () => assertBinary64([-1n, -1200])([0n, 0]),
382
+ () => assertBinary64([0n, 0])([0n, 0]),
383
+ () => assertEq(toBinary64([0n, -400])?.[1], 0),
384
+ // Negatives mirror positives exactly, subnormals included.
385
+ () => assertBinary64([-3n, -1074])([-3n, -1074]),
386
+ () => assertBinary64([-1n, -1022])([-(1n << 52n), -1074]),
387
+ () => assertBinary64([-1n, 0])([-(1n << 52n), -52]),
388
+ // A whole-number input takes the other scaling branch (`de >= 0`),
389
+ // both when it needs widening and when it needs truncating.
390
+ () => assertBinary64([1n, 0])([1n << 52n, -52]),
391
+ () => assertBinary64([(1n << 60n) + 1n, 0])([1n << 52n, 8]),
392
+ ],
393
+ // Cross-check against `Number(decimal)`, which the language specifies to
394
+ // be correctly rounded. 13220 decimals, every one of them agreeing.
395
+ binary64AgreesWithNumber: () => corpus.forEach(assertSameAsNumber),
264
396
  }
@@ -4,3 +4,20 @@
4
4
  * @module
5
5
  */
6
6
  export type BigFloat = readonly [bigint, number];
7
+ /**
8
+ * A binary floating-point format: how many significant bits a value may carry,
9
+ * and the range its exponent may take.
10
+ *
11
+ * `minExp` and `maxExp` are in `BigFloat`'s own units — the exponent of the
12
+ * mantissa's last bit, not of its first. So `minExp` is the exponent of the
13
+ * smallest representable value, and `maxExp` the one the largest finite value
14
+ * carries with a full `precision`-bit mantissa. The other convention (the
15
+ * exponent of the *leading* bit, as IEEE-754 states it) differs by
16
+ * `precision - 1` on one end and `precision` on the other; keeping one set of
17
+ * units is why this type restates them rather than naming IEEE's.
18
+ */
19
+ export type Format = {
20
+ readonly precision: number;
21
+ readonly minExp: number;
22
+ readonly maxExp: number;
23
+ };
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Function combinators: composition, identity, argument flipping, and the
3
- * chainable `Fn` wrapper.
2
+ * Function combinators: composition, identity, sequential iteration, argument
3
+ * flipping, and the chainable `Fn` wrapper.
4
4
  *
5
5
  * @module
6
6
  *
@@ -19,6 +19,15 @@ export declare const compose: <I, X>(g: Func<I, X>) => <O>(f: Func<X, O>) => Fun
19
19
  * @type {<T>(value: T) => T}
20
20
  */
21
21
  export declare const identity: <T>(value: T) => T;
22
+ /**
23
+ * Applies `f` to `value` `n` times sequentially.
24
+ *
25
+ * Unlike monoid `repeat`, this performs every application, taking O(n)
26
+ * applications. A non-positive `n` leaves `value` unchanged.
27
+ *
28
+ * @type {(n: bigint) => <T>(value: T) => (f: (value: T) => T) => T}
29
+ */
30
+ export declare const iterate: (n: bigint) => <T>(value: T) => (f: (value: T) => T) => T;
22
31
  /**
23
32
  * Flips the arguments of a curried function.
24
33
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Function combinators: composition, identity, argument flipping, and the
3
- * chainable `Fn` wrapper.
2
+ * Function combinators: composition, identity, sequential iteration, argument
3
+ * flipping, and the chainable `Fn` wrapper.
4
4
  *
5
5
  * @module
6
6
  *
@@ -21,6 +21,24 @@ export const compose = g => f => x => f(g(x))
21
21
  */
22
22
  export const identity = value => value
23
23
 
24
+ /**
25
+ * Applies `f` to `value` `n` times sequentially.
26
+ *
27
+ * Unlike monoid `repeat`, this performs every application, taking O(n)
28
+ * applications. A non-positive `n` leaves `value` unchanged.
29
+ *
30
+ * @type {(n: bigint) => <T>(value: T) => (f: (value: T) => T) => T}
31
+ */
32
+ export const iterate = n => value => f => {
33
+ let v = value
34
+ let i = 0n
35
+ while (i < n) {
36
+ v = f(v)
37
+ i = i + 1n
38
+ }
39
+ return v
40
+ }
41
+
24
42
  /**
25
43
  * Flips the arguments of a curried function.
26
44
  *
@@ -1,4 +1,4 @@
1
- import { fn } from './module.f.mjs'
1
+ import { fn, iterate } from './module.f.mjs'
2
2
  import { assertEq } from '../../asserts/module.f.mjs'
3
3
 
4
4
  export const proof = () => {
@@ -13,4 +13,8 @@ export const proof = () => {
13
13
 
14
14
  const result = r('hello')
15
15
  assertEq(result, 5, r)
16
+
17
+ assertEq(iterate(0n)(5)(x => x + 1), 5, iterate)
18
+ assertEq(iterate(3n)(5)(x => x + 1), 8, iterate)
19
+ assertEq(iterate(-1n)(5)(x => x + 1), 5, iterate)
16
20
  }
@@ -16,6 +16,27 @@ export type { phantomKey };
16
16
  * The field is optional (`?`) so it never needs to be present at runtime.
17
17
  * Use `phantomKey` to read the phantom type back out via a conditional type:
18
18
  * `S extends Phantom<unknown, infer T> ? T : never`.
19
+ *
20
+ * **`T` is an unchecked annotation, not a derivation** — nothing stops it from
21
+ * being wrong, and once something reads it back (e.g. `Ts<>` in
22
+ * `fjs/types/rtti/ts/types.ts`, which short-circuits to `T` instead of
23
+ * structurally recursing), a wrong `T` is trusted silently. Guard every
24
+ * `Phantom<typeof rawThunk, T>` with two asserts: one against the
25
+ * un-annotated `rawThunk` (forces the real structural check, catching a
26
+ * wrong `T`) and one against the phantom-wrapped export (catches the export
27
+ * and the raw thunk drifting apart), using `Check` from
28
+ * `fjs/types/rtti/ts/types.ts` — or `Check3`, which pairs the two into one
29
+ * assert. See `fjs/edag/module.f.mjs` (`_exp`/`exp`) for the pattern:
30
+ *
31
+ * ```ts
32
+ * const rawThunk = () => [...] as const
33
+ * export const thunk: Phantom<typeof rawThunk, MyType> = rawThunk
34
+ * type _Check = Assert<Check3<MyType, typeof rawThunk, typeof thunk>>
35
+ * ```
36
+ *
37
+ * One phantom per recursive cycle is enough: `fjs/edag` wraps only `exp`,
38
+ * the union every node kind recurses through, and the node schemas
39
+ * themselves stay un-phantomed, each pinned with a plain `Check`.
19
40
  */
20
41
  export type Phantom<S, T> = S & {
21
42
  readonly [phantomKey]?: T;
@@ -27,19 +27,19 @@ import type { Ok, Error, Result } from './types.ts';
27
27
  /**
28
28
  * Creates a successful result.
29
29
  *
30
- * @template T
30
+ * @template const T
31
31
  * @param {T} value - The value to wrap.
32
32
  * @returns {Ok<T>} A successful result containing the value.
33
33
  */
34
- export declare const ok: <T>(value: T) => Ok<T>;
34
+ export declare const ok: <const T>(value: T) => Ok<T>;
35
35
  /**
36
36
  * Creates a failed result.
37
37
  *
38
- * @template E
38
+ * @template const E
39
39
  * @param {E} e - The error to wrap.
40
40
  * @returns {Error<E>} A failed result containing the error.
41
41
  */
42
- export declare const error: <E>(e: E) => Error<E>;
42
+ export declare const error: <const E>(e: E) => Error<E>;
43
43
  /**
44
44
  * Unwraps a result, returning the value if successful or throwing the error if failed.
45
45
  *
@@ -27,7 +27,7 @@
27
27
  /**
28
28
  * Creates a successful result.
29
29
  *
30
- * @template T
30
+ * @template const T
31
31
  * @param {T} value - The value to wrap.
32
32
  * @returns {Ok<T>} A successful result containing the value.
33
33
  */
@@ -36,7 +36,7 @@ export const ok = value => ['ok', value]
36
36
  /**
37
37
  * Creates a failed result.
38
38
  *
39
- * @template E
39
+ * @template const E
40
40
  * @param {E} e - The error to wrap.
41
41
  * @returns {Error<E>} A failed result containing the error.
42
42
  */
@@ -1,17 +1,21 @@
1
1
  /**
2
- * @import { Result } from './types.ts'
2
+ * @import { Error, Ok, Result } from './types.ts'
3
+ * @import { Assert } from '../../asserts/types.ts'
4
+ * @import { Equal } from '../ts/types.ts'
3
5
  */
4
6
  declare const example: () => void;
7
+ declare const constInference: () => void;
5
8
  declare const invertTest: () => void;
6
9
  declare const mapOkTest: () => void;
7
10
  declare const okThenTest: () => void;
8
11
  export declare const proof: {
9
12
  example: typeof example;
13
+ constInference: typeof constInference;
10
14
  invertTest: typeof invertTest;
11
15
  mapOkTest: typeof mapOkTest;
12
16
  okThenTest: typeof okThenTest;
13
17
  throw: {
14
- unwrapError: () => string;
18
+ unwrapError: () => "oops";
15
19
  };
16
20
  };
17
21
  export {};
@@ -1,5 +1,7 @@
1
1
  /**
2
- * @import { Result } from './types.ts'
2
+ * @import { Error, Ok, Result } from './types.ts'
3
+ * @import { Assert } from '../../asserts/types.ts'
4
+ * @import { Equal } from '../ts/types.ts'
3
5
  */
4
6
 
5
7
  import { error, ok, unwrap, invert, mapOk, okThen } from './module.f.mjs'
@@ -18,6 +20,19 @@ const example = () => {
18
20
  assertEq(v, 'Something went wrong', 'error')
19
21
  }
20
22
 
23
+ // `ok` and `error` take `const` type parameters, so a literal argument keeps
24
+ // its literal type without an `@type {const}` cast at the call site — a tagged
25
+ // error stays a tuple of literals instead of widening to `string[]`. These two
26
+ // assertions are what fail if a modifier is dropped.
27
+ const constInference = () => {
28
+ const o = ok([1, 2])
29
+ /** @typedef {Assert<Equal<typeof o, Ok<readonly [1, 2]>>>} _ConstOk */
30
+ const e = error(['notImplemented', 'read'])
31
+ /** @typedef {Assert<Equal<typeof e, Error<readonly ['notImplemented', 'read']>>>} _ConstError */
32
+ assertEq(o[1][1], 2)
33
+ assertEq(e[1][0], 'notImplemented')
34
+ }
35
+
21
36
  const invertTest = () => {
22
37
  const [k0, v0] = invert(ok(42))
23
38
  assert(!(k0 !== 'error' || v0 !== 42), [k0, v0])
@@ -52,6 +67,7 @@ const okThenTest = () => {
52
67
 
53
68
  export const proof = {
54
69
  example,
70
+ constInference,
55
71
  invertTest,
56
72
  mapOkTest,
57
73
  okThenTest,