poulpy/poulpy-hal/src/reference/vec_znx/shift.rs
poulpy-hal/src/reference/vec_znx/shift.rsRaw
1use std::hint::black_box;
2
3use criterion::{BenchmarkId, Criterion};
4
5use crate::{
6 api::{ModuleNew, ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxLsh, VecZnxLshInplace, VecZnxRsh, VecZnxRshInplace},
7 layouts::{Backend, FillUniform, Module, ScratchOwned, VecZnx, VecZnxToMut, VecZnxToRef, ZnxInfos, ZnxView, ZnxViewMut},
8 reference::znx::{
9 ZnxCopy, ZnxNormalizeFinalStep, ZnxNormalizeFinalStepInplace, ZnxNormalizeFirstStep, ZnxNormalizeFirstStepCarryOnly,
10 ZnxNormalizeFirstStepInplace, ZnxNormalizeMiddleStep, ZnxNormalizeMiddleStepCarryOnly, ZnxNormalizeMiddleStepInplace,
11 ZnxZero,
12 },
13 source::Source,
14};
15
16pub fn vec_znx_lsh_tmp_bytes(n: usize) -> usize {
17 n * size_of::<i64>()
18}
19
20pub fn vec_znx_lsh_inplace<R, ZNXARI>(base2k: usize, k: usize, res: &mut R, res_col: usize, carry: &mut [i64])
21where
22 R: VecZnxToMut,
23 ZNXARI: ZnxZero
24 + ZnxCopy
25 + ZnxNormalizeFirstStepInplace
26 + ZnxNormalizeMiddleStepInplace
27 + ZnxNormalizeFirstStepInplace
28 + ZnxNormalizeFinalStepInplace,
29{
30 let mut res: VecZnx<&mut [u8]> = res.to_mut();
31
32 let n: usize = res.n();
33 let cols: usize = res.cols();
34 let size: usize = res.size();
35 let steps: usize = k / base2k;
36 let k_rem: usize = k % base2k;
37
38 if steps >= size {
39 for j in 0..size {
40 ZNXARI::znx_zero(res.at_mut(res_col, j));
41 }
42 return;
43 }
44
45 // Inplace shift of limbs by a k/base2k
46 if steps > 0 {
47 let start: usize = n * res_col;
48 let end: usize = start + n;
49 let slice_size: usize = n * cols;
50 let res_raw: &mut [i64] = res.raw_mut();
51
52 (0..size - steps).for_each(|j| {
53 let (lhs, rhs) = res_raw.split_at_mut(slice_size * (j + steps));
54 ZNXARI::znx_copy(&mut lhs[start + j * slice_size..end + j * slice_size], &rhs[start..end]);
55 });
56
57 for j in size - steps..size {
58 ZNXARI::znx_zero(res.at_mut(res_col, j));
59 }
60 }
61
62 for j in (0..size - steps).rev() {
63 if j == size - steps - 1 {
64 ZNXARI::znx_normalize_first_step_inplace(base2k, k_rem, res.at_mut(res_col, j), carry);
65 } else if j == 0 {
66 ZNXARI::znx_normalize_final_step_inplace(base2k, k_rem, res.at_mut(res_col, j), carry);
67 } else {
68 ZNXARI::znx_normalize_middle_step_inplace(base2k, k_rem, res.at_mut(res_col, j), carry);
69 }
70 }
71}
72
73pub fn vec_znx_lsh<R, A, ZNXARI>(base2k: usize, k: usize, res: &mut R, res_col: usize, a: &A, a_col: usize, carry: &mut [i64])
74where
75 R: VecZnxToMut,
76 A: VecZnxToRef,
77 ZNXARI: ZnxZero + ZnxNormalizeFirstStep + ZnxNormalizeMiddleStep + ZnxNormalizeFirstStep + ZnxCopy + ZnxNormalizeFinalStep,
78{
79 let mut res: VecZnx<&mut [u8]> = res.to_mut();
80 let a: VecZnx<&[u8]> = a.to_ref();
81
82 let res_size: usize = res.size();
83 let a_size = a.size();
84 let steps: usize = k / base2k;
85 let k_rem: usize = k % base2k;
86
87 if steps >= res_size.min(a_size) {
88 for j in 0..res_size {
89 ZNXARI::znx_zero(res.at_mut(res_col, j));
90 }
91 return;
92 }
93
94 let min_size: usize = a_size.min(res_size) - steps;
95
96 // Simply a left shifted normalization of limbs
97 // by k/base2k and intra-limb by base2k - k%base2k
98 for j in (0..min_size).rev() {
99 if j == min_size - 1 {
100 ZNXARI::znx_normalize_first_step(base2k, k_rem, res.at_mut(res_col, j), a.at(a_col, j + steps), carry);
101 } else if j == 0 {
102 ZNXARI::znx_normalize_final_step(base2k, k_rem, res.at_mut(res_col, j), a.at(a_col, j + steps), carry);
103 } else {
104 ZNXARI::znx_normalize_middle_step(base2k, k_rem, res.at_mut(res_col, j), a.at(a_col, j + steps), carry);
105 }
106 }
107
108 // Zeroes bottom
109 for j in min_size..res_size {
110 ZNXARI::znx_zero(res.at_mut(res_col, j));
111 }
112}
113
114pub fn vec_znx_rsh_tmp_bytes(n: usize) -> usize {
115 2 * n * size_of::<i64>()
116}
117
118pub fn vec_znx_rsh_inplace<R, ZNXARI>(base2k: usize, k: usize, res: &mut R, res_col: usize, tmp: &mut [i64])
119where
120 R: VecZnxToMut,
121 ZNXARI: ZnxZero
122 + ZnxCopy
123 + ZnxNormalizeFirstStepCarryOnly
124 + ZnxNormalizeMiddleStepCarryOnly
125 + ZnxNormalizeMiddleStep
126 + ZnxNormalizeMiddleStepInplace
127 + ZnxNormalizeFirstStepInplace
128 + ZnxNormalizeFinalStepInplace,
129{
130 let mut res: VecZnx<&mut [u8]> = res.to_mut();
131 let n: usize = res.n();
132
133 let size: usize = res.size();
134
135 let mut steps: usize = k / base2k;
136 let k_rem: usize = k % base2k;
137
138 if !k.is_multiple_of(base2k) {
139 // We rsh by an additional base2k and then lsh by base2k-k
140 // Allows to re-use efficient normalization code, avoids
141 // avoids overflows & produce output that is normalized
142 steps += 1;
143 }
144
145 let (carry, tmp) = tmp[..2 * n].split_at_mut(n);
146
147 let lsh: usize = (base2k - k_rem) % base2k;
148
149 // All limbs of a that would fall outside of the limbs of res are discarded,
150 // but the carry still need to be computed.
151 for j in 0..steps {
152 if j == 0 {
153 ZNXARI::znx_normalize_first_step_carry_only(base2k, lsh, res.at(res_col, size - j - 1), carry);
154 } else {
155 ZNXARI::znx_normalize_middle_step_carry_only(base2k, lsh, res.at(res_col, size - j - 1), carry);
156 }
157 }
158
159 // Continues with shifted normalization
160 for j in 0..size - steps {
161 ZNXARI::znx_copy(tmp, res.at(res_col, size - steps - j - 1));
162 ZNXARI::znx_normalize_middle_step_inplace(base2k, lsh, tmp, carry);
163 ZNXARI::znx_copy(res.at_mut(res_col, size - j - 1), tmp);
164 }
165
166 // Propagates carry on the rest of the limbs of res
167 for j in 0..steps {
168 ZNXARI::znx_zero(res.at_mut(res_col, j));
169 if j == 0 {
170 ZNXARI::znx_normalize_final_step_inplace(base2k, lsh, res.at_mut(res_col, steps - j - 1), carry);
171 } else {
172 ZNXARI::znx_normalize_middle_step_inplace(base2k, lsh, res.at_mut(res_col, steps - j - 1), carry);
173 }
174 }
175}
176
177pub fn vec_znx_rsh<R, A, ZNXARI>(base2k: usize, k: usize, res: &mut R, res_col: usize, a: &A, a_col: usize, carry: &mut [i64])
178where
179 R: VecZnxToMut,
180 A: VecZnxToRef,
181 ZNXARI: ZnxZero
182 + ZnxCopy
183 + ZnxNormalizeFirstStepCarryOnly
184 + ZnxNormalizeMiddleStepCarryOnly
185 + ZnxNormalizeFirstStep
186 + ZnxNormalizeMiddleStep
187 + ZnxNormalizeMiddleStepInplace
188 + ZnxNormalizeFirstStepInplace
189 + ZnxNormalizeFinalStepInplace,
190{
191 let mut res: VecZnx<&mut [u8]> = res.to_mut();
192 let a: VecZnx<&[u8]> = a.to_ref();
193
194 let res_size: usize = res.size();
195 let a_size: usize = a.size();
196
197 let mut steps: usize = k / base2k;
198 let k_rem: usize = k % base2k;
199
200 if !k.is_multiple_of(base2k) {
201 // We rsh by an additional base2k and then lsh by base2k-k
202 // Allows to re-use efficient normalization code, avoids
203 // avoids overflows & produce output that is normalized
204 steps += 1;
205 }
206
207 let lsh: usize = (base2k - k_rem) % base2k; // 0 if k | base2k
208 let res_end: usize = res_size.min(steps);
209 let res_start: usize = res_size.min(a_size + steps);
210 let a_start: usize = a_size.min(res_size.saturating_sub(steps));
211
212 // All limbs of a that are moved outside of the limbs of res are discarded,
213 // but the carry still need to be computed.
214 let a_out_range: usize = a_size.saturating_sub(a_start);
215
216 for j in 0..a_out_range {
217 if j == 0 {
218 ZNXARI::znx_normalize_first_step_carry_only(base2k, lsh, a.at(a_col, a_size - j - 1), carry);
219 } else {
220 ZNXARI::znx_normalize_middle_step_carry_only(base2k, lsh, a.at(a_col, a_size - j - 1), carry);
221 }
222 }
223
224 if a_out_range == 0 {
225 ZNXARI::znx_zero(carry);
226 }
227
228 // Zeroes lower limbs of res if a_size + steps < res_size
229 for j in 0..res_size {
230 ZNXARI::znx_zero(res.at_mut(res_col, j));
231 }
232
233 // Continues with shifted normalization
234 let mid_range: usize = res_start.saturating_sub(res_end);
235
236 for j in 0..mid_range {
237 ZNXARI::znx_normalize_middle_step(
238 base2k,
239 lsh,
240 res.at_mut(res_col, res_start - j - 1),
241 a.at(a_col, a_start - j - 1),
242 carry,
243 );
244 }
245
246 // Propagates carry on the rest of the limbs of res
247 for j in 0..res_end {
248 if j == res_end - 1 {
249 ZNXARI::znx_normalize_final_step_inplace(base2k, lsh, res.at_mut(res_col, res_end - j - 1), carry);
250 } else {
251 ZNXARI::znx_normalize_middle_step_inplace(base2k, lsh, res.at_mut(res_col, res_end - j - 1), carry);
252 }
253 }
254}
255
256pub fn bench_vec_znx_lsh_inplace<B: Backend>(c: &mut Criterion, label: &str)
257where
258 Module<B>: ModuleNew<B> + VecZnxLshInplace<B>,
259 ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
260{
261 let group_name: String = format!("vec_znx_lsh_inplace::{label}");
262
263 let mut group = c.benchmark_group(group_name);
264
265 fn runner<B: Backend>(params: [usize; 3]) -> impl FnMut()
266 where
267 Module<B>: VecZnxLshInplace<B> + ModuleNew<B>,
268 ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
269 {
270 let n: usize = 1 << params[0];
271 let cols: usize = params[1];
272 let size: usize = params[2];
273
274 let module: Module<B> = Module::<B>::new(n as u64);
275
276 let base2k: usize = 50;
277
278 let mut source: Source = Source::new([0u8; 32]);
279
280 let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
281 let mut b: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
282
283 let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(vec_znx_lsh_tmp_bytes(n));
284
285 // Fill a with random i64
286 a.fill_uniform(50, &mut source);
287 b.fill_uniform(50, &mut source);
288
289 move || {
290 for i in 0..cols {
291 module.vec_znx_lsh_inplace(base2k, base2k - 1, &mut b, i, scratch.borrow());
292 }
293 black_box(());
294 }
295 }
296
297 for params in [[10, 2, 2], [11, 2, 4], [12, 2, 8], [13, 2, 16], [14, 2, 32]] {
298 let id: BenchmarkId = BenchmarkId::from_parameter(format!("{}x({}x{})", 1 << params[0], params[1], params[2]));
299 let mut runner = runner::<B>(params);
300 group.bench_with_input(id, &(), |b, _| b.iter(&mut runner));
301 }
302
303 group.finish();
304}
305
306pub fn bench_vec_znx_lsh<B: Backend>(c: &mut Criterion, label: &str)
307where
308 Module<B>: VecZnxLsh<B> + ModuleNew<B>,
309 ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
310{
311 let group_name: String = format!("vec_znx_lsh::{label}");
312
313 let mut group = c.benchmark_group(group_name);
314
315 fn runner<B: Backend>(params: [usize; 3]) -> impl FnMut()
316 where
317 Module<B>: VecZnxLsh<B> + ModuleNew<B>,
318 ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
319 {
320 let n: usize = 1 << params[0];
321 let cols: usize = params[1];
322 let size: usize = params[2];
323
324 let module: Module<B> = Module::<B>::new(n as u64);
325
326 let base2k: usize = 50;
327
328 let mut source: Source = Source::new([0u8; 32]);
329
330 let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
331 let mut res: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
332
333 let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(vec_znx_lsh_tmp_bytes(n));
334
335 // Fill a with random i64
336 a.fill_uniform(50, &mut source);
337 res.fill_uniform(50, &mut source);
338
339 move || {
340 for i in 0..cols {
341 module.vec_znx_lsh(base2k, base2k - 1, &mut res, i, &a, i, scratch.borrow());
342 }
343 black_box(());
344 }
345 }
346
347 for params in [[10, 2, 2], [11, 2, 4], [12, 2, 8], [13, 2, 16], [14, 2, 32]] {
348 let id: BenchmarkId = BenchmarkId::from_parameter(format!("{}x({}x{})", 1 << params[0], params[1], params[2]));
349 let mut runner = runner::<B>(params);
350 group.bench_with_input(id, &(), |b, _| b.iter(&mut runner));
351 }
352
353 group.finish();
354}
355
356pub fn bench_vec_znx_rsh_inplace<B: Backend>(c: &mut Criterion, label: &str)
357where
358 Module<B>: VecZnxRshInplace<B> + ModuleNew<B>,
359 ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
360{
361 let group_name: String = format!("vec_znx_rsh_inplace::{label}");
362
363 let mut group = c.benchmark_group(group_name);
364
365 fn runner<B: Backend>(params: [usize; 3]) -> impl FnMut()
366 where
367 Module<B>: VecZnxRshInplace<B> + ModuleNew<B>,
368 ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
369 {
370 let n: usize = 1 << params[0];
371 let cols: usize = params[1];
372 let size: usize = params[2];
373
374 let module: Module<B> = Module::<B>::new(n as u64);
375
376 let base2k: usize = 50;
377
378 let mut source: Source = Source::new([0u8; 32]);
379
380 let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
381 let mut b: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
382
383 let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(vec_znx_rsh_tmp_bytes(n));
384
385 // Fill a with random i64
386 a.fill_uniform(50, &mut source);
387 b.fill_uniform(50, &mut source);
388
389 move || {
390 for i in 0..cols {
391 module.vec_znx_rsh_inplace(base2k, base2k - 1, &mut b, i, scratch.borrow());
392 }
393 black_box(());
394 }
395 }
396
397 for params in [[10, 2, 2], [11, 2, 4], [12, 2, 8], [13, 2, 16], [14, 2, 32]] {
398 let id: BenchmarkId = BenchmarkId::from_parameter(format!("{}x({}x{})", 1 << params[0], params[1], params[2]));
399 let mut runner = runner::<B>(params);
400 group.bench_with_input(id, &(), |b, _| b.iter(&mut runner));
401 }
402
403 group.finish();
404}
405
406pub fn bench_vec_znx_rsh<B: Backend>(c: &mut Criterion, label: &str)
407where
408 Module<B>: VecZnxRsh<B> + ModuleNew<B>,
409 ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
410{
411 let group_name: String = format!("vec_znx_rsh::{label}");
412
413 let mut group = c.benchmark_group(group_name);
414
415 fn runner<B: Backend>(params: [usize; 3]) -> impl FnMut()
416 where
417 Module<B>: VecZnxRsh<B> + ModuleNew<B>,
418 ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
419 {
420 let n: usize = 1 << params[0];
421 let cols: usize = params[1];
422 let size: usize = params[2];
423
424 let module: Module<B> = Module::<B>::new(n as u64);
425
426 let base2k: usize = 50;
427
428 let mut source: Source = Source::new([0u8; 32]);
429
430 let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
431 let mut res: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
432
433 let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(vec_znx_rsh_tmp_bytes(n));
434
435 // Fill a with random i64
436 a.fill_uniform(50, &mut source);
437 res.fill_uniform(50, &mut source);
438
439 move || {
440 for i in 0..cols {
441 module.vec_znx_rsh(base2k, base2k - 1, &mut res, i, &a, i, scratch.borrow());
442 }
443 black_box(());
444 }
445 }
446
447 for params in [[10, 2, 2], [11, 2, 4], [12, 2, 8], [13, 2, 16], [14, 2, 32]] {
448 let id: BenchmarkId = BenchmarkId::from_parameter(format!("{}x({}x{})", 1 << params[0], params[1], params[2]));
449 let mut runner = runner::<B>(params);
450 group.bench_with_input(id, &(), |b, _| b.iter(&mut runner));
451 }
452
453 group.finish();
454}
455
456#[cfg(test)]
457mod tests {
458 use crate::{
459 layouts::{FillUniform, VecZnx, ZnxView},
460 reference::{
461 vec_znx::{
462 vec_znx_copy, vec_znx_lsh, vec_znx_lsh_inplace, vec_znx_lsh_tmp_bytes, vec_znx_normalize_inplace, vec_znx_rsh,
463 vec_znx_rsh_inplace, vec_znx_rsh_tmp_bytes, vec_znx_sub_inplace,
464 },
465 znx::ZnxRef,
466 },
467 source::Source,
468 };
469
470 #[test]
471 fn test_vec_znx_lsh() {
472 let n: usize = 8;
473 let cols: usize = 2;
474 let size: usize = 7;
475
476 let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
477 let mut res_ref: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
478 let mut res_test: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
479
480 let mut source: Source = Source::new([0u8; 32]);
481
482 let mut carry: Vec<i64> = vec![0i64; vec_znx_lsh_tmp_bytes(n) / size_of::<i64>()];
483
484 let base2k: usize = 50;
485
486 for k in 0..256 {
487 a.fill_uniform(50, &mut source);
488
489 for i in 0..cols {
490 vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut a, i, &mut carry);
491 vec_znx_copy::<_, _, ZnxRef>(&mut res_ref, i, &a, i);
492 }
493
494 for i in 0..cols {
495 vec_znx_lsh_inplace::<_, ZnxRef>(base2k, k, &mut res_ref, i, &mut carry);
496 vec_znx_lsh::<_, _, ZnxRef>(base2k, k, &mut res_test, i, &a, i, &mut carry);
497 vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut res_test, i, &mut carry);
498 }
499
500 assert_eq!(res_ref, res_test);
501 }
502 }
503
504 #[test]
505 fn test_vec_znx_rsh() {
506 let n: usize = 8;
507 let cols: usize = 2;
508
509 let res_size: usize = 7;
510
511 let mut res_ref: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, res_size);
512 let mut res_test: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, res_size);
513
514 let mut carry: Vec<i64> = vec![0i64; vec_znx_rsh_tmp_bytes(n) / size_of::<i64>()];
515
516 let base2k: usize = 50;
517
518 let mut source: Source = Source::new([0u8; 32]);
519
520 let zero: Vec<i64> = vec![0i64; n];
521
522 for a_size in [res_size - 1, res_size, res_size + 1] {
523 let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, a_size);
524
525 for k in 0..res_size * base2k {
526 a.fill_uniform(50, &mut source);
527
528 for i in 0..cols {
529 vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut a, i, &mut carry);
530 vec_znx_copy::<_, _, ZnxRef>(&mut res_ref, i, &a, i);
531 }
532
533 res_test.fill_uniform(50, &mut source);
534
535 for j in 0..cols {
536 vec_znx_rsh_inplace::<_, ZnxRef>(base2k, k, &mut res_ref, j, &mut carry);
537 vec_znx_rsh::<_, _, ZnxRef>(base2k, k, &mut res_test, j, &a, j, &mut carry);
538 }
539
540 for j in 0..cols {
541 vec_znx_lsh_inplace::<_, ZnxRef>(base2k, k, &mut res_ref, j, &mut carry);
542 vec_znx_lsh_inplace::<_, ZnxRef>(base2k, k, &mut res_test, j, &mut carry);
543 }
544
545 // Case where res has enough to fully store a right shifted without any loss
546 // In this case we can check exact equality.
547 if a_size + k.div_ceil(base2k) <= res_size {
548 assert_eq!(res_ref, res_test);
549
550 for i in 0..cols {
551 for j in 0..a_size {
552 assert_eq!(res_ref.at(i, j), a.at(i, j), "r0 {} {}", i, j);
553 assert_eq!(res_test.at(i, j), a.at(i, j), "r1 {} {}", i, j);
554 }
555
556 for j in a_size..res_size {
557 assert_eq!(res_ref.at(i, j), zero, "r0 {} {}", i, j);
558 assert_eq!(res_test.at(i, j), zero, "r1 {} {}", i, j);
559 }
560 }
561 // Some loss occures, either because a initially has more precision than res
562 // or because the storage of the right shift of a requires more precision than
563 // res.
564 } else {
565 for j in 0..cols {
566 vec_znx_sub_inplace::<_, _, ZnxRef>(&mut res_ref, j, &a, j);
567 vec_znx_sub_inplace::<_, _, ZnxRef>(&mut res_test, j, &a, j);
568
569 vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut res_ref, j, &mut carry);
570 vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut res_test, j, &mut carry);
571
572 assert!(res_ref.stats(base2k, j).std().log2() - (k as f64) <= (k * base2k) as f64);
573 assert!(res_test.stats(base2k, j).std().log2() - (k as f64) <= (k * base2k) as f64);
574 }
575 }
576 }
577 }
578 }
579}