@fuzdev/fuz_util 0.43.0 → 0.44.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/stats.d.ts CHANGED
@@ -95,18 +95,18 @@ export declare const stats_outliers_mad: (values: Array<number>, options?: Stats
95
95
  /**
96
96
  * Common z-scores for confidence intervals.
97
97
  */
98
- export declare const CONFIDENCE_Z_SCORES: Record<number, number>;
98
+ export declare const STATS_CONFIDENCE_Z_SCORES: Record<number, number>;
99
99
  /**
100
100
  * Convert a confidence level (0-1) to a z-score.
101
101
  * Uses a lookup table for common values, approximates others.
102
102
  *
103
103
  * @example
104
104
  * ```ts
105
- * confidence_level_to_z_score(0.95); // 1.96
106
- * confidence_level_to_z_score(0.99); // 2.576
105
+ * stats_confidence_level_to_z_score(0.95); // 1.96
106
+ * stats_confidence_level_to_z_score(0.99); // 2.576
107
107
  * ```
108
108
  */
109
- export declare const confidence_level_to_z_score: (level: number) => number;
109
+ export declare const stats_confidence_level_to_z_score: (level: number) => number;
110
110
  /**
111
111
  * Configuration options for confidence interval calculation.
112
112
  */
@@ -123,4 +123,58 @@ export interface StatsConfidenceIntervalOptions {
123
123
  * @returns [lower_bound, upper_bound]
124
124
  */
125
125
  export declare const stats_confidence_interval: (values: Array<number>, options?: StatsConfidenceIntervalOptions) => [number, number];
126
+ /**
127
+ * Calculate confidence interval from summary statistics (mean, std_dev, sample_size).
128
+ * Useful when raw data is not available.
129
+ * @param mean - Mean of the data
130
+ * @param std_dev - Standard deviation of the data
131
+ * @param sample_size - Number of samples
132
+ * @param options - Configuration options
133
+ * @returns [lower_bound, upper_bound]
134
+ */
135
+ export declare const stats_confidence_interval_from_summary: (mean: number, std_dev: number, sample_size: number, options?: StatsConfidenceIntervalOptions) => [number, number];
136
+ /**
137
+ * Result from Welch's t-test calculation.
138
+ */
139
+ export interface StatsWelchTTestResult {
140
+ /** The t-statistic */
141
+ t_statistic: number;
142
+ /** Welch-Satterthwaite degrees of freedom */
143
+ degrees_of_freedom: number;
144
+ }
145
+ /**
146
+ * Calculate Welch's t-test statistic and degrees of freedom.
147
+ * Welch's t-test is more robust than Student's t-test when variances are unequal.
148
+ *
149
+ * @param mean1 - Mean of first sample
150
+ * @param std1 - Standard deviation of first sample
151
+ * @param n1 - Size of first sample
152
+ * @param mean2 - Mean of second sample
153
+ * @param std2 - Standard deviation of second sample
154
+ * @param n2 - Size of second sample
155
+ */
156
+ export declare const stats_welch_t_test: (mean1: number, std1: number, n1: number, mean2: number, std2: number, n2: number) => StatsWelchTTestResult;
157
+ /**
158
+ * Standard normal CDF approximation (Abramowitz and Stegun formula 7.1.26).
159
+ */
160
+ export declare const stats_normal_cdf: (x: number) => number;
161
+ /**
162
+ * Log gamma function approximation (Lanczos approximation).
163
+ */
164
+ export declare const stats_ln_gamma: (z: number) => number;
165
+ /**
166
+ * Approximate regularized incomplete beta function for p-value calculation.
167
+ * Uses continued fraction expansion for reasonable accuracy.
168
+ */
169
+ export declare const stats_incomplete_beta: (x: number, a: number, b: number) => number;
170
+ /**
171
+ * Approximate two-tailed p-value from t-distribution.
172
+ * For large df (>100), uses normal approximation.
173
+ * For smaller df, uses incomplete beta function.
174
+ *
175
+ * @param t - Absolute value of t-statistic
176
+ * @param df - Degrees of freedom
177
+ * @returns Two-tailed p-value
178
+ */
179
+ export declare const stats_t_distribution_p_value: (t: number, df: number) => number;
126
180
  //# sourceMappingURL=stats.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stats.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/stats.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,KAAK,CAAC,MAAM,CAAC,KAAG,MAGlD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,KAAK,CAAC,MAAM,CAAC,KAAG,MAKpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,MAAM,KAAG,MAKpE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,MAAM,KAAG,MAIrE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,KAAG,MAmBnE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,MAGxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,KAAK,CAAC,MAAM,CAAC,KAAG;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAU9E,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,qCAAqC;IACrC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvB,8BAA8B;IAC9B,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC9B,QAAQ,KAAK,CAAC,MAAM,CAAC,EACrB,UAAU,uBAAuB,KAC/B,kBAgCF,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oEAAoE;IACpE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACtC;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC9B,QAAQ,KAAK,CAAC,MAAM,CAAC,EACrB,UAAU,uBAAuB,KAC/B,kBAqEF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAMtD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,MAAM,KAAG,MAuB3D,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kGAAkG;IAClG,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GACrC,QAAQ,KAAK,CAAC,MAAM,CAAC,EACrB,UAAU,8BAA8B,KACtC,CAAC,MAAM,EAAE,MAAM,CAgBjB,CAAC"}
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/stats.ts"],"names":[],"mappings":"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"}
package/dist/stats.js CHANGED
@@ -204,7 +204,7 @@ export const stats_outliers_mad = (values, options) => {
204
204
  /**
205
205
  * Common z-scores for confidence intervals.
206
206
  */
207
- export const CONFIDENCE_Z_SCORES = {
207
+ export const STATS_CONFIDENCE_Z_SCORES = {
208
208
  0.8: 1.282,
209
209
  0.9: 1.645,
210
210
  0.95: 1.96,
@@ -217,17 +217,17 @@ export const CONFIDENCE_Z_SCORES = {
217
217
  *
218
218
  * @example
219
219
  * ```ts
220
- * confidence_level_to_z_score(0.95); // 1.96
221
- * confidence_level_to_z_score(0.99); // 2.576
220
+ * stats_confidence_level_to_z_score(0.95); // 1.96
221
+ * stats_confidence_level_to_z_score(0.99); // 2.576
222
222
  * ```
223
223
  */
224
- export const confidence_level_to_z_score = (level) => {
224
+ export const stats_confidence_level_to_z_score = (level) => {
225
225
  if (level <= 0 || level >= 1) {
226
226
  throw new Error('Confidence level must be between 0 and 1 (exclusive)');
227
227
  }
228
228
  // Check lookup table first
229
- if (level in CONFIDENCE_Z_SCORES) {
230
- return CONFIDENCE_Z_SCORES[level];
229
+ if (level in STATS_CONFIDENCE_Z_SCORES) {
230
+ return STATS_CONFIDENCE_Z_SCORES[level];
231
231
  }
232
232
  // For confidence level c, we want z such that P(-z < Z < z) = c
233
233
  // This means Φ(z) = (1 + c) / 2, so z = Φ⁻¹((1 + c) / 2)
@@ -248,15 +248,155 @@ export const confidence_level_to_z_score = (level) => {
248
248
  * @returns [lower_bound, upper_bound]
249
249
  */
250
250
  export const stats_confidence_interval = (values, options) => {
251
- // z_score takes precedence, then confidence_level, then default
252
- const z_score = options?.z_score ??
253
- (options?.confidence_level ? confidence_level_to_z_score(options.confidence_level) : null) ??
254
- DEFAULT_CONFIDENCE_Z;
255
251
  if (values.length === 0)
256
252
  return [NaN, NaN];
257
253
  const mean = stats_mean(values);
258
254
  const std_dev = stats_std_dev(values, mean);
259
- const se = std_dev / Math.sqrt(values.length);
255
+ return stats_confidence_interval_from_summary(mean, std_dev, values.length, options);
256
+ };
257
+ /**
258
+ * Calculate confidence interval from summary statistics (mean, std_dev, sample_size).
259
+ * Useful when raw data is not available.
260
+ * @param mean - Mean of the data
261
+ * @param std_dev - Standard deviation of the data
262
+ * @param sample_size - Number of samples
263
+ * @param options - Configuration options
264
+ * @returns [lower_bound, upper_bound]
265
+ */
266
+ export const stats_confidence_interval_from_summary = (mean, std_dev, sample_size, options) => {
267
+ // z_score takes precedence, then confidence_level, then default
268
+ const z_score = options?.z_score ??
269
+ (options?.confidence_level
270
+ ? stats_confidence_level_to_z_score(options.confidence_level)
271
+ : null) ??
272
+ DEFAULT_CONFIDENCE_Z;
273
+ if (sample_size === 0)
274
+ return [NaN, NaN];
275
+ const se = std_dev / Math.sqrt(sample_size);
260
276
  const margin = z_score * se;
261
277
  return [mean - margin, mean + margin];
262
278
  };
279
+ /**
280
+ * Calculate Welch's t-test statistic and degrees of freedom.
281
+ * Welch's t-test is more robust than Student's t-test when variances are unequal.
282
+ *
283
+ * @param mean1 - Mean of first sample
284
+ * @param std1 - Standard deviation of first sample
285
+ * @param n1 - Size of first sample
286
+ * @param mean2 - Mean of second sample
287
+ * @param std2 - Standard deviation of second sample
288
+ * @param n2 - Size of second sample
289
+ */
290
+ export const stats_welch_t_test = (mean1, std1, n1, mean2, std2, n2) => {
291
+ const var1 = std1 ** 2;
292
+ const var2 = std2 ** 2;
293
+ const se1 = var1 / n1;
294
+ const se2 = var2 / n2;
295
+ const t_statistic = (mean1 - mean2) / Math.sqrt(se1 + se2);
296
+ // Welch-Satterthwaite degrees of freedom
297
+ const numerator = (se1 + se2) ** 2;
298
+ const denominator = se1 ** 2 / (n1 - 1) + se2 ** 2 / (n2 - 1);
299
+ const degrees_of_freedom = numerator / denominator;
300
+ return { t_statistic, degrees_of_freedom };
301
+ };
302
+ /**
303
+ * Standard normal CDF approximation (Abramowitz and Stegun formula 7.1.26).
304
+ */
305
+ export const stats_normal_cdf = (x) => {
306
+ const t = 1 / (1 + 0.2316419 * Math.abs(x));
307
+ const d = 0.3989423 * Math.exp((-x * x) / 2);
308
+ const p = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
309
+ return x > 0 ? 1 - p : p;
310
+ };
311
+ /**
312
+ * Log gamma function approximation (Lanczos approximation).
313
+ */
314
+ export const stats_ln_gamma = (z) => {
315
+ const g = 7;
316
+ const c = [
317
+ 0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313,
318
+ -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6,
319
+ 1.5056327351493116e-7,
320
+ ];
321
+ if (z < 0.5) {
322
+ return Math.log(Math.PI / Math.sin(Math.PI * z)) - stats_ln_gamma(1 - z);
323
+ }
324
+ const z_adj = z - 1;
325
+ let x = c[0];
326
+ for (let i = 1; i < g + 2; i++) {
327
+ x += c[i] / (z_adj + i);
328
+ }
329
+ const t = z_adj + g + 0.5;
330
+ return 0.5 * Math.log(2 * Math.PI) + (z_adj + 0.5) * Math.log(t) - t + Math.log(x);
331
+ };
332
+ /**
333
+ * Approximate regularized incomplete beta function for p-value calculation.
334
+ * Uses continued fraction expansion for reasonable accuracy.
335
+ */
336
+ export const stats_incomplete_beta = (x, a, b) => {
337
+ // Simple approximation using the relationship between beta and normal distributions
338
+ // For our use case (t-distribution p-values), this provides sufficient accuracy
339
+ if (x <= 0)
340
+ return 0;
341
+ if (x >= 1)
342
+ return 1;
343
+ // Use symmetry if needed
344
+ if (x > (a + 1) / (a + b + 2)) {
345
+ return 1 - stats_incomplete_beta(1 - x, b, a);
346
+ }
347
+ // Continued fraction approximation (first few terms)
348
+ const lnBeta = stats_ln_gamma(a) + stats_ln_gamma(b) - stats_ln_gamma(a + b);
349
+ const front = Math.exp(Math.log(x) * a + Math.log(1 - x) * b - lnBeta) / a;
350
+ // Simple continued fraction (limited iterations for speed)
351
+ let f = 1;
352
+ let c = 1;
353
+ let d = 0;
354
+ for (let m = 1; m <= 100; m++) {
355
+ const m2 = 2 * m;
356
+ // Even step
357
+ let aa = (m * (b - m) * x) / ((a + m2 - 1) * (a + m2));
358
+ d = 1 + aa * d;
359
+ if (Math.abs(d) < 1e-30)
360
+ d = 1e-30;
361
+ c = 1 + aa / c;
362
+ if (Math.abs(c) < 1e-30)
363
+ c = 1e-30;
364
+ d = 1 / d;
365
+ f *= d * c;
366
+ // Odd step
367
+ aa = (-(a + m) * (a + b + m) * x) / ((a + m2) * (a + m2 + 1));
368
+ d = 1 + aa * d;
369
+ if (Math.abs(d) < 1e-30)
370
+ d = 1e-30;
371
+ c = 1 + aa / c;
372
+ if (Math.abs(c) < 1e-30)
373
+ c = 1e-30;
374
+ d = 1 / d;
375
+ const delta = d * c;
376
+ f *= delta;
377
+ if (Math.abs(delta - 1) < 1e-8)
378
+ break;
379
+ }
380
+ return front * f;
381
+ };
382
+ /**
383
+ * Approximate two-tailed p-value from t-distribution.
384
+ * For large df (>100), uses normal approximation.
385
+ * For smaller df, uses incomplete beta function.
386
+ *
387
+ * @param t - Absolute value of t-statistic
388
+ * @param df - Degrees of freedom
389
+ * @returns Two-tailed p-value
390
+ */
391
+ export const stats_t_distribution_p_value = (t, df) => {
392
+ // Use normal approximation for large df
393
+ if (df > 100) {
394
+ return 2 * (1 - stats_normal_cdf(t));
395
+ }
396
+ // For smaller df, use a more accurate approximation
397
+ // Based on the incomplete beta function relationship
398
+ const x = df / (df + t * t);
399
+ const a = df / 2;
400
+ const b = 0.5;
401
+ return stats_incomplete_beta(x, a, b);
402
+ };
package/dist/string.d.ts CHANGED
@@ -48,4 +48,17 @@ export declare const strip_ansi: (str: string) => string;
48
48
  * @source https://2ality.com/2025/04/stringification-javascript.html
49
49
  */
50
50
  export declare const stringify: (value: unknown) => string;
51
+ /**
52
+ * Calculate the display width of a string in terminal columns.
53
+ * - Strips ANSI escape codes (they have 0 width)
54
+ * - Emojis and other wide characters take 2 columns
55
+ * - Tab characters take 4 columns
56
+ * - Newlines and other control characters take 0 columns
57
+ * - Uses `Intl.Segmenter` to properly handle grapheme clusters (e.g., family emoji "👨‍👩‍👧‍👦")
58
+ */
59
+ export declare const string_display_width: (str: string) => number;
60
+ /**
61
+ * Pad a string to a target display width (accounting for wide characters).
62
+ */
63
+ export declare const pad_width: (str: string, target_width: number, align?: "left" | "right") => string;
51
64
  //# sourceMappingURL=string.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"string.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/string.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,EAAE,WAAW,MAAM,EAAE,eAAc,KAAG,MAMzE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAG9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAG5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAK9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAK/D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,EAAE,SAAS,MAAM,KAAG,MAG9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,EAAE,SAAS,MAAM,KAAG,MAG5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,KAAG,MAK1B,CAAC;AAEd;;GAEG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,eAAY,KAAG,MAC9C,CAAC;AAE3B;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,KAAG,MACI,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAAsD,CAAC;AAEhG;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,OAAO,KAAG,MACwC,CAAC"}
1
+ {"version":3,"file":"string.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/string.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,EAAE,WAAW,MAAM,EAAE,eAAc,KAAG,MAMzE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAG9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAG5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAK9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAK/D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,EAAE,SAAS,MAAM,KAAG,MAG9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,EAAE,SAAS,MAAM,KAAG,MAG5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,KAAG,MAK1B,CAAC;AAEd;;GAEG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,eAAY,KAAG,MAC9C,CAAC;AAE3B;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,KAAG,MACI,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAAsD,CAAC;AAEhG;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,OAAO,KAAG,MACwC,CAAC;AAEpF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,MAAM,KAAG,MAuClD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GACrB,KAAK,MAAM,EACX,cAAc,MAAM,EACpB,QAAO,MAAM,GAAG,OAAgB,KAC9B,MAQF,CAAC"}
package/dist/string.js CHANGED
@@ -90,3 +90,61 @@ export const strip_ansi = (str) => str.replaceAll(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
90
90
  * @source https://2ality.com/2025/04/stringification-javascript.html
91
91
  */
92
92
  export const stringify = (value) => typeof value === 'bigint' ? value + 'n' : (JSON.stringify(value) ?? String(value)); // eslint-disable-line @typescript-eslint/no-unnecessary-condition
93
+ /**
94
+ * Calculate the display width of a string in terminal columns.
95
+ * - Strips ANSI escape codes (they have 0 width)
96
+ * - Emojis and other wide characters take 2 columns
97
+ * - Tab characters take 4 columns
98
+ * - Newlines and other control characters take 0 columns
99
+ * - Uses `Intl.Segmenter` to properly handle grapheme clusters (e.g., family emoji "👨‍👩‍👧‍👦")
100
+ */
101
+ export const string_display_width = (str) => {
102
+ // Strip ANSI codes first (they have 0 display width)
103
+ const clean = strip_ansi(str);
104
+ let width = 0;
105
+ const segmenter = new Intl.Segmenter();
106
+ for (const { segment } of segmenter.segment(clean)) {
107
+ const code = segment.codePointAt(0);
108
+ // Handle control characters
109
+ if (code === 0x09) {
110
+ // Tab = 4 columns
111
+ width += 4;
112
+ continue;
113
+ }
114
+ if (code < 0x20 || (code >= 0x7f && code < 0xa0)) {
115
+ // Other control characters (including newline) = 0 width
116
+ continue;
117
+ }
118
+ // Emoji and other wide characters (rough heuristic)
119
+ // - Most emoji are in range 0x1F300-0x1FAFF
120
+ // - Some are in 0x2600-0x27BF (misc symbols)
121
+ // - CJK characters 0x4E00-0x9FFF also double-width
122
+ // - Grapheme clusters with multiple code points (like ZWJ sequences) are typically emoji
123
+ if (segment.length > 1 || // Multi-codepoint graphemes (ZWJ sequences, etc.)
124
+ (code >= 0x1f300 && code <= 0x1faff) ||
125
+ (code >= 0x2600 && code <= 0x27bf) ||
126
+ (code >= 0x1f600 && code <= 0x1f64f) ||
127
+ (code >= 0x1f680 && code <= 0x1f6ff) ||
128
+ (code >= 0x4e00 && code <= 0x9fff) // CJK
129
+ ) {
130
+ width += 2;
131
+ }
132
+ else {
133
+ width += 1;
134
+ }
135
+ }
136
+ return width;
137
+ };
138
+ /**
139
+ * Pad a string to a target display width (accounting for wide characters).
140
+ */
141
+ export const pad_width = (str, target_width, align = 'left') => {
142
+ const current_width = string_display_width(str);
143
+ const padding = Math.max(0, target_width - current_width);
144
+ if (align === 'left') {
145
+ return str + ' '.repeat(padding);
146
+ }
147
+ else {
148
+ return ' '.repeat(padding) + str;
149
+ }
150
+ };
package/dist/time.d.ts CHANGED
@@ -56,6 +56,10 @@ export declare const time_ns_to_sec: (ns: number) => number;
56
56
  * Time unit for formatting.
57
57
  */
58
58
  export type TimeUnit = 'ns' | 'us' | 'ms' | 's';
59
+ /**
60
+ * Display labels for time units (uses proper Unicode μ for microseconds).
61
+ */
62
+ export declare const TIME_UNIT_DISPLAY: Record<TimeUnit, string>;
59
63
  /**
60
64
  * Detect the best time unit for a set of nanosecond values.
61
65
  * Chooses the unit where most values fall in the range 1-9999.
@@ -1 +1 @@
1
- {"version":3,"file":"time.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/time.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,WAAW,KAAK;IACrB,sCAAsC;IACtC,GAAG,EAAE,MAAM,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAKxB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAmCF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,OAAQ,CAAC;AACpC,eAAO,MAAM,cAAc,UAAY,CAAC;AACxC,eAAO,MAAM,eAAe,aAAgB,CAAC;AAE7C;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,IAAI,MAAM,KAAG,MAA8B,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,KAAK,CAAC,MAAM,CAAC,KAAG,QAqBhE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,MAAM,EAAE,MAAM,QAAQ,EAAE,WAAU,MAAU,KAAG,MAa9E,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI,MAAM,EAAE,WAAU,MAAU,KAAG,MAavE,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,GAAU,CAAC,EACjC,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,QAAO,KAAqB,KAC1B,OAAO,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAC,CAgBzC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EAC1B,IAAI,MAAM,CAAC,EACX,QAAO,KAAqB,KAC1B;IAAC,MAAM,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAgBhC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,GACxB,IAAI,MAAM,OAAO,EACjB,YAAY,MAAM,EAClB,QAAO,KAAqB,KAC1B,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAWvB,CAAC"}
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/time.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,WAAW,KAAK;IACrB,sCAAsC;IACtC,GAAG,EAAE,MAAM,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAKxB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAmCF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,OAAQ,CAAC;AACpC,eAAO,MAAM,cAAc,UAAY,CAAC;AACxC,eAAO,MAAM,eAAe,aAAgB,CAAC;AAE7C;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,IAAI,MAAM,KAAG,MAA8B,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AAEhD;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAA0C,CAAC;AAElG;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,KAAK,CAAC,MAAM,CAAC,KAAG,QAqBhE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,MAAM,EAAE,MAAM,QAAQ,EAAE,WAAU,MAAU,KAAG,MAa9E,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI,MAAM,EAAE,WAAU,MAAU,KAAG,MAavE,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,GAAU,CAAC,EACjC,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,QAAO,KAAqB,KAC1B,OAAO,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAC,CAgBzC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EAC1B,IAAI,MAAM,CAAC,EACX,QAAO,KAAqB,KAC1B;IAAC,MAAM,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAgBhC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,GACxB,IAAI,MAAM,OAAO,EACjB,YAAY,MAAM,EAClB,QAAO,KAAqB,KAC1B,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAWvB,CAAC"}
package/dist/time.js CHANGED
@@ -87,6 +87,10 @@ export const time_ns_to_ms = (ns) => ns / TIME_NS_PER_MS;
87
87
  * Convert nanoseconds to seconds.
88
88
  */
89
89
  export const time_ns_to_sec = (ns) => ns / TIME_NS_PER_SEC;
90
+ /**
91
+ * Display labels for time units (uses proper Unicode μ for microseconds).
92
+ */
93
+ export const TIME_UNIT_DISPLAY = { ns: 'ns', us: 'μs', ms: 'ms', s: 's' };
90
94
  /**
91
95
  * Detect the best time unit for a set of nanosecond values.
92
96
  * Chooses the unit where most values fall in the range 1-9999.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_util",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "description": "utility belt for JS",
5
5
  "glyph": "🦕",
6
6
  "logo": "logo.svg",
@@ -8,12 +8,14 @@ import {join} from 'node:path';
8
8
  import {z} from 'zod';
9
9
 
10
10
  import {fs_exists} from './fs.js';
11
+ import {git_info_get} from './git.js';
11
12
  import type {BenchmarkResult} from './benchmark_types.js';
12
13
  import {
13
14
  benchmark_stats_compare,
14
15
  type BenchmarkComparison,
15
16
  type BenchmarkStatsComparable,
16
17
  } from './benchmark_stats.js';
18
+ import {stats_confidence_interval_from_summary} from './stats.js';
17
19
 
18
20
  // Version for forward compatibility - increment when schema changes
19
21
  const BASELINE_VERSION = 1;
@@ -128,21 +130,6 @@ export interface BenchmarkBaselineTaskComparison {
128
130
  const DEFAULT_BASELINE_PATH = '.gro/benchmarks';
129
131
  const BASELINE_FILENAME = 'baseline.json';
130
132
 
131
- /** Z-score for 95% confidence interval */
132
- const Z_95 = 1.96;
133
-
134
- /**
135
- * Calculate 95% confidence interval from mean, std_dev, and sample_size.
136
- */
137
- const calculate_confidence_interval = (
138
- mean: number,
139
- std_dev: number,
140
- sample_size: number,
141
- ): [number, number] => {
142
- const margin = Z_95 * (std_dev / Math.sqrt(sample_size));
143
- return [mean - margin, mean + margin];
144
- };
145
-
146
133
  /**
147
134
  * Convert benchmark results to baseline entries.
148
135
  */
@@ -163,28 +150,6 @@ const results_to_entries = (results: Array<BenchmarkResult>): Array<BenchmarkBas
163
150
  }));
164
151
  };
165
152
 
166
- /**
167
- * Try to get git info from the environment or git commands.
168
- */
169
- const get_git_info = async (): Promise<{commit: string | null; branch: string | null}> => {
170
- try {
171
- const {promisify} = await import('node:util');
172
- const exec = promisify((await import('node:child_process')).exec);
173
-
174
- const [commit_result, branch_result] = await Promise.all([
175
- exec('git rev-parse HEAD').catch(() => ({stdout: ''})),
176
- exec('git rev-parse --abbrev-ref HEAD').catch(() => ({stdout: ''})),
177
- ]);
178
-
179
- return {
180
- commit: commit_result.stdout.trim() || null,
181
- branch: branch_result.stdout.trim() || null,
182
- };
183
- } catch {
184
- return {commit: null, branch: null};
185
- }
186
- };
187
-
188
153
  /**
189
154
  * Save benchmark results as the current baseline.
190
155
  *
@@ -209,7 +174,7 @@ export const benchmark_baseline_save = async (
209
174
  let git_commit = options.git_commit;
210
175
  let git_branch = options.git_branch;
211
176
  if (git_commit === undefined || git_branch === undefined) {
212
- const git_info = await get_git_info();
177
+ const git_info = await git_info_get();
213
178
  git_commit ??= git_info.commit;
214
179
  git_branch ??= git_info.branch;
215
180
  }
@@ -360,7 +325,7 @@ export const benchmark_baseline_compare = async (
360
325
  mean_ns: baseline_entry.mean_ns,
361
326
  std_dev_ns: baseline_entry.std_dev_ns,
362
327
  sample_size: baseline_entry.sample_size,
363
- confidence_interval_ns: calculate_confidence_interval(
328
+ confidence_interval_ns: stats_confidence_interval_from_summary(
364
329
  baseline_entry.mean_ns,
365
330
  baseline_entry.std_dev_ns,
366
331
  baseline_entry.sample_size,
@@ -370,7 +335,7 @@ export const benchmark_baseline_compare = async (
370
335
  mean_ns: current.mean_ns,
371
336
  std_dev_ns: current.std_dev_ns,
372
337
  sample_size: current.sample_size,
373
- confidence_interval_ns: calculate_confidence_interval(
338
+ confidence_interval_ns: stats_confidence_interval_from_summary(
374
339
  current.mean_ns,
375
340
  current.std_dev_ns,
376
341
  current.sample_size,