sketch_tdigest_v2_test.nx source
↩ module page · 123 lines · 4297 B
1// sketch_tdigest_v2_test.nx -- V2 (Dunning k1 arcsin scale) verification.
2
3import "syscalls.nx"
4import "sketch_tdigest_v2.nx"
5import "sketch_types.nx"
6
7func iabs(x: i64) -> i64 {
8 if x < 0 { return -x }
9 return x
10}
11
12func main() -> i64 {
13 // ---- alloc rejection ----
14 let bad_small: *TDigestV2 = nx_tdv2_alloc(5)
15 if bad_small != (0 as *TDigestV2) { return __syscall(93, 1, 0, 0, 0, 0, 0) }
16 let bad_huge: *TDigestV2 = nx_tdv2_alloc(999)
17 if bad_huge != (0 as *TDigestV2) { return __syscall(93, 2, 0, 0, 0, 0, 0) }
18
19 // ---- trig constants for delta=100 ----
20 // Taylor in Q14: x = 102944/100 = 1029
21 // x² = 1029·1029/16384 = 64
22 // x³ = 64·1029/16384 = 4
23 // x⁴ = 64·64/16384 = 0
24 // sin = 1029 - 4/6 = 1029
25 // cos = 16384 - 32 + 0 = 16352 → one_minus_cos = 32
26 let td: *TDigestV2 = nx_tdv2_alloc(100)
27 if td == (0 as *TDigestV2) { return __syscall(93, 3, 0, 0, 0, 0, 0) }
28 if nx_tdv2_sin_q14(td) != 1029 {
29 return __syscall(93, 4, 0, 0, 0, 0, 0)
30 }
31 if nx_tdv2_one_minus_cos_q14(td) != 32 {
32 return __syscall(93, 5, 0, 0, 0, 0, 0)
33 }
34
35 // ---- empty + single-value ----
36 if nx_tdv2_quantile(td, 500) != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
37 nx_tdv2_add(td, 42)
38 if nx_tdv2_quantile(td, 500) != 42 {
39 return __syscall(93, 11, 0, 0, 0, 0, 0)
40 }
41
42 // ---- stream 1..2000 / delta=100 ----
43 let td2: *TDigestV2 = nx_tdv2_alloc(100)
44 var i: i64 = 1
45 while i <= 2000 {
46 nx_tdv2_add(td2, i)
47 i = i + 1
48 }
49 if td2.total_weight != 2000 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
50 // Median ~ 1000. V2 global rank-err 1.5% at delta=100 → abs
51 // err <= 30, allow +- 40 margin.
52 let median: i64 = nx_tdv2_quantile(td2, 500)
53 if iabs(median - 1000) > 40 {
54 return __syscall(93, 21, 0, 0, 0, 0, 0)
55 }
56 // p10 = 200, p90 = 1800
57 let p10: i64 = nx_tdv2_quantile(td2, 100)
58 if iabs(p10 - 200) > 40 {
59 return __syscall(93, 22, 0, 0, 0, 0, 0)
60 }
61 let p90: i64 = nx_tdv2_quantile(td2, 900)
62 if iabs(p90 - 1800) > 40 {
63 return __syscall(93, 23, 0, 0, 0, 0, 0)
64 }
65 // p99 = 1980; tail bound looser for V2; tolerate +- 80.
66 let p99: i64 = nx_tdv2_quantile(td2, 990)
67 if iabs(p99 - 1980) > 80 {
68 return __syscall(93, 24, 0, 0, 0, 0, 0)
69 }
70
71 // ---- memory-efficiency claim: V2 < 200 centroids at delta=100 ----
72 // (V1 typically uses 200-400 centroids on same stream; V2's
73 // looser tail capacity allows fewer centroids overall.)
74 let n_cent: i64 = nx_tdv2_n_centroids(td2)
75 if n_cent < 1 { return __syscall(93, 30, 0, 0, 0, 0, 0) }
76 if n_cent > 200 { return __syscall(93, 31, 0, 0, 0, 0, 0) }
77
78 // ---- typed envelope ----
79 let q: *ApproxI64 = nx_tdv2_query(td2, 500)
80 if q.envelope_kind != NX_ENV_RANK_ERROR {
81 return __syscall(93, 40, 0, 0, 0, 0, 0)
82 }
83 if q.conf_ppb != 950000000 {
84 return __syscall(93, 41, 0, 0, 0, 0, 0)
85 }
86 if q.maturity != NX_MATURITY_REFERENCE_IMPL {
87 return __syscall(93, 42, 0, 0, 0, 0, 0)
88 }
89 if q.adv_safety != NX_ADV_HONEST {
90 return __syscall(93, 43, 0, 0, 0, 0, 0)
91 }
92 // delta=100 → 1.5% rank err → 15_000_000 ppb
93 if q.param_a != 15000000 {
94 return __syscall(93, 44, 0, 0, 0, 0, 0)
95 }
96
97 // ---- delta=50 trig sanity ----
98 // x = 102944/50 = 2058. x² = 2058·2058/16384 = 258.
99 // x³ = 258·2058/16384 = 32. x⁴ = 258·258/16384 = 4.
100 // sin = 2058 - 32/6 = 2058 - 5 = 2053
101 // cos = 16384 - 129 + 0 = 16255 → one_minus_cos = 129
102 let td50: *TDigestV2 = nx_tdv2_alloc(50)
103 if nx_tdv2_sin_q14(td50) != 2053 {
104 return __syscall(93, 50, 0, 0, 0, 0, 0)
105 }
106 if nx_tdv2_one_minus_cos_q14(td50) != 129 {
107 return __syscall(93, 51, 0, 0, 0, 0, 0)
108 }
109
110 // ---- delta=200 trig sanity ----
111 // x = 102944/200 = 514. x² = 514·514/16384 = 16.
112 // x³ = 16·514/16384 = 0. x⁴ ~ 0.
113 // sin = 514. cos = 16384 - 8 = 16376. one_minus_cos = 8.
114 let td200: *TDigestV2 = nx_tdv2_alloc(200)
115 if nx_tdv2_sin_q14(td200) != 514 {
116 return __syscall(93, 60, 0, 0, 0, 0, 0)
117 }
118 if nx_tdv2_one_minus_cos_q14(td200) != 8 {
119 return __syscall(93, 61, 0, 0, 0, 0, 0)
120 }
121
122 return 0
123}