@@ -6,6 +6,8 @@ use crate::util::Truncate;
6
6
pub trait CompressionFactor : EncodingSize {
7
7
const POW2_HALF : u32 ;
8
8
const MASK : Integer ;
9
+ const DIV_SHIFT : usize ;
10
+ const DIV_MUL : u64 ;
9
11
}
10
12
11
13
impl < T > CompressionFactor for T
14
16
{
15
17
const POW2_HALF : u32 = 1 << ( T :: USIZE - 1 ) ;
16
18
const MASK : Integer = ( ( 1 as Integer ) << T :: USIZE ) - 1 ;
19
+ const DIV_SHIFT : usize = 34 ;
20
+ #[ allow( clippy:: integer_division_remainder_used) ]
21
+ const DIV_MUL : u64 = ( 1 << T :: DIV_SHIFT ) / FieldElement :: Q64 ;
17
22
}
18
23
19
24
// Traits for objects that allow compression / decompression
@@ -25,25 +30,26 @@ pub trait Compress {
25
30
impl Compress for FieldElement {
26
31
// Equation 4.5: Compress_d(x) = round((2^d / q) x)
27
32
//
28
- // Here and in decompression, we leverage the following fact :
33
+ // Here and in decompression, we leverage the following facts :
29
34
//
30
35
// round(a / b) = floor((a + b/2) / b)
36
+ // a / q ~= (a * x) >> s where x >> s ~= 1/q
31
37
fn compress < D : CompressionFactor > ( & mut self ) -> & Self {
32
- const Q_HALF : u32 = ( FieldElement :: Q32 - 1 ) / 2 ;
33
- let x = u32 :: from ( self . 0 ) ;
34
- let y = ( ( x << D :: USIZE ) + Q_HALF ) / FieldElement :: Q32 ;
38
+ const Q_HALF : u64 = ( FieldElement :: Q64 + 1 ) >> 1 ;
39
+ let x = u64 :: from ( self . 0 ) ;
40
+ let y = ( ( ( ( x << D :: USIZE ) + Q_HALF ) * D :: DIV_MUL ) >> D :: DIV_SHIFT ) . truncate ( ) ;
35
41
self . 0 = y. truncate ( ) & D :: MASK ;
36
42
self
37
43
}
38
- // Equation 4.6: Decomporess_d(x) = round((q / 2^d) x)
44
+
45
+ // Equation 4.6: Decompress_d(x) = round((q / 2^d) x)
39
46
fn decompress < D : CompressionFactor > ( & mut self ) -> & Self {
40
47
let x = u32:: from ( self . 0 ) ;
41
48
let y = ( ( x * FieldElement :: Q32 ) + D :: POW2_HALF ) >> D :: USIZE ;
42
49
self . 0 = y. truncate ( ) ;
43
50
self
44
51
}
45
52
}
46
-
47
53
impl Compress for Polynomial {
48
54
fn compress < D : CompressionFactor > ( & mut self ) -> & Self {
49
55
for x in & mut self . 0 {
@@ -84,37 +90,100 @@ impl<K: ArraySize> Compress for PolynomialVector<K> {
84
90
pub ( crate ) mod test {
85
91
use super :: * ;
86
92
use hybrid_array:: typenum:: { U1 , U10 , U11 , U12 , U4 , U5 , U6 } ;
93
+ use num_rational:: Ratio ;
94
+
95
+ fn rational_compress < D : CompressionFactor > ( input : u16 ) -> u16 {
96
+ let fraction = Ratio :: new ( u32:: from ( input) * ( 1 << D :: USIZE ) , FieldElement :: Q32 ) ;
97
+ ( fraction. round ( ) . to_integer ( ) as u16 ) & D :: MASK
98
+ }
99
+
100
+ fn rational_decompress < D : CompressionFactor > ( input : u16 ) -> u16 {
101
+ let fraction = Ratio :: new ( u32:: from ( input) * FieldElement :: Q32 , 1 << D :: USIZE ) ;
102
+ fraction. round ( ) . to_integer ( ) as u16
103
+ }
87
104
88
- // Verify that the integer compression routine produces the same results as rounding with
89
- // floats.
90
- fn compression_known_answer_test < D : CompressionFactor > ( ) {
91
- let fq : f64 = FieldElement :: Q as f64 ;
92
- let f2d : f64 = 2.0_f64 . powi ( D :: I32 ) ;
105
+ // Verify against inequality 4.7
106
+ # [ allow ( clippy :: integer_division_remainder_used ) ]
107
+ fn compression_decompression_inequality < D : CompressionFactor > ( ) {
108
+ const QI32 : i32 = FieldElement :: Q as i32 ;
109
+ let error_threshold = Ratio :: new ( FieldElement :: Q , 1 << D :: USIZE ) . to_integer ( ) as i32 ;
93
110
94
111
for x in 0 ..FieldElement :: Q {
95
- let fx = x as f64 ;
96
- let mut x = FieldElement ( x) ;
112
+ let mut y = FieldElement ( x) ;
113
+ y. compress :: < D > ( ) ;
114
+ y. decompress :: < D > ( ) ;
115
+
116
+ let mut error = i32:: from ( y. 0 ) - i32:: from ( x) + QI32 ;
117
+ if error > ( QI32 - 1 ) / 2 {
118
+ error -= QI32 ;
119
+ }
120
+
121
+ assert ! (
122
+ error. abs( ) <= error_threshold,
123
+ "Inequality failed for x = {x}: error = {}, error_threshold = {error_threshold}, D = {:?}" ,
124
+ error. abs( ) ,
125
+ D :: USIZE
126
+ ) ;
127
+ }
128
+ }
97
129
98
- // Verify equivalence of compression
99
- x. compress :: < D > ( ) ;
100
- let fcx = ( ( f2d / fq * fx) . round ( ) as Integer ) % ( 1 << D :: USIZE ) ;
101
- assert_eq ! ( x. 0 , fcx) ;
130
+ fn decompression_compression_equality < D : CompressionFactor > ( ) {
131
+ for x in 0 ..( 1 << D :: USIZE ) {
132
+ let mut y = FieldElement ( x) ;
133
+ y. decompress :: < D > ( ) ;
134
+ y. compress :: < D > ( ) ;
102
135
103
- // Verify equivalence of decompression
104
- x. decompress :: < D > ( ) ;
105
- let fdx = ( fq / f2d * ( fcx as f64 ) ) . round ( ) as Integer ;
106
- assert_eq ! ( x. 0 , fdx) ;
136
+ assert_eq ! ( y. 0 , x, "failed for x: {}, D: {}" , x, D :: USIZE ) ;
137
+ }
138
+ }
139
+
140
+ fn decompress_KAT < D : CompressionFactor > ( ) {
141
+ for y in 0 ..( 1 << D :: USIZE ) {
142
+ let x_expected = rational_decompress :: < D > ( y) ;
143
+ let mut x_actual = FieldElement ( y) ;
144
+ x_actual. decompress :: < D > ( ) ;
145
+
146
+ assert_eq ! ( x_expected, x_actual. 0 ) ;
147
+ }
148
+ }
149
+
150
+ fn compress_KAT < D : CompressionFactor > ( ) {
151
+ for x in 0 ..FieldElement :: Q {
152
+ let y_expected = rational_compress :: < D > ( x) ;
153
+ let mut y_actual = FieldElement ( x) ;
154
+ y_actual. compress :: < D > ( ) ;
155
+
156
+ assert_eq ! ( y_expected, y_actual. 0 , "for x: {}, D: {}" , x, D :: USIZE ) ;
107
157
}
108
158
}
109
159
160
+ fn compress_decompress_properties < D : CompressionFactor > ( ) {
161
+ compression_decompression_inequality :: < D > ( ) ;
162
+ decompression_compression_equality :: < D > ( ) ;
163
+ }
164
+
165
+ fn compress_decompress_KATs < D : CompressionFactor > ( ) {
166
+ decompress_KAT :: < D > ( ) ;
167
+ compress_KAT :: < D > ( ) ;
168
+ }
169
+
110
170
#[ test]
111
- fn compress_decompress ( ) {
112
- compression_known_answer_test :: < U1 > ( ) ;
113
- compression_known_answer_test :: < U4 > ( ) ;
114
- compression_known_answer_test :: < U5 > ( ) ;
115
- compression_known_answer_test :: < U6 > ( ) ;
116
- compression_known_answer_test :: < U10 > ( ) ;
117
- compression_known_answer_test :: < U11 > ( ) ;
118
- compression_known_answer_test :: < U12 > ( ) ;
171
+ fn decompress_compress ( ) {
172
+ compress_decompress_properties :: < U1 > ( ) ;
173
+ compress_decompress_properties :: < U4 > ( ) ;
174
+ compress_decompress_properties :: < U5 > ( ) ;
175
+ compress_decompress_properties :: < U6 > ( ) ;
176
+ compress_decompress_properties :: < U10 > ( ) ;
177
+ compress_decompress_properties :: < U11 > ( ) ;
178
+ // preservation under decompression first only holds for d < 12
179
+ compression_decompression_inequality :: < U12 > ( ) ;
180
+
181
+ compress_decompress_KATs :: < U1 > ( ) ;
182
+ compress_decompress_KATs :: < U4 > ( ) ;
183
+ compress_decompress_KATs :: < U5 > ( ) ;
184
+ compress_decompress_KATs :: < U6 > ( ) ;
185
+ compress_decompress_KATs :: < U10 > ( ) ;
186
+ compress_decompress_KATs :: < U11 > ( ) ;
187
+ compress_decompress_KATs :: < U12 > ( ) ;
119
188
}
120
189
}
0 commit comments