ore_rs/convert.rs
1/*
2 This following function implement an order-preserving translation of 64 bit
3 floats to 64 bit doubles (and the reverse operation - although that is just
4 used for verifying correctness).
5
6 The 64 bit integer that is produced is a plaintext that will be ORE encrypted
7 later on.
8
9 The mapping is such that the ordering of the floats will be preserved when
10 mapped to an unsigned integer, for example, an array of unsigned integers
11 dervived from a sorted array of doubles will result in no change to its
12 ordering when it itself is sorted.
13
14 The mapping does not preserve any notion of the previous value after the
15 conversion - only ordering is preserved.
16
17 Caveat: NaN and -ve & +ve infinity will also be mapped and ordering is not
18 well-defined with those values. Those values should be discarded before
19 converting arrays of those values.
20
21 This post was used as a reference for building this implementation:
22 https://lemire.me/blog/2020/12/14/converting-floating-point-numbers-to-integers-while-preserving-order
23*/
24
25use core::mem;
26
27pub(crate) trait ToOrderedInteger<T> {
28 fn map_to(&self) -> T;
29}
30
31trait FromOrderedInteger<T> {
32 fn map_from(input: T) -> Self;
33}
34
35impl ToOrderedInteger<u64> for f64 {
36 fn map_to(&self) -> u64 {
37 let num: u64 = self.to_bits();
38 let signed: i64 = -(unsafe { mem::transmute(num >> 63) });
39 let mut mask: u64 = unsafe { mem::transmute(signed) };
40 mask |= 0x8000000000000000;
41 num ^ mask
42 }
43}
44
45impl FromOrderedInteger<u64> for f64 {
46 fn map_from(input: u64) -> f64 {
47 let i = (((input >> 63) as i64) - 1) as u64;
48 let mask: u64 = i | 0x8000000000000000;
49 f64::from_bits(input ^ mask)
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use quickcheck::TestResult;
57
58 quickcheck! {
59 fn roundtrip(x: f64) -> TestResult {
60 if !x.is_nan() && x.is_finite() {
61 TestResult::from_bool(x == f64::map_from(x.map_to()))
62 } else {
63 TestResult::discard()
64 }
65 }
66 }
67}