@@ -288,6 +288,41 @@ describe("rollingQuantile", () => {
288288 } ) ;
289289} ) ;
290290
291+ // ─── property helpers ─────────────────────────────────────────────────────────
292+
293+ /** Returns true if every non-null value in `result` is a non-negative number. */
294+ function allNonNegative ( result : readonly ( number | null | undefined ) [ ] ) : boolean {
295+ for ( const v of result ) {
296+ if ( v !== null && v !== undefined && ( typeof v !== "number" || v < - 1e-12 ) ) {
297+ return false ;
298+ }
299+ }
300+ return true ;
301+ }
302+
303+ /** Returns true if lo ≤ med ≤ hi (with tolerance) for every non-null triple. */
304+ function quantilesOrdered (
305+ lo : readonly ( number | null | undefined ) [ ] ,
306+ med : readonly ( number | null | undefined ) [ ] ,
307+ hi : readonly ( number | null | undefined ) [ ] ,
308+ ) : boolean {
309+ for ( let i = 0 ; i < lo . length ; i ++ ) {
310+ const l = lo [ i ] ;
311+ const m = med [ i ] ;
312+ const h = hi [ i ] ;
313+ if ( l === null || m === null || h === null ) {
314+ continue ;
315+ }
316+ if ( ( l as number ) > ( m as number ) + 1e-9 ) {
317+ return false ;
318+ }
319+ if ( ( m as number ) > ( h as number ) + 1e-9 ) {
320+ return false ;
321+ }
322+ }
323+ return true ;
324+ }
325+
291326// ─── property-based tests ─────────────────────────────────────────────────────
292327
293328describe ( "window_extended — property tests" , ( ) => {
@@ -299,20 +334,7 @@ describe("window_extended — property tests", () => {
299334 maxLength : 20 ,
300335 } ) ,
301336 fc . integer ( { min : 2 , max : 10 } ) ,
302- ( data , window ) => {
303- const result = vals ( rollingSem ( s ( data ) , window ) ) ;
304- for ( const v of result ) {
305- if ( v !== null && v !== undefined ) {
306- if ( typeof v !== "number" ) {
307- return false ;
308- }
309- if ( v < - 1e-12 ) {
310- return false ;
311- }
312- }
313- }
314- return true ;
315- } ,
337+ ( data , window ) => allNonNegative ( vals ( rollingSem ( s ( data ) , window ) ) ) ,
316338 ) ,
317339 ) ;
318340 } ) ;
@@ -325,26 +347,12 @@ describe("window_extended — property tests", () => {
325347 maxLength : 15 ,
326348 } ) ,
327349 fc . integer ( { min : 2 , max : 8 } ) ,
328- ( data , window ) => {
329- const lo = vals ( rollingQuantile ( s ( data ) , 0 , window , { minPeriods : 1 } ) ) ;
330- const med = vals ( rollingQuantile ( s ( data ) , 0.5 , window , { minPeriods : 1 } ) ) ;
331- const hi = vals ( rollingQuantile ( s ( data ) , 1 , window , { minPeriods : 1 } ) ) ;
332- for ( let i = 0 ; i < data . length ; i ++ ) {
333- const l = lo [ i ] ;
334- const m = med [ i ] ;
335- const h = hi [ i ] ;
336- if ( l === null || m === null || h === null ) {
337- continue ;
338- }
339- if ( ( l as number ) > ( m as number ) + 1e-9 ) {
340- return false ;
341- }
342- if ( ( m as number ) > ( h as number ) + 1e-9 ) {
343- return false ;
344- }
345- }
346- return true ;
347- } ,
350+ ( data , window ) =>
351+ quantilesOrdered (
352+ vals ( rollingQuantile ( s ( data ) , 0 , window , { minPeriods : 1 } ) ) ,
353+ vals ( rollingQuantile ( s ( data ) , 0.5 , window , { minPeriods : 1 } ) ) ,
354+ vals ( rollingQuantile ( s ( data ) , 1 , window , { minPeriods : 1 } ) ) ,
355+ ) ,
348356 ) ,
349357 ) ;
350358 } ) ;
0 commit comments