@raoh/core 0.9.0-dev.15.20261004151401.ge5893e0cc07d

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/float.js ADDED
@@ -0,0 +1,257 @@
1
+ // IEEE 754 binary32 and binary64 values as the Raoh Specification reads and writes them.
2
+ import { JsonNumber } from "./input.js";
3
+ import { tagOf } from "./copy.js";
4
+ const EXACT_INTEGER = 2n ** 53n;
5
+ /** 10^0 to 10^22, each an exact binary64, written as literals rather than computed with `**`. */
6
+ const POWERS_OF_TEN = [
7
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20,
8
+ 1e21, 1e22,
9
+ ];
10
+ const FORMATS = {
11
+ 32: { precision: 24, minExponent: -126, maxExponent: 127 },
12
+ 64: { precision: 53, minExponent: -1022, maxExponent: 1023 },
13
+ };
14
+ /**
15
+ * A float, kept with its width, as an issue's metadata holds one: the width decides how it is
16
+ * written in a message, so that a float32 bound of 0.1 reads `0.1` and not the binary64 digits of
17
+ * the same value.
18
+ */
19
+ export class Float {
20
+ value;
21
+ width;
22
+ constructor(value, width) {
23
+ this.value = width === 32 ? Math.fround(value) : value;
24
+ this.width = width;
25
+ }
26
+ get [Symbol.toStringTag]() {
27
+ return tagOf("Float");
28
+ }
29
+ valueOf() {
30
+ return this.value;
31
+ }
32
+ /** The message form: the canonical decimal, written as Java writes a float. */
33
+ toString() {
34
+ return floatMessageForm(this.value, this.width);
35
+ }
36
+ /** The observation of the float: its canonical decimal, or a tag where JSON cannot carry it. */
37
+ toJSON() {
38
+ return floatJson(this.value, this.width);
39
+ }
40
+ }
41
+ /**
42
+ * A float as the Raoh Specification observes it: its canonical decimal as a JSON number, or a
43
+ * tag, `{"float": "-0"}`, `{"float": "NaN"}`, `{"float": "+Infinity"}` or
44
+ * `{"float": "-Infinity"}`, where JSON cannot carry it.
45
+ */
46
+ export function floatJson(value, width) {
47
+ if (Number.isNaN(value))
48
+ return { float: "NaN" };
49
+ if (value === Infinity)
50
+ return { float: "+Infinity" };
51
+ if (value === -Infinity)
52
+ return { float: "-Infinity" };
53
+ if (Object.is(value, -0))
54
+ return { float: "-0" };
55
+ return new JsonNumber(value === 0 ? "0" : floatMessageForm(value, width));
56
+ }
57
+ /**
58
+ * The float of the given width nearest to ±coefficient × 10^exponent, rounding to nearest with
59
+ * ties to even, once: `Infinity` (with the sign) where it rounds beyond the largest finite value.
60
+ * The number is read exactly, so a float32 is not rounded through a binary64 first.
61
+ */
62
+ export function nearestFloat(negative, coefficient, exponent, width) {
63
+ const sign = negative ? -1 : 1;
64
+ if (coefficient === 0n) {
65
+ return negative ? -0 : 0;
66
+ }
67
+ // Clinger's fast path: an integer below 2^53 and a power of ten up to 10^22 are both exact
68
+ // binary64 values, so one multiplication or division rounds the number once.
69
+ if (width === 64 && coefficient < EXACT_INTEGER && exponent >= -22 && exponent <= 22) {
70
+ const digits = Number(coefficient);
71
+ const power = POWERS_OF_TEN[Math.abs(exponent)];
72
+ return sign * (exponent >= 0 ? digits * power : digits / power);
73
+ }
74
+ // Far enough beyond either end of binary64 that the answer is known without the arithmetic.
75
+ const magnitude = coefficient.toString().length - 1 + exponent;
76
+ if (magnitude > 400) {
77
+ return sign * Infinity;
78
+ }
79
+ if (magnitude < -400) {
80
+ return negative ? -0 : 0;
81
+ }
82
+ let numerator = coefficient;
83
+ let denominator = 1n;
84
+ if (exponent >= 0) {
85
+ numerator *= 10n ** BigInt(exponent);
86
+ }
87
+ else {
88
+ denominator = 10n ** BigInt(-exponent);
89
+ }
90
+ const { precision, minExponent, maxExponent } = FORMATS[width];
91
+ // The binary exponent of the leading bit: 2^e <= numerator / denominator < 2^(e+1).
92
+ let e = bitLength(numerator) - bitLength(denominator);
93
+ if (compareWithPowerOfTwo(numerator, denominator, e) < 0) {
94
+ e -= 1;
95
+ }
96
+ let shift = Math.max(e, minExponent) - (precision - 1);
97
+ let n = numerator;
98
+ let d = denominator;
99
+ if (shift >= 0) {
100
+ d <<= BigInt(shift);
101
+ }
102
+ else {
103
+ n <<= BigInt(-shift);
104
+ }
105
+ let mantissa = n / d;
106
+ const twice = 2n * (n - mantissa * d);
107
+ if (twice > d || (twice === d && (mantissa & 1n) === 1n)) {
108
+ mantissa += 1n;
109
+ }
110
+ if (mantissa === 1n << BigInt(precision)) {
111
+ mantissa >>= 1n;
112
+ shift += 1;
113
+ }
114
+ if (bitLength(mantissa) - 1 + shift > maxExponent) {
115
+ return sign * Infinity;
116
+ }
117
+ return sign * Number(mantissa) * 2 ** shift;
118
+ }
119
+ /**
120
+ * The canonical decimal of a finite, non-zero float as `digits × 10^exponent`, with no trailing
121
+ * zero in `digits`: the decimal of the least length that rounds to the float, the one closest to
122
+ * it, ties going to the even coefficient; where one digit is enough, the closest decimal of one or
123
+ * two digits, so that the least float64 is `4.9E-324` rather than `5E-324`.
124
+ */
125
+ export function canonicalDecimal(value, width) {
126
+ const magnitude = Math.abs(value);
127
+ const [numerator, denominator] = exactly(magnitude);
128
+ let leading = Math.floor(Math.log10(magnitude));
129
+ // log10 is near enough to be off by one at most; settle it exactly.
130
+ while (compareWithPowerOfTen(numerator, denominator, leading) < 0) {
131
+ leading -= 1;
132
+ }
133
+ while (compareWithPowerOfTen(numerator, denominator, leading + 1) >= 0) {
134
+ leading += 1;
135
+ }
136
+ for (let length = 1; length <= 17; length += 1) {
137
+ const grid = leading - length + 1;
138
+ const coefficient = nearestOnGrid(numerator, denominator, grid);
139
+ if (nearestFloat(false, coefficient, grid, width) !== magnitude) {
140
+ continue;
141
+ }
142
+ if (length > 1) {
143
+ return stripped(coefficient, grid);
144
+ }
145
+ // One digit reaches the float, so two may come closer, and the closest of those is
146
+ // as close as any decimal of one or two digits.
147
+ return stripped(nearestOnGrid(numerator, denominator, grid - 1), grid - 1);
148
+ }
149
+ throw new Error(`no decimal of up to 17 digits rounds to ${value}`);
150
+ }
151
+ /**
152
+ * The message form of a float: its canonical decimal, plain with at least one digit after the
153
+ * point where the first digit's exponent is from -3 to 6, and otherwise one digit, a point, the
154
+ * others (at least one), `E` and the exponent; `0.0`, `-0.0`, `NaN`, `Infinity`, `-Infinity`.
155
+ */
156
+ export function floatMessageForm(value, width) {
157
+ if (Number.isNaN(value)) {
158
+ return "NaN";
159
+ }
160
+ if (value === Infinity) {
161
+ return "Infinity";
162
+ }
163
+ if (value === -Infinity) {
164
+ return "-Infinity";
165
+ }
166
+ if (value === 0) {
167
+ return Object.is(value, -0) ? "-0.0" : "0.0";
168
+ }
169
+ const sign = value < 0 ? "-" : "";
170
+ const { digits, exponent } = canonicalDecimal(value, width);
171
+ const first = exponent + digits.length - 1;
172
+ if (first < -3 || first > 6) {
173
+ return `${sign}${digits[0]}.${digits.slice(1) || "0"}E${first}`;
174
+ }
175
+ if (exponent >= 0) {
176
+ return `${sign}${digits}${"0".repeat(exponent)}.0`;
177
+ }
178
+ const point = digits.length + exponent;
179
+ if (point > 0) {
180
+ return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
181
+ }
182
+ return `${sign}0.${"0".repeat(-point)}${digits}`;
183
+ }
184
+ /**
185
+ * -1, 0 or 1 as `a` comes before, with or after `b` in the float order of the value model: -∞,
186
+ * the negative values, -0, +0, the positive values, +∞, and last NaN.
187
+ */
188
+ export function compareFloats(a, b) {
189
+ const aNaN = Number.isNaN(a);
190
+ const bNaN = Number.isNaN(b);
191
+ if (aNaN || bNaN) {
192
+ return aNaN === bNaN ? 0 : aNaN ? 1 : -1;
193
+ }
194
+ if (a !== b) {
195
+ return a < b ? -1 : 1;
196
+ }
197
+ if (a === 0) {
198
+ const aNegative = Object.is(a, -0);
199
+ return aNegative === Object.is(b, -0) ? 0 : aNegative ? -1 : 1;
200
+ }
201
+ return 0;
202
+ }
203
+ /** Whether the two are the same float: +0 and -0 differ, and NaN is NaN. */
204
+ export function sameFloat(a, b) {
205
+ return Object.is(a, b);
206
+ }
207
+ function bitLength(n) {
208
+ return n === 0n ? 0 : n.toString(2).length;
209
+ }
210
+ /** The sign of numerator / denominator - 2^e. */
211
+ function compareWithPowerOfTwo(numerator, denominator, e) {
212
+ const left = e >= 0 ? numerator : numerator << BigInt(-e);
213
+ const right = e >= 0 ? denominator << BigInt(e) : denominator;
214
+ return left < right ? -1 : left > right ? 1 : 0;
215
+ }
216
+ /** The sign of numerator / denominator - 10^e. */
217
+ function compareWithPowerOfTen(numerator, denominator, e) {
218
+ const left = e >= 0 ? numerator : numerator * 10n ** BigInt(-e);
219
+ const right = e >= 0 ? denominator * 10n ** BigInt(e) : denominator;
220
+ return left < right ? -1 : left > right ? 1 : 0;
221
+ }
222
+ /** A finite, positive binary64 as an exact fraction. */
223
+ function exactly(magnitude) {
224
+ const view = new DataView(new ArrayBuffer(8));
225
+ view.setFloat64(0, magnitude);
226
+ const bits = view.getBigUint64(0);
227
+ const biased = Number((bits >> 52n) & 0x7ffn);
228
+ const fraction = bits & ((1n << 52n) - 1n);
229
+ const mantissa = biased === 0 ? fraction : fraction | (1n << 52n);
230
+ const exponent = (biased === 0 ? 1 : biased) - 1075;
231
+ return exponent >= 0 ? [mantissa << BigInt(exponent), 1n] : [mantissa, 1n << BigInt(-exponent)];
232
+ }
233
+ /** The integer nearest numerator / (denominator × 10^grid), ties to even. */
234
+ function nearestOnGrid(numerator, denominator, grid) {
235
+ let n = numerator;
236
+ let d = denominator;
237
+ if (grid >= 0) {
238
+ d *= 10n ** BigInt(grid);
239
+ }
240
+ else {
241
+ n *= 10n ** BigInt(-grid);
242
+ }
243
+ let q = n / d;
244
+ const twice = 2n * (n - q * d);
245
+ if (twice > d || (twice === d && (q & 1n) === 1n)) {
246
+ q += 1n;
247
+ }
248
+ return q;
249
+ }
250
+ function stripped(coefficient, exponent) {
251
+ let digits = coefficient.toString();
252
+ while (digits.length > 1 && digits.endsWith("0")) {
253
+ digits = digits.slice(0, -1);
254
+ exponent += 1;
255
+ }
256
+ return { digits, exponent };
257
+ }
@@ -0,0 +1,13 @@
1
+ export { Decimal } from "./decimal.ts";
2
+ export { Chain, Decoder, type Run, decoder, nullable, recover, recoverWith, withDefault } from "./decoder.ts";
3
+ export * as encode from "./encode.ts";
4
+ export { Float, type Width } from "./float.ts";
5
+ export { JsonNumber, type Kind, kindOf, parse, stringify } from "./input.ts";
6
+ export { Issue, type IssueInit, Issues, type Failed, type Ok, type Result, failed, ok } from "./issue.ts";
7
+ export { INVALID_FORMAT_JSON, type MessageResolver, Messages } from "./messages.ts";
8
+ export { messageForm, same } from "./meta.ts";
9
+ export { Path, type Segment } from "./path.ts";
10
+ export { BoolDecoder, BoundedDecoder, DecimalDecoder, DoubleDecoder, FloatDecoder, IntDecoder, LongDecoder, StringDecoder, bool, decimal, double, float, int, long, string, } from "./scalars.ts";
11
+ export { ValueSet } from "./set.ts";
12
+ export { type IssueWire, issueWire, type Wire } from "./wire.ts";
13
+ export { ABSENT, DictDecoder, Field, ListDecoder, NULL, ObjectDecoder, type Presence, type ReadWith, dict, discriminate, discriminateBy, enumOf, field, flat, list, literal, object, oneOf, optionalField, optionalNullableField, presentWith, strict, } from "./structure.ts";
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // Raoh for TypeScript: decoders that turn untyped boundary input into typed domain values.
2
+ export { Decimal } from "./decimal.js";
3
+ export { Chain, Decoder, decoder, nullable, recover, recoverWith, withDefault } from "./decoder.js";
4
+ export * as encode from "./encode.js";
5
+ export { Float } from "./float.js";
6
+ export { JsonNumber, kindOf, parse, stringify } from "./input.js";
7
+ export { Issue, Issues, failed, ok } from "./issue.js";
8
+ export { INVALID_FORMAT_JSON, Messages } from "./messages.js";
9
+ export { messageForm, same } from "./meta.js";
10
+ export { Path } from "./path.js";
11
+ export { BoolDecoder, BoundedDecoder, DecimalDecoder, DoubleDecoder, FloatDecoder, IntDecoder, LongDecoder, StringDecoder, bool, decimal, double, float, int, long, string, } from "./scalars.js";
12
+ export { ValueSet } from "./set.js";
13
+ export { issueWire } from "./wire.js";
14
+ export { ABSENT, DictDecoder, Field, ListDecoder, NULL, ObjectDecoder, dict, discriminate, discriminateBy, enumOf, field, flat, list, literal, object, oneOf, optionalField, optionalNullableField, presentWith, strict, } from "./structure.js";
@@ -0,0 +1,78 @@
1
+ /** A number of the input model: the text it is written with. */
2
+ export declare class JsonNumber {
3
+ readonly lexeme: string;
4
+ constructor(lexeme: string);
5
+ get [Symbol.toStringTag](): string;
6
+ toString(): string;
7
+ /**
8
+ * The number for `JSON.stringify` to write as this text. Where the engine has no
9
+ * `JSON.rawJSON`, a JavaScript number is given in its place only where the text it is written
10
+ * as denotes the same number; for one it would change, such as an integer beyond 2^53, this
11
+ * throws rather than write another number.
12
+ */
13
+ toJSON(): unknown;
14
+ }
15
+ /** The kinds of value of the input model, and `missing` for no value at all. */
16
+ export type Kind = "null" | "boolean" | "number" | "string" | "array" | "object" | "missing";
17
+ /**
18
+ * The kind of `value`, as an issue's `actual` names it.
19
+ *
20
+ * This is where a JavaScript value is read as a value of the input model, for a decoder and for
21
+ * {@link stringify} alike. A string is one only where it is a sequence of Unicode scalar values;
22
+ * an object is a `Map` of string keys, or an object whose data are its own properties, plain or an
23
+ * instance of a class of the program's. A value that holds its data elsewhere — a `Date`, a `Set`,
24
+ * a `String` or `Number` object, a typed array, a function — is no value of the input model, and
25
+ * reading it as an object of no members, or of its characters, would read something it is not.
26
+ *
27
+ * @throws {TypeError} for a value that is no value of the input model
28
+ */
29
+ export declare function kindOf(value: unknown): Kind;
30
+ /**
31
+ * The elements of an array of the input model, in order.
32
+ *
33
+ * @throws {TypeError} for an array with a hole, a place no value is at, which no JSON text writes
34
+ */
35
+ export declare function elementsOf(value: readonly unknown[]): unknown[];
36
+ /** Whether `value` is an object of the input model. */
37
+ export declare function isObject(value: unknown): value is object;
38
+ /**
39
+ * The lexeme of a number of the input model; `undefined` for a JavaScript number that no JSON
40
+ * text writes (NaN or an infinity). An integer is written in full, as `BigInt` writes it, so that
41
+ * 1e21 read by `JSON.parse` is still an integer to an integer decoder. A `Decimal` is written at its
42
+ * scale, so that `decimal()` reads back the decimal it is, trailing zeros and all.
43
+ */
44
+ export declare function lexemeOf(value: unknown): string | undefined;
45
+ /**
46
+ * The members of an object of the input model, in order; a member whose value is `undefined` is
47
+ * absent.
48
+ *
49
+ * @throws {TypeError} for a `Map` with a key that is not a string, and for a name that is not a
50
+ * sequence of Unicode scalar values: no JSON text names a member so
51
+ */
52
+ export declare function membersOf(value: object): [string, unknown][];
53
+ /** The member of an object of the input model, or `undefined` where it is absent. */
54
+ export declare function memberOf(value: object, name: string): unknown;
55
+ /**
56
+ * Reads a JSON text (RFC 8259) into the input model: every number a {@link JsonNumber} holding its
57
+ * lexeme, and every object a `Map` holding its members in the order written.
58
+ *
59
+ * @throws {SyntaxError} where the text is not JSON, an object repeats a member name, or a string
60
+ * holds an unpaired surrogate, none of which is in the input model
61
+ */
62
+ export declare function parse(text: string): unknown;
63
+ /**
64
+ * Writes a value of the input model as JSON text, as {@link parse} reads it back: every number as
65
+ * its lexeme, and every object's members in their order, a `Map`'s as it holds them. This is how
66
+ * a value is handed on to what reads JSON text and not JavaScript values, such as a module across
67
+ * a boundary, without a number rounded or a member moved: `JSON.stringify` writes a `Map` as `{}`
68
+ * and an object's integer-like member names before its others.
69
+ *
70
+ * What it writes, `parse` reads, as the value it was: a value is read as {@link kindOf} reads one,
71
+ * the same way a decoder reads it, so what the input model has no place for is refused here rather
72
+ * than written as text `parse` refuses or as some other value.
73
+ *
74
+ * @throws {TypeError} for `undefined` where a value has to be, a hole in an array, a number that is
75
+ * NaN or an infinity, a string or member name holding an unpaired surrogate, and any value that
76
+ * is no value of the input model
77
+ */
78
+ export declare function stringify(value: unknown): string;