ts-data-forge 6.12.0 → 6.13.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.
- package/dist/number/num.d.mts +65 -1
- package/dist/number/num.d.mts.map +1 -1
- package/dist/number/num.mjs +76 -0
- package/dist/number/num.mjs.map +1 -1
- package/package.json +1 -1
- package/src/number/num.mts +81 -0
- package/src/number/num.test.mts +62 -0
package/dist/number/num.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Decrement, type Increment, type Index, type Int, type Min, type NaNType, type NegativeIndex, type NonNegativeNumber, type NonZeroNumber, type PositiveNumber, type PositiveSafeIntWithSmallInt, type RelaxedExclude, type SmallInt, type SmallUint } from 'ts-type-forge';
|
|
1
|
+
import { type Decrement, type FiniteNumber, type Increment, type Index, type Int, type Min, type NaNType, type NegativeIndex, type NonNegativeNumber, type NonZeroNumber, type PositiveNumber, type PositiveSafeIntWithSmallInt, type RelaxedExclude, type SmallInt, type SmallUint } from 'ts-type-forge';
|
|
2
2
|
import { Result } from '../functional/index.mjs';
|
|
3
3
|
import { type SmallPositiveInt } from '../types.mjs';
|
|
4
4
|
/**
|
|
@@ -98,6 +98,70 @@ export declare namespace Num {
|
|
|
98
98
|
* wrapping an `Error` describing the invalid input.
|
|
99
99
|
*/
|
|
100
100
|
export const safeParseInt: (s: string) => Result<Int, Error>;
|
|
101
|
+
/**
|
|
102
|
+
* Safely parses a finite floating-point number from a string, returning a
|
|
103
|
+
* {@link Result} that is `Ok<FiniteNumber>` for valid input and `Err<Error>`
|
|
104
|
+
* otherwise.
|
|
105
|
+
*
|
|
106
|
+
* This is a stricter alternative to both `parseFloat` and `Number`:
|
|
107
|
+
*
|
|
108
|
+
* - Unlike `parseFloat('12abc')` (which returns `12`), trailing non-numeric
|
|
109
|
+
* characters make the whole input invalid and yield `Err`.
|
|
110
|
+
* - Unlike `Number('')` / `Number(' ')` (which return `0`), empty or
|
|
111
|
+
* whitespace-only input yields `Err`.
|
|
112
|
+
* - Unlike `Number('Infinity')` (which returns `Infinity`), non-finite values
|
|
113
|
+
* yield `Err`.
|
|
114
|
+
*
|
|
115
|
+
* The empty-string case is rejected by delegating to `parseFloat` (which
|
|
116
|
+
* returns `NaN` there) rather than hard-coding a check, while the trailing-
|
|
117
|
+
* garbage case is rejected via `Number`. Decimal values are preserved as-is,
|
|
118
|
+
* so `'12.9'` stays `12.9`.
|
|
119
|
+
*
|
|
120
|
+
* Use `Result.unwrapOk` (optionally with a `?? Number.NaN` fallback) or
|
|
121
|
+
* `Result.unwrapOkOr` to get a plain number back.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* assert.strictEqual(
|
|
127
|
+
* Result.unwrapOkOr(Num.safeParseFloat('12.9'), Number.NaN),
|
|
128
|
+
* 12.9,
|
|
129
|
+
* );
|
|
130
|
+
*
|
|
131
|
+
* assert.strictEqual(
|
|
132
|
+
* Result.unwrapOkOr(Num.safeParseFloat('-3.5'), Number.NaN),
|
|
133
|
+
* -3.5,
|
|
134
|
+
* );
|
|
135
|
+
*
|
|
136
|
+
* assert.strictEqual(
|
|
137
|
+
* Result.unwrapOkOr(Num.safeParseFloat('1e3'), Number.NaN),
|
|
138
|
+
* 1000,
|
|
139
|
+
* );
|
|
140
|
+
*
|
|
141
|
+
* // Native `parseFloat` ignores trailing non-numeric characters
|
|
142
|
+
*
|
|
143
|
+
* assert.strictEqual(Number.parseFloat('12px'), 12);
|
|
144
|
+
*
|
|
145
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('12px')));
|
|
146
|
+
*
|
|
147
|
+
* // Whitespace is not a valid number, so we return an error instead of coercing to 0.
|
|
148
|
+
*
|
|
149
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('')));
|
|
150
|
+
*
|
|
151
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat(' ')));
|
|
152
|
+
*
|
|
153
|
+
* // Infinity and NaN are not finite, so they are rejected.
|
|
154
|
+
*
|
|
155
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('Infinity')));
|
|
156
|
+
*
|
|
157
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('NaN')));
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* @param s The string to parse.
|
|
161
|
+
* @returns `Result.ok(parsedFloat)` for valid finite input, otherwise
|
|
162
|
+
* `Result.err` wrapping an `Error` describing the invalid input.
|
|
163
|
+
*/
|
|
164
|
+
export const safeParseFloat: (s: string) => Result<FiniteNumber, Error>;
|
|
101
165
|
/**
|
|
102
166
|
* Type guard that checks if a number is non-zero.
|
|
103
167
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"num.d.mts","sourceRoot":"","sources":["../../src/number/num.mts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"num.d.mts","sourceRoot":"","sources":["../../src/number/num.mts"],"names":[],"mappings":"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"}
|
package/dist/number/num.mjs
CHANGED
|
@@ -109,6 +109,82 @@ var Num;
|
|
|
109
109
|
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
|
|
110
110
|
ok(Math.trunc(viaNumber));
|
|
111
111
|
};
|
|
112
|
+
/**
|
|
113
|
+
* Safely parses a finite floating-point number from a string, returning a
|
|
114
|
+
* {@link Result} that is `Ok<FiniteNumber>` for valid input and `Err<Error>`
|
|
115
|
+
* otherwise.
|
|
116
|
+
*
|
|
117
|
+
* This is a stricter alternative to both `parseFloat` and `Number`:
|
|
118
|
+
*
|
|
119
|
+
* - Unlike `parseFloat('12abc')` (which returns `12`), trailing non-numeric
|
|
120
|
+
* characters make the whole input invalid and yield `Err`.
|
|
121
|
+
* - Unlike `Number('')` / `Number(' ')` (which return `0`), empty or
|
|
122
|
+
* whitespace-only input yields `Err`.
|
|
123
|
+
* - Unlike `Number('Infinity')` (which returns `Infinity`), non-finite values
|
|
124
|
+
* yield `Err`.
|
|
125
|
+
*
|
|
126
|
+
* The empty-string case is rejected by delegating to `parseFloat` (which
|
|
127
|
+
* returns `NaN` there) rather than hard-coding a check, while the trailing-
|
|
128
|
+
* garbage case is rejected via `Number`. Decimal values are preserved as-is,
|
|
129
|
+
* so `'12.9'` stays `12.9`.
|
|
130
|
+
*
|
|
131
|
+
* Use `Result.unwrapOk` (optionally with a `?? Number.NaN` fallback) or
|
|
132
|
+
* `Result.unwrapOkOr` to get a plain number back.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* assert.strictEqual(
|
|
138
|
+
* Result.unwrapOkOr(Num.safeParseFloat('12.9'), Number.NaN),
|
|
139
|
+
* 12.9,
|
|
140
|
+
* );
|
|
141
|
+
*
|
|
142
|
+
* assert.strictEqual(
|
|
143
|
+
* Result.unwrapOkOr(Num.safeParseFloat('-3.5'), Number.NaN),
|
|
144
|
+
* -3.5,
|
|
145
|
+
* );
|
|
146
|
+
*
|
|
147
|
+
* assert.strictEqual(
|
|
148
|
+
* Result.unwrapOkOr(Num.safeParseFloat('1e3'), Number.NaN),
|
|
149
|
+
* 1000,
|
|
150
|
+
* );
|
|
151
|
+
*
|
|
152
|
+
* // Native `parseFloat` ignores trailing non-numeric characters
|
|
153
|
+
*
|
|
154
|
+
* assert.strictEqual(Number.parseFloat('12px'), 12);
|
|
155
|
+
*
|
|
156
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('12px')));
|
|
157
|
+
*
|
|
158
|
+
* // Whitespace is not a valid number, so we return an error instead of coercing to 0.
|
|
159
|
+
*
|
|
160
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('')));
|
|
161
|
+
*
|
|
162
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat(' ')));
|
|
163
|
+
*
|
|
164
|
+
* // Infinity and NaN are not finite, so they are rejected.
|
|
165
|
+
*
|
|
166
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('Infinity')));
|
|
167
|
+
*
|
|
168
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('NaN')));
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* @param s The string to parse.
|
|
172
|
+
* @returns `Result.ok(parsedFloat)` for valid finite input, otherwise
|
|
173
|
+
* `Result.err` wrapping an `Error` describing the invalid input.
|
|
174
|
+
*/
|
|
175
|
+
Num.safeParseFloat = (s) => {
|
|
176
|
+
const viaNumber = Number(s);
|
|
177
|
+
// `Number('')` / `Number(' ')` は 0 を返すが、`parseFloat` は NaN を返す。
|
|
178
|
+
// 末尾不正文字 ('12abc' 等) は `Number` 側が NaN にするので、両者が共に
|
|
179
|
+
// 非 NaN かつ有限の場合のみ採用することで空文字・空白のみ・末尾不正・
|
|
180
|
+
// Infinity をまとめて弾く。
|
|
181
|
+
return Number.isNaN(viaNumber) ||
|
|
182
|
+
!Number.isFinite(viaNumber) ||
|
|
183
|
+
Number.isNaN(Number.parseFloat(s))
|
|
184
|
+
? err(new Error(`safeParseFloat: "${s}" is not a valid finite number`))
|
|
185
|
+
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
|
|
186
|
+
ok(viaNumber);
|
|
187
|
+
};
|
|
112
188
|
/**
|
|
113
189
|
* Type guard that checks if a number is non-zero.
|
|
114
190
|
*
|
package/dist/number/num.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"num.mjs","sources":["../../src/number/num.mts"],"sourcesContent":[null],"names":["Result.err","Result.ok"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"num.mjs","sources":["../../src/number/num.mts"],"sourcesContent":[null],"names":["Result.err","Result.ok"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/number/num.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Decrement,
|
|
3
|
+
type FiniteNumber,
|
|
3
4
|
type Increment,
|
|
4
5
|
type Index,
|
|
5
6
|
type Int,
|
|
@@ -130,6 +131,86 @@ export namespace Num {
|
|
|
130
131
|
Result.ok(Math.trunc(viaNumber) as Int);
|
|
131
132
|
};
|
|
132
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Safely parses a finite floating-point number from a string, returning a
|
|
136
|
+
* {@link Result} that is `Ok<FiniteNumber>` for valid input and `Err<Error>`
|
|
137
|
+
* otherwise.
|
|
138
|
+
*
|
|
139
|
+
* This is a stricter alternative to both `parseFloat` and `Number`:
|
|
140
|
+
*
|
|
141
|
+
* - Unlike `parseFloat('12abc')` (which returns `12`), trailing non-numeric
|
|
142
|
+
* characters make the whole input invalid and yield `Err`.
|
|
143
|
+
* - Unlike `Number('')` / `Number(' ')` (which return `0`), empty or
|
|
144
|
+
* whitespace-only input yields `Err`.
|
|
145
|
+
* - Unlike `Number('Infinity')` (which returns `Infinity`), non-finite values
|
|
146
|
+
* yield `Err`.
|
|
147
|
+
*
|
|
148
|
+
* The empty-string case is rejected by delegating to `parseFloat` (which
|
|
149
|
+
* returns `NaN` there) rather than hard-coding a check, while the trailing-
|
|
150
|
+
* garbage case is rejected via `Number`. Decimal values are preserved as-is,
|
|
151
|
+
* so `'12.9'` stays `12.9`.
|
|
152
|
+
*
|
|
153
|
+
* Use `Result.unwrapOk` (optionally with a `?? Number.NaN` fallback) or
|
|
154
|
+
* `Result.unwrapOkOr` to get a plain number back.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
*
|
|
158
|
+
* ```ts
|
|
159
|
+
* assert.strictEqual(
|
|
160
|
+
* Result.unwrapOkOr(Num.safeParseFloat('12.9'), Number.NaN),
|
|
161
|
+
* 12.9,
|
|
162
|
+
* );
|
|
163
|
+
*
|
|
164
|
+
* assert.strictEqual(
|
|
165
|
+
* Result.unwrapOkOr(Num.safeParseFloat('-3.5'), Number.NaN),
|
|
166
|
+
* -3.5,
|
|
167
|
+
* );
|
|
168
|
+
*
|
|
169
|
+
* assert.strictEqual(
|
|
170
|
+
* Result.unwrapOkOr(Num.safeParseFloat('1e3'), Number.NaN),
|
|
171
|
+
* 1000,
|
|
172
|
+
* );
|
|
173
|
+
*
|
|
174
|
+
* // Native `parseFloat` ignores trailing non-numeric characters
|
|
175
|
+
*
|
|
176
|
+
* assert.strictEqual(Number.parseFloat('12px'), 12);
|
|
177
|
+
*
|
|
178
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('12px')));
|
|
179
|
+
*
|
|
180
|
+
* // Whitespace is not a valid number, so we return an error instead of coercing to 0.
|
|
181
|
+
*
|
|
182
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('')));
|
|
183
|
+
*
|
|
184
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat(' ')));
|
|
185
|
+
*
|
|
186
|
+
* // Infinity and NaN are not finite, so they are rejected.
|
|
187
|
+
*
|
|
188
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('Infinity')));
|
|
189
|
+
*
|
|
190
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('NaN')));
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @param s The string to parse.
|
|
194
|
+
* @returns `Result.ok(parsedFloat)` for valid finite input, otherwise
|
|
195
|
+
* `Result.err` wrapping an `Error` describing the invalid input.
|
|
196
|
+
*/
|
|
197
|
+
export const safeParseFloat = (s: string): Result<FiniteNumber, Error> => {
|
|
198
|
+
const viaNumber = Number(s);
|
|
199
|
+
|
|
200
|
+
// `Number('')` / `Number(' ')` は 0 を返すが、`parseFloat` は NaN を返す。
|
|
201
|
+
// 末尾不正文字 ('12abc' 等) は `Number` 側が NaN にするので、両者が共に
|
|
202
|
+
// 非 NaN かつ有限の場合のみ採用することで空文字・空白のみ・末尾不正・
|
|
203
|
+
// Infinity をまとめて弾く。
|
|
204
|
+
return Number.isNaN(viaNumber) ||
|
|
205
|
+
!Number.isFinite(viaNumber) ||
|
|
206
|
+
Number.isNaN(Number.parseFloat(s))
|
|
207
|
+
? Result.err(
|
|
208
|
+
new Error(`safeParseFloat: "${s}" is not a valid finite number`),
|
|
209
|
+
)
|
|
210
|
+
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
|
|
211
|
+
Result.ok(viaNumber as FiniteNumber);
|
|
212
|
+
};
|
|
213
|
+
|
|
133
214
|
/**
|
|
134
215
|
* Type guard that checks if a number is non-zero.
|
|
135
216
|
*
|
package/src/number/num.test.mts
CHANGED
|
@@ -190,6 +190,68 @@ describe('Num test', () => {
|
|
|
190
190
|
});
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
+
describe('safeParseFloat', () => {
|
|
194
|
+
test('parses valid numeric strings into Ok', () => {
|
|
195
|
+
expect(Result.unwrapOk(Num.safeParseFloat('123'))).toBe(123);
|
|
196
|
+
|
|
197
|
+
expect(Result.unwrapOk(Num.safeParseFloat('-42'))).toBe(-42);
|
|
198
|
+
|
|
199
|
+
expect(Result.unwrapOk(Num.safeParseFloat('+7'))).toBe(7);
|
|
200
|
+
|
|
201
|
+
expect(Result.unwrapOk(Num.safeParseFloat('0'))).toBe(0);
|
|
202
|
+
|
|
203
|
+
expect(Result.unwrapOk(Num.safeParseFloat(' 12 '))).toBe(12);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('preserves decimal values', () => {
|
|
207
|
+
expect(Result.unwrapOk(Num.safeParseFloat('12.9'))).toBe(12.9);
|
|
208
|
+
|
|
209
|
+
expect(Result.unwrapOk(Num.safeParseFloat('-3.5'))).toBe(-3.5);
|
|
210
|
+
|
|
211
|
+
expect(Result.unwrapOk(Num.safeParseFloat('1e3'))).toBe(1000);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('rejects trailing non-numeric characters (unlike parseFloat)', () => {
|
|
215
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('123abc')));
|
|
216
|
+
|
|
217
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('12px')));
|
|
218
|
+
|
|
219
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('abc')));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('rejects empty / whitespace-only input (unlike Number)', () => {
|
|
223
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('')));
|
|
224
|
+
|
|
225
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat(' ')));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('rejects non-finite values', () => {
|
|
229
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('Infinity')));
|
|
230
|
+
|
|
231
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('-Infinity')));
|
|
232
|
+
|
|
233
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('NaN')));
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('the Err carries a descriptive Error', () => {
|
|
237
|
+
const result = Num.safeParseFloat('nope');
|
|
238
|
+
|
|
239
|
+
assert.isTrue(Result.isErr(result));
|
|
240
|
+
|
|
241
|
+
if (Result.isErr(result)) {
|
|
242
|
+
expect(Result.unwrapErr(result)).toBeInstanceOf(Error);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('composes with Result.unwrapOk + nullish fallback', () => {
|
|
247
|
+
expect(Result.unwrapOk(Num.safeParseFloat('3.14')) ?? Number.NaN).toBe(
|
|
248
|
+
3.14,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
expect(Result.unwrapOk(Num.safeParseFloat('')) ?? Number.NaN).toBeNaN();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
193
255
|
describe('isInRange', () => {
|
|
194
256
|
test('checks range (lower inclusive, upper exclusive)', () => {
|
|
195
257
|
const inRange = Num.isInRange(0, 10);
|