@vbarbarosh/node-helpers 3.72.5 → 3.74.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/README.md +1 -1
- package/dist/browser.js +213 -2
- package/package.json +1 -1
- package/src/array_chunk_balanced.js +42 -0
- package/src/array_chunk_balanced.test.js +37 -0
- package/src/array_fps.js +20 -0
- package/src/array_fps.test.js +72 -0
- package/src/fs_lsr.js +3 -0
- package/src/fs_walk.js +40 -0
- package/src/round.js +86 -9
- package/src/round.test.js +18 -0
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npm install @vbarbarosh/node-helpers
|
|
|
20
20
|
A subset of `@vbarbarosh/node-helpers` is available for browser:
|
|
21
21
|
|
|
22
22
|
```html
|
|
23
|
-
<script src="https://unpkg.com/@vbarbarosh/node-helpers@3.
|
|
23
|
+
<script src="https://unpkg.com/@vbarbarosh/node-helpers@3.74.0/dist/browser.js"></script>
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
⚠️ All browser functions are exposed globally.
|
package/dist/browser.js
CHANGED
|
@@ -80,6 +80,8 @@ function serializer(replacer, cycleReplacer) {
|
|
|
80
80
|
|
|
81
81
|
var map = {
|
|
82
82
|
"./array_chunk.js": "./src/array_chunk.js",
|
|
83
|
+
"./array_chunk_balanced.js": "./src/array_chunk_balanced.js",
|
|
84
|
+
"./array_fps.js": "./src/array_fps.js",
|
|
83
85
|
"./array_gcd.js": "./src/array_gcd.js",
|
|
84
86
|
"./array_group.js": "./src/array_group.js",
|
|
85
87
|
"./array_group_map.js": "./src/array_group_map.js",
|
|
@@ -201,6 +203,88 @@ function array_chunk(array = [], limit = 1)
|
|
|
201
203
|
module.exports = array_chunk;
|
|
202
204
|
|
|
203
205
|
|
|
206
|
+
/***/ }),
|
|
207
|
+
|
|
208
|
+
/***/ "./src/array_chunk_balanced.js":
|
|
209
|
+
/*!*************************************!*\
|
|
210
|
+
!*** ./src/array_chunk_balanced.js ***!
|
|
211
|
+
\*************************************/
|
|
212
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
213
|
+
|
|
214
|
+
const clamp = __webpack_require__(/*! ./clamp */ "./src/clamp.js");
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Split an array into up to `max_chunks` balanced chunks, while ensuring that
|
|
218
|
+
* each chunk contains at least `min_items_per_chunk` items (when possible).
|
|
219
|
+
*
|
|
220
|
+
* The function distributes items as evenly as possible:
|
|
221
|
+
* - Chunk sizes differ by at most 1 item.
|
|
222
|
+
* - The number of chunks never exceeds `max_chunks`.
|
|
223
|
+
*
|
|
224
|
+
* Originally designed for distributing workload across threads
|
|
225
|
+
* (e.g. `array_chunk_balanced(jobs, max_threads, min_jobs_per_thread)`).
|
|
226
|
+
*
|
|
227
|
+
* Edge cases:
|
|
228
|
+
* - If the input array is empty → returns [].
|
|
229
|
+
* - If there are too few items to satisfy `min_items_per_chunk`, even for a single
|
|
230
|
+
* chunk, all items are placed into one chunk.
|
|
231
|
+
*
|
|
232
|
+
* @param {Array} array
|
|
233
|
+
* @param {number} max_chunks
|
|
234
|
+
* @param {number} min_items_per_chunk
|
|
235
|
+
* @returns {Array<Array>}
|
|
236
|
+
*/
|
|
237
|
+
function array_chunk_balanced(array = [], max_chunks = 1, min_items_per_chunk = 1)
|
|
238
|
+
{
|
|
239
|
+
const total_chunks = clamp(1, max_chunks, Math.floor(array.length / min_items_per_chunk));
|
|
240
|
+
const items_per_chunk = Math.floor(array.length / total_chunks);
|
|
241
|
+
let extra = array.length % total_chunks;
|
|
242
|
+
|
|
243
|
+
const out = [];
|
|
244
|
+
for (let i = 0; i < array.length; ) {
|
|
245
|
+
const size = items_per_chunk + (extra ? 1 : 0);
|
|
246
|
+
out.push(array.slice(i, i + size));
|
|
247
|
+
i += size;
|
|
248
|
+
if (extra) {
|
|
249
|
+
extra--;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = array_chunk_balanced;
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
/***/ }),
|
|
259
|
+
|
|
260
|
+
/***/ "./src/array_fps.js":
|
|
261
|
+
/*!**************************!*\
|
|
262
|
+
!*** ./src/array_fps.js ***!
|
|
263
|
+
\**************************/
|
|
264
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
265
|
+
|
|
266
|
+
const round = __webpack_require__(/*! ./round */ "./src/round.js");
|
|
267
|
+
|
|
268
|
+
const EPS = 0.00001;
|
|
269
|
+
|
|
270
|
+
function array_fps(duration_sec, fps)
|
|
271
|
+
{
|
|
272
|
+
const total_frames = Math.floor(duration_sec * fps);
|
|
273
|
+
|
|
274
|
+
if (total_frames <= 1) {
|
|
275
|
+
return [0];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const out = [];
|
|
279
|
+
for (let i = 0; i < total_frames; ++i) {
|
|
280
|
+
out.push(round((i / (total_frames - 1)) * (duration_sec - EPS), EPS));
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
module.exports = array_fps;
|
|
286
|
+
|
|
287
|
+
|
|
204
288
|
/***/ }),
|
|
205
289
|
|
|
206
290
|
/***/ "./src/array_gcd.js":
|
|
@@ -644,6 +728,22 @@ function array_unique_last(array, fn = identity)
|
|
|
644
728
|
module.exports = array_unique_last;
|
|
645
729
|
|
|
646
730
|
|
|
731
|
+
/***/ }),
|
|
732
|
+
|
|
733
|
+
/***/ "./src/clamp.js":
|
|
734
|
+
/*!**********************!*\
|
|
735
|
+
!*** ./src/clamp.js ***!
|
|
736
|
+
\**********************/
|
|
737
|
+
/***/ ((module) => {
|
|
738
|
+
|
|
739
|
+
function clamp(min, max, value)
|
|
740
|
+
{
|
|
741
|
+
return Math.max(min, Math.min(max, value));
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
module.exports = clamp;
|
|
745
|
+
|
|
746
|
+
|
|
647
747
|
/***/ }),
|
|
648
748
|
|
|
649
749
|
/***/ "./src/date_add_hours.js":
|
|
@@ -2124,6 +2224,117 @@ function random_int(min, max)
|
|
|
2124
2224
|
module.exports = random_int;
|
|
2125
2225
|
|
|
2126
2226
|
|
|
2227
|
+
/***/ }),
|
|
2228
|
+
|
|
2229
|
+
/***/ "./src/round.js":
|
|
2230
|
+
/*!**********************!*\
|
|
2231
|
+
!*** ./src/round.js ***!
|
|
2232
|
+
\**********************/
|
|
2233
|
+
/***/ ((module) => {
|
|
2234
|
+
|
|
2235
|
+
function round(value, precision = 1)
|
|
2236
|
+
{
|
|
2237
|
+
if (precision === 0) {
|
|
2238
|
+
return value;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
const factor = 1 / precision;
|
|
2242
|
+
const sign = Math.sign(value);
|
|
2243
|
+
|
|
2244
|
+
// Count decimal places for the cleanup (toFixed)
|
|
2245
|
+
const exponent = get_decimal_places(precision);
|
|
2246
|
+
|
|
2247
|
+
// Small epsilon helps fix most floating point edge cases involving ties
|
|
2248
|
+
const epsilon = 1e-12*sign;
|
|
2249
|
+
|
|
2250
|
+
// Scale → Nudge → Round → Unscale
|
|
2251
|
+
const final = (Math.round(value*factor + epsilon) / factor);
|
|
2252
|
+
|
|
2253
|
+
// Cleanup trailing garbage such as: 34.038000000000004 → 34.038
|
|
2254
|
+
return Number(final.toFixed(exponent));
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
function get_decimal_places(precision)
|
|
2258
|
+
{
|
|
2259
|
+
if (Math.floor(precision) === precision) {
|
|
2260
|
+
return 0;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
// Scientific notation support: e.g., 1e-20
|
|
2264
|
+
const e = precision.toExponential().split('e');
|
|
2265
|
+
if (e.length > 1) {
|
|
2266
|
+
return Math.abs(parseInt(e[1], 10));
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// Normal decimal notation (e.g., 0.001)
|
|
2270
|
+
return precision.toString().split('.')[1]?.length || 0;
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// // round(34.037531, 0.001) -> 34.038000000000004
|
|
2274
|
+
// function round(value, precision = 1)
|
|
2275
|
+
// {
|
|
2276
|
+
// return Math.round(value / precision) * precision;
|
|
2277
|
+
// }
|
|
2278
|
+
|
|
2279
|
+
// // round(1.005, 0.01) -> 1
|
|
2280
|
+
// function round(value, precision = 1)
|
|
2281
|
+
// {
|
|
2282
|
+
// const p = 1 / precision;
|
|
2283
|
+
// return Math.round(value * p) / p;
|
|
2284
|
+
// }
|
|
2285
|
+
|
|
2286
|
+
// function round(value, precision = 1)
|
|
2287
|
+
// {
|
|
2288
|
+
// if (precision === 0) {
|
|
2289
|
+
// return value;
|
|
2290
|
+
// }
|
|
2291
|
+
// const factor = 1 / precision;
|
|
2292
|
+
// const epsilon = 1e-12 * Math.sign(value);
|
|
2293
|
+
// return Number((Math.round((value * factor) + epsilon) / factor).toPrecision(15));
|
|
2294
|
+
// }
|
|
2295
|
+
|
|
2296
|
+
// function round(value, precision = 1)
|
|
2297
|
+
// {
|
|
2298
|
+
// if (precision === 0) {
|
|
2299
|
+
// return value;
|
|
2300
|
+
// }
|
|
2301
|
+
//
|
|
2302
|
+
// const factor = 1 / precision;
|
|
2303
|
+
//
|
|
2304
|
+
// // The exponent is critical for defining the precision of the cleanup
|
|
2305
|
+
// const exponent = Math.max(0, String(precision).length - 2);
|
|
2306
|
+
//
|
|
2307
|
+
// // 1. Calculate the scaled value.
|
|
2308
|
+
// let scaled = value * factor;
|
|
2309
|
+
//
|
|
2310
|
+
// // 2. Strategic Nudge: Add a tiny, signed epsilon (1e-12) to correct floating-point
|
|
2311
|
+
// // representation errors and force symmetric rounding (as Math.abs failed).
|
|
2312
|
+
// const epsilon = 1e-12 * Math.sign(value);
|
|
2313
|
+
//
|
|
2314
|
+
// // 3. Round the nudged value.
|
|
2315
|
+
// const roundedScaled = Math.round(scaled + epsilon);
|
|
2316
|
+
//
|
|
2317
|
+
// // 4. Unscale the value.
|
|
2318
|
+
// const result = roundedScaled / factor;
|
|
2319
|
+
//
|
|
2320
|
+
// // 5. CRITICAL FINAL STEP: Use .toFixed() with the required decimal places for cleanup.
|
|
2321
|
+
// // This stabilizes the result against the strict equality checks, including
|
|
2322
|
+
// // the safe integer boundary errors caused by the final division.
|
|
2323
|
+
// return Number(result.toFixed(exponent));
|
|
2324
|
+
// }
|
|
2325
|
+
|
|
2326
|
+
// 💎 The most precise solution, but - extra dependency.
|
|
2327
|
+
//
|
|
2328
|
+
// const Decimal = require('decimal.js');
|
|
2329
|
+
//
|
|
2330
|
+
// function round(value, precision = 1)
|
|
2331
|
+
// {
|
|
2332
|
+
// return new Decimal(value).div(precision).round().times(precision).toNumber();
|
|
2333
|
+
// }
|
|
2334
|
+
|
|
2335
|
+
module.exports = round;
|
|
2336
|
+
|
|
2337
|
+
|
|
2127
2338
|
/***/ }),
|
|
2128
2339
|
|
|
2129
2340
|
/***/ "./src/urlmod.js":
|
|
@@ -2273,10 +2484,10 @@ var __webpack_exports__ = {};
|
|
|
2273
2484
|
\******************************/
|
|
2274
2485
|
const ns = new URL(document.currentScript.src).searchParams.get('var') ?? 'h';
|
|
2275
2486
|
if (typeof window[ns] !== 'undefined') {
|
|
2276
|
-
console.log(`❌ @vbarbarosh/node-helpers@${"3.
|
|
2487
|
+
console.log(`❌ @vbarbarosh/node-helpers@${"3.74.0"} was not injected — window.${ns} is already in use`);
|
|
2277
2488
|
}
|
|
2278
2489
|
else {
|
|
2279
|
-
console.log(`🎉 @vbarbarosh/node-helpers@${"3.
|
|
2490
|
+
console.log(`🎉 @vbarbarosh/node-helpers@${"3.74.0"} successfully exposed as window.${ns}`);
|
|
2280
2491
|
window[ns] = {};
|
|
2281
2492
|
// https://github.com/webpack/webpack/issues/625
|
|
2282
2493
|
// https://webpack.js.org/guides/dependency-management/#require-context
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Vladimir Barbarosh <vladimir.barbarosh@gmail.com>",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@vbarbarosh/node-helpers",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.74.0",
|
|
6
6
|
"description": "A set of helpers for JavaScript/Node.js",
|
|
7
7
|
"unpkg": "dist/browser.js",
|
|
8
8
|
"jsdelivr": "dist/browser.js",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const clamp = require('./clamp');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Split an array into up to `max_chunks` balanced chunks, while ensuring that
|
|
5
|
+
* each chunk contains at least `min_items_per_chunk` items (when possible).
|
|
6
|
+
*
|
|
7
|
+
* The function distributes items as evenly as possible:
|
|
8
|
+
* - Chunk sizes differ by at most 1 item.
|
|
9
|
+
* - The number of chunks never exceeds `max_chunks`.
|
|
10
|
+
*
|
|
11
|
+
* Originally designed for distributing workload across threads
|
|
12
|
+
* (e.g. `array_chunk_balanced(jobs, max_threads, min_jobs_per_thread)`).
|
|
13
|
+
*
|
|
14
|
+
* Edge cases:
|
|
15
|
+
* - If the input array is empty → returns [].
|
|
16
|
+
* - If there are too few items to satisfy `min_items_per_chunk`, even for a single
|
|
17
|
+
* chunk, all items are placed into one chunk.
|
|
18
|
+
*
|
|
19
|
+
* @param {Array} array
|
|
20
|
+
* @param {number} max_chunks
|
|
21
|
+
* @param {number} min_items_per_chunk
|
|
22
|
+
* @returns {Array<Array>}
|
|
23
|
+
*/
|
|
24
|
+
function array_chunk_balanced(array = [], max_chunks = 1, min_items_per_chunk = 1)
|
|
25
|
+
{
|
|
26
|
+
const total_chunks = clamp(1, max_chunks, Math.floor(array.length / min_items_per_chunk));
|
|
27
|
+
const items_per_chunk = Math.floor(array.length / total_chunks);
|
|
28
|
+
let extra = array.length % total_chunks;
|
|
29
|
+
|
|
30
|
+
const out = [];
|
|
31
|
+
for (let i = 0; i < array.length; ) {
|
|
32
|
+
const size = items_per_chunk + (extra ? 1 : 0);
|
|
33
|
+
out.push(array.slice(i, i + size));
|
|
34
|
+
i += size;
|
|
35
|
+
if (extra) {
|
|
36
|
+
extra--;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = array_chunk_balanced;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const array_chunk_balanced = require('./array_chunk_balanced');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('array_chunk_balanced', function () {
|
|
5
|
+
it('should handle basic input', function () {
|
|
6
|
+
assert.deepStrictEqual(array_chunk_balanced([1]), [[1]]);
|
|
7
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2]), [[1,2]]);
|
|
8
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2,3]), [[1,2,3]]);
|
|
9
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5], 3, 2), [[1,2,3],[4,5]]);
|
|
10
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6], 3, 2), [[1,2],[3,4],[5,6]]);
|
|
11
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6,7], 3, 2), [[1,2,3],[4,5],[6,7]]);
|
|
12
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5], 3, 3), [[1,2,3,4,5]]);
|
|
13
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6], 3, 3), [[1,2,3],[4,5,6]]);
|
|
14
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6,7], 3, 3), [[1,2,3,4],[5,6,7]]);
|
|
15
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2,3,4,5,6,7,8], 3, 3), [[1,2,3,4],[5,6,7,8]]);
|
|
16
|
+
});
|
|
17
|
+
it('edge: If an array is empty → returns []', function () {
|
|
18
|
+
assert.deepStrictEqual(array_chunk_balanced([]), []);
|
|
19
|
+
});
|
|
20
|
+
it('edge: If there are too few items to satisfy `min_items_per_chunk`, even for 1 chunk, everything goes into a single chunk', function () {
|
|
21
|
+
assert.deepStrictEqual(array_chunk_balanced([1,2,3], 5, 5), [[1,2,3]]);
|
|
22
|
+
});
|
|
23
|
+
it('should never produce more chunks than max_threads', function () {
|
|
24
|
+
// 12 jobs, max 5 threads, min 2 jobs per thread
|
|
25
|
+
// Correct result MUST have exactly 5 chunks
|
|
26
|
+
assert.deepStrictEqual(
|
|
27
|
+
array_chunk_balanced([1,2,3,4,5,6,7,8,9,10,11,12], 5, 2),
|
|
28
|
+
[
|
|
29
|
+
[1,2,3],
|
|
30
|
+
[4,5,6],
|
|
31
|
+
[7,8],
|
|
32
|
+
[9,10],
|
|
33
|
+
[11,12]
|
|
34
|
+
]
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
});
|
package/src/array_fps.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const round = require('./round');
|
|
2
|
+
|
|
3
|
+
const EPS = 0.00001;
|
|
4
|
+
|
|
5
|
+
function array_fps(duration_sec, fps)
|
|
6
|
+
{
|
|
7
|
+
const total_frames = Math.floor(duration_sec * fps);
|
|
8
|
+
|
|
9
|
+
if (total_frames <= 1) {
|
|
10
|
+
return [0];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const out = [];
|
|
14
|
+
for (let i = 0; i < total_frames; ++i) {
|
|
15
|
+
out.push(round((i / (total_frames - 1)) * (duration_sec - EPS), EPS));
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = array_fps;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const array_fps = require('./array_fps');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('array_fps', function () {
|
|
5
|
+
it('should handle basic input', function () {
|
|
6
|
+
assert.deepStrictEqual(array_fps(1, 1), [0]);
|
|
7
|
+
assert.deepStrictEqual(array_fps(1, 2), [0, 0.99999]);
|
|
8
|
+
assert.deepStrictEqual(array_fps(1, 3), [0, 0.49999, 0.99999]);
|
|
9
|
+
assert.deepStrictEqual(array_fps(1, 4), [0, 0.33333, 0.66666, 0.99999]);
|
|
10
|
+
assert.deepStrictEqual(array_fps(5, 20), [
|
|
11
|
+
0, 0.0505, 0.10101, 0.15151, 0.20202, 0.25252, 0.30303, 0.35353, 0.40404, 0.45454,
|
|
12
|
+
0.50505, 0.55555, 0.60606, 0.65656, 0.70707, 0.75757, 0.80808, 0.85858, 0.90909, 0.95959,
|
|
13
|
+
1.0101, 1.0606, 1.11111, 1.16161, 1.21212, 1.26262, 1.31313, 1.36363, 1.41414, 1.46464,
|
|
14
|
+
1.51515, 1.56565, 1.61616, 1.66666, 1.71717, 1.76767, 1.81818, 1.86868, 1.91919, 1.96969,
|
|
15
|
+
2.0202, 2.0707, 2.12121, 2.17171, 2.22222, 2.27272, 2.32323, 2.37373, 2.42424, 2.47474,
|
|
16
|
+
2.52525, 2.57575, 2.62626, 2.67676, 2.72727, 2.77777, 2.82828, 2.87878, 2.92929, 2.97979,
|
|
17
|
+
3.0303, 3.0808, 3.13131, 3.18181, 3.23232, 3.28282, 3.33333, 3.38383, 3.43434, 3.48484,
|
|
18
|
+
3.53535, 3.58585, 3.63636, 3.68686, 3.73737, 3.78787, 3.83838, 3.88888, 3.93939, 3.98989,
|
|
19
|
+
4.0404, 4.0909, 4.14141, 4.19191, 4.24242, 4.29292, 4.34343, 4.39393, 4.44444, 4.49494,
|
|
20
|
+
4.54545, 4.59595, 4.64646, 4.69696, 4.74747, 4.79797, 4.84848, 4.89898, 4.94949, 4.99999,
|
|
21
|
+
]);
|
|
22
|
+
assert.deepStrictEqual(array_fps(5, 30), [
|
|
23
|
+
0, 0.03356, 0.06711, 0.10067, 0.13423, 0.16778, 0.20134, 0.2349, 0.26846, 0.30201,
|
|
24
|
+
0.33557, 0.36913, 0.40268, 0.43624, 0.4698, 0.50335, 0.53691, 0.57047, 0.60403, 0.63758,
|
|
25
|
+
0.67114, 0.7047, 0.73825, 0.77181, 0.80537, 0.83892, 0.87248, 0.90604, 0.9396, 0.97315,
|
|
26
|
+
1.00671, 1.04027, 1.07382, 1.10738, 1.14094, 1.17449, 1.20805, 1.24161, 1.27517, 1.30872,
|
|
27
|
+
1.34228, 1.37584, 1.40939, 1.44295, 1.47651, 1.51006, 1.54362, 1.57718, 1.61074, 1.64429,
|
|
28
|
+
1.67785, 1.71141, 1.74496, 1.77852, 1.81208, 1.84563, 1.87919, 1.91275, 1.9463, 1.97986,
|
|
29
|
+
2.01342, 2.04698, 2.08053, 2.11409, 2.14765, 2.1812, 2.21476, 2.24832, 2.28187, 2.31543,
|
|
30
|
+
2.34899, 2.38255, 2.4161, 2.44966, 2.48322, 2.51677, 2.55033, 2.58389, 2.61744, 2.651,
|
|
31
|
+
2.68456, 2.71812, 2.75167, 2.78523, 2.81879, 2.85234, 2.8859, 2.91946, 2.95301, 2.98657,
|
|
32
|
+
3.02013, 3.05369, 3.08724, 3.1208, 3.15436, 3.18791, 3.22147, 3.25503, 3.28858, 3.32214,
|
|
33
|
+
3.3557, 3.38925, 3.42281, 3.45637, 3.48993, 3.52348, 3.55704, 3.5906, 3.62415, 3.65771,
|
|
34
|
+
3.69127, 3.72482, 3.75838, 3.79194, 3.8255, 3.85905, 3.89261, 3.92617, 3.95972, 3.99328,
|
|
35
|
+
4.02684, 4.06039, 4.09395, 4.12751, 4.16107, 4.19462, 4.22818, 4.26174, 4.29529, 4.32885,
|
|
36
|
+
4.36241, 4.39596, 4.42952, 4.46308, 4.49664, 4.53019, 4.56375, 4.59731, 4.63086, 4.66442,
|
|
37
|
+
4.69798, 4.73153, 4.76509, 4.79865, 4.83221, 4.86576, 4.89932, 4.93288, 4.96643, 4.99999,
|
|
38
|
+
]);
|
|
39
|
+
assert.deepStrictEqual(array_fps(5, 60), [
|
|
40
|
+
0, 0.01672, 0.03344, 0.05017, 0.06689, 0.08361, 0.10033, 0.11706, 0.13378, 0.1505,
|
|
41
|
+
0.16722, 0.18395, 0.20067, 0.21739, 0.23411, 0.25084, 0.26756, 0.28428, 0.301, 0.31773,
|
|
42
|
+
0.33445, 0.35117, 0.36789, 0.38461, 0.40134, 0.41806, 0.43478, 0.4515, 0.46823, 0.48495,
|
|
43
|
+
0.50167, 0.51839, 0.53512, 0.55184, 0.56856, 0.58528, 0.60201, 0.61873, 0.63545, 0.65217,
|
|
44
|
+
0.66889, 0.68562, 0.70234, 0.71906, 0.73578, 0.75251, 0.76923, 0.78595, 0.80267, 0.8194,
|
|
45
|
+
0.83612, 0.85284, 0.86956, 0.88629, 0.90301, 0.91973, 0.93645, 0.95318, 0.9699, 0.98662,
|
|
46
|
+
1.00334, 1.02006, 1.03679, 1.05351, 1.07023, 1.08695, 1.10368, 1.1204, 1.13712, 1.15384,
|
|
47
|
+
1.17057, 1.18729, 1.20401, 1.22073, 1.23746, 1.25418, 1.2709, 1.28762, 1.30435, 1.32107,
|
|
48
|
+
1.33779, 1.35451, 1.37123, 1.38796, 1.40468, 1.4214, 1.43812, 1.45485, 1.47157, 1.48829,
|
|
49
|
+
1.50501, 1.52174, 1.53846, 1.55518, 1.5719, 1.58863, 1.60535, 1.62207, 1.63879, 1.65552,
|
|
50
|
+
1.67224, 1.68896, 1.70568, 1.7224, 1.73913, 1.75585, 1.77257, 1.78929, 1.80602, 1.82274,
|
|
51
|
+
1.83946, 1.85618, 1.87291, 1.88963, 1.90635, 1.92307, 1.9398, 1.95652, 1.97324, 1.98996,
|
|
52
|
+
2.00668, 2.02341, 2.04013, 2.05685, 2.07357, 2.0903, 2.10702, 2.12374, 2.14046, 2.15719,
|
|
53
|
+
2.17391, 2.19063, 2.20735, 2.22408, 2.2408, 2.25752, 2.27424, 2.29097, 2.30769, 2.32441,
|
|
54
|
+
2.34113, 2.35785, 2.37458, 2.3913, 2.40802, 2.42474, 2.44147, 2.45819, 2.47491, 2.49163,
|
|
55
|
+
2.50836, 2.52508, 2.5418, 2.55852, 2.57525, 2.59197, 2.60869, 2.62541, 2.64214, 2.65886,
|
|
56
|
+
2.67558, 2.6923, 2.70902, 2.72575, 2.74247, 2.75919, 2.77591, 2.79264, 2.80936, 2.82608,
|
|
57
|
+
2.8428, 2.85953, 2.87625, 2.89297, 2.90969, 2.92642, 2.94314, 2.95986, 2.97658, 2.99331,
|
|
58
|
+
3.01003, 3.02675, 3.04347, 3.06019, 3.07692, 3.09364, 3.11036, 3.12708, 3.14381, 3.16053,
|
|
59
|
+
3.17725, 3.19397, 3.2107, 3.22742, 3.24414, 3.26086, 3.27759, 3.29431, 3.31103, 3.32775,
|
|
60
|
+
3.34447, 3.3612, 3.37792, 3.39464, 3.41136, 3.42809, 3.44481, 3.46153, 3.47825, 3.49498,
|
|
61
|
+
3.5117, 3.52842, 3.54514, 3.56187, 3.57859, 3.59531, 3.61203, 3.62876, 3.64548, 3.6622,
|
|
62
|
+
3.67892, 3.69564, 3.71237, 3.72909, 3.74581, 3.76253, 3.77926, 3.79598, 3.8127, 3.82942,
|
|
63
|
+
3.84615, 3.86287, 3.87959, 3.89631, 3.91304, 3.92976, 3.94648, 3.9632, 3.97993, 3.99665,
|
|
64
|
+
4.01337, 4.03009, 4.04681, 4.06354, 4.08026, 4.09698, 4.1137, 4.13043, 4.14715, 4.16387,
|
|
65
|
+
4.18059, 4.19732, 4.21404, 4.23076, 4.24748, 4.26421, 4.28093, 4.29765, 4.31437, 4.3311,
|
|
66
|
+
4.34782, 4.36454, 4.38126, 4.39798, 4.41471, 4.43143, 4.44815, 4.46487, 4.4816, 4.49832,
|
|
67
|
+
4.51504, 4.53176, 4.54849, 4.56521, 4.58193, 4.59865, 4.61538, 4.6321, 4.64882, 4.66554,
|
|
68
|
+
4.68226, 4.69899, 4.71571, 4.73243, 4.74915, 4.76588, 4.7826, 4.79932, 4.81604, 4.83277,
|
|
69
|
+
4.84949, 4.86621, 4.88293, 4.89966, 4.91638, 4.9331, 4.94982, 4.96655, 4.98327, 4.99999,
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
});
|
package/src/fs_lsr.js
CHANGED
|
@@ -3,6 +3,9 @@ const fs_fi = require('./fs_fi');
|
|
|
3
3
|
const fs_path_join = require('./fs_path_join');
|
|
4
4
|
const fs_readdir = require('./fs_readdir');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Deprecated in favor or fs_walk
|
|
8
|
+
*/
|
|
6
9
|
async function fs_lsr(pathname = '.')
|
|
7
10
|
{
|
|
8
11
|
const fi = await fs_fi(pathname);
|
package/src/fs_walk.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs_lstat = require('./fs_lstat');
|
|
2
|
+
const fs_path_join = require('./fs_path_join');
|
|
3
|
+
const fs_readdir = require('./fs_readdir');
|
|
4
|
+
const ignore = require('./ignore');
|
|
5
|
+
|
|
6
|
+
async function fs_walk({path = '.', user_friendly_status = ignore, error_handler = error_handler, signal})
|
|
7
|
+
{
|
|
8
|
+
const out = [];
|
|
9
|
+
const queue = [path];
|
|
10
|
+
while (queue.length) {
|
|
11
|
+
if (signal) {
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const current_path = queue.pop();
|
|
16
|
+
user_friendly_status(`Reading ${current_path}`);
|
|
17
|
+
const lstat = await fs_lstat(current_path);
|
|
18
|
+
lstat.path = current_path;
|
|
19
|
+
out.push(lstat);
|
|
20
|
+
if (lstat.isDirectory()) {
|
|
21
|
+
if (current_path === '/dev' || current_path === '/proc') {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const basenames = await fs_readdir(current_path);
|
|
25
|
+
basenames.forEach(v => queue.push(fs_path_join(current_path, v)));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
error_handler(error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function error_handler(error)
|
|
36
|
+
{
|
|
37
|
+
console.log(`⚠️ ${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = fs_walk;
|
package/src/round.js
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
function round(value, precision = 1)
|
|
2
|
+
{
|
|
3
|
+
if (precision === 0) {
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const factor = 1 / precision;
|
|
8
|
+
const sign = Math.sign(value);
|
|
9
|
+
|
|
10
|
+
// Count decimal places for the cleanup (toFixed)
|
|
11
|
+
const exponent = get_decimal_places(precision);
|
|
12
|
+
|
|
13
|
+
// Small epsilon helps fix most floating point edge cases involving ties
|
|
14
|
+
const epsilon = 1e-12*sign;
|
|
15
|
+
|
|
16
|
+
// Scale → Nudge → Round → Unscale
|
|
17
|
+
const final = (Math.round(value*factor + epsilon) / factor);
|
|
18
|
+
|
|
19
|
+
// Cleanup trailing garbage such as: 34.038000000000004 → 34.038
|
|
20
|
+
return Number(final.toFixed(exponent));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function get_decimal_places(precision)
|
|
24
|
+
{
|
|
25
|
+
if (Math.floor(precision) === precision) {
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Scientific notation support: e.g., 1e-20
|
|
30
|
+
const e = precision.toExponential().split('e');
|
|
31
|
+
if (e.length > 1) {
|
|
32
|
+
return Math.abs(parseInt(e[1], 10));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Normal decimal notation (e.g., 0.001)
|
|
36
|
+
return precision.toString().split('.')[1]?.length || 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
1
39
|
// // round(34.037531, 0.001) -> 34.038000000000004
|
|
2
40
|
// function round(value, precision = 1)
|
|
3
41
|
// {
|
|
@@ -11,14 +49,53 @@
|
|
|
11
49
|
// return Math.round(value * p) / p;
|
|
12
50
|
// }
|
|
13
51
|
|
|
14
|
-
function round(value, precision = 1)
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
52
|
+
// function round(value, precision = 1)
|
|
53
|
+
// {
|
|
54
|
+
// if (precision === 0) {
|
|
55
|
+
// return value;
|
|
56
|
+
// }
|
|
57
|
+
// const factor = 1 / precision;
|
|
58
|
+
// const epsilon = 1e-12 * Math.sign(value);
|
|
59
|
+
// return Number((Math.round((value * factor) + epsilon) / factor).toPrecision(15));
|
|
60
|
+
// }
|
|
61
|
+
|
|
62
|
+
// function round(value, precision = 1)
|
|
63
|
+
// {
|
|
64
|
+
// if (precision === 0) {
|
|
65
|
+
// return value;
|
|
66
|
+
// }
|
|
67
|
+
//
|
|
68
|
+
// const factor = 1 / precision;
|
|
69
|
+
//
|
|
70
|
+
// // The exponent is critical for defining the precision of the cleanup
|
|
71
|
+
// const exponent = Math.max(0, String(precision).length - 2);
|
|
72
|
+
//
|
|
73
|
+
// // 1. Calculate the scaled value.
|
|
74
|
+
// let scaled = value * factor;
|
|
75
|
+
//
|
|
76
|
+
// // 2. Strategic Nudge: Add a tiny, signed epsilon (1e-12) to correct floating-point
|
|
77
|
+
// // representation errors and force symmetric rounding (as Math.abs failed).
|
|
78
|
+
// const epsilon = 1e-12 * Math.sign(value);
|
|
79
|
+
//
|
|
80
|
+
// // 3. Round the nudged value.
|
|
81
|
+
// const roundedScaled = Math.round(scaled + epsilon);
|
|
82
|
+
//
|
|
83
|
+
// // 4. Unscale the value.
|
|
84
|
+
// const result = roundedScaled / factor;
|
|
85
|
+
//
|
|
86
|
+
// // 5. CRITICAL FINAL STEP: Use .toFixed() with the required decimal places for cleanup.
|
|
87
|
+
// // This stabilizes the result against the strict equality checks, including
|
|
88
|
+
// // the safe integer boundary errors caused by the final division.
|
|
89
|
+
// return Number(result.toFixed(exponent));
|
|
90
|
+
// }
|
|
91
|
+
|
|
92
|
+
// 💎 The most precise solution, but - extra dependency.
|
|
93
|
+
//
|
|
94
|
+
// const Decimal = require('decimal.js');
|
|
95
|
+
//
|
|
96
|
+
// function round(value, precision = 1)
|
|
97
|
+
// {
|
|
98
|
+
// return new Decimal(value).div(precision).round().times(precision).toNumber();
|
|
99
|
+
// }
|
|
23
100
|
|
|
24
101
|
module.exports = round;
|
package/src/round.test.js
CHANGED
|
@@ -6,6 +6,7 @@ const items = [
|
|
|
6
6
|
[37.5, 25, 50],
|
|
7
7
|
[34.037531, 0.001, 34.038],
|
|
8
8
|
[1.005, 0.01, 1.01],
|
|
9
|
+
[0.99999, 0.00001, 0.99999],
|
|
9
10
|
|
|
10
11
|
// Synthetic cases generated by DeepSeek
|
|
11
12
|
[1.005, 0.001, 1.005], // More precise rounding
|
|
@@ -27,6 +28,23 @@ const items = [
|
|
|
27
28
|
[999.999, 0.001, 999.999], // Rounding that causes carry-over
|
|
28
29
|
[0.0000000000001, 0.01, 0], // Extremely small number
|
|
29
30
|
[Number.MAX_SAFE_INTEGER, 1000, 9007199254741000], // Large numbers
|
|
31
|
+
|
|
32
|
+
[0.005, 0.01, 0.01],
|
|
33
|
+
[-1.5, 1, -2], // Symmetric negative rounding (Math.abs is critical here)
|
|
34
|
+
[-0.5, 1, -1], // Symmetric negative rounding for half
|
|
35
|
+
[123456789123.45, 1000, 123456789000], // Testing very large numbers
|
|
36
|
+
[1.2345e-20, 1e-20, 1e-20], // Testing very small numbers
|
|
37
|
+
[1.0000000000000001, 0.0000000000000001, 1.0000000000000000], // Testing rounding near Number.EPSILON range
|
|
38
|
+
|
|
39
|
+
[9007199254740992, 1, 9007199254740992],
|
|
40
|
+
[9007199254740991.5, 1, 9007199254740992],
|
|
41
|
+
[Number.MIN_SAFE_INTEGER, 1, Number.MIN_SAFE_INTEGER],
|
|
42
|
+
[Number.MAX_SAFE_INTEGER, 1, Number.MAX_SAFE_INTEGER],
|
|
43
|
+
|
|
44
|
+
// Out of scope of standard JS
|
|
45
|
+
// [0.004999999999999999, 0.01, 0.0],
|
|
46
|
+
// [Number.MIN_SAFE_INTEGER, 0.1, Number.MIN_SAFE_INTEGER],
|
|
47
|
+
// [Number.MAX_SAFE_INTEGER, 0.1, Number.MAX_SAFE_INTEGER],
|
|
30
48
|
];
|
|
31
49
|
|
|
32
50
|
describe('round', function () {
|