malachite_nz/integer/logic/checked_hamming_distance.rs
1// Copyright © 2025 Mikhail Hogrefe
2//
3// Uses code adopted from the GNU MP Library.
4//
5// Copyright © 1994, 1996, 2001, 2002, 2009-2011 Free Software Foundation, Inc.
6//
7// This file is part of Malachite.
8//
9// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
10// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
11// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
12
13use crate::integer::Integer;
14use crate::integer::logic::checked_count_zeros::limbs_count_zeros_neg;
15use crate::natural::InnerNatural::{Large, Small};
16use crate::natural::Natural;
17use crate::natural::logic::count_ones::limbs_count_ones;
18use crate::natural::logic::hamming_distance::limbs_hamming_distance_same_length;
19use crate::platform::Limb;
20use core::cmp::Ordering::*;
21use malachite_base::num::logic::traits::{
22 CheckedHammingDistance, CountOnes, CountZeros, HammingDistance,
23};
24use malachite_base::slices::slice_leading_zeros;
25
26// Interpreting a slice of `Limb`s as the limbs of a `Natural` in ascending order, returns the
27// Hamming distance between the negative of that `Natural` (two's complement) and the negative of a
28// `Limb`. Both have infinitely many implicit leading ones. `xs` cannot be empty or only contain
29// zeros; `y` cannot be zero.
30//
31// # Worst-case complexity
32// $T(n) = O(n)$
33//
34// $M(n) = O(1)$
35//
36// where $T$ is time, $M$ is additional memory, and $n$ is `xs.len()`.
37//
38// # Panics
39// Panics if `xs` is empty.
40pub_test! {limbs_hamming_distance_limb_neg(xs: &[Limb], y: Limb) -> u64 {
41 let x_lo = xs[0].wrapping_neg();
42 limbs_count_zeros_neg(xs) - CountZeros::count_zeros(x_lo)
43 + x_lo.hamming_distance(y.wrapping_neg())
44}}
45
46fn limbs_count_zeros(xs: &[Limb]) -> u64 {
47 xs.iter().map(|&limb| CountZeros::count_zeros(limb)).sum()
48}
49
50fn limbs_hamming_distance_neg_leading_limbs_helper(xs: &[Limb], ys: &[Limb], i: usize) -> u64 {
51 let xs_len = xs.len();
52 let ys_len = ys.len();
53 match xs_len.cmp(&ys_len) {
54 Equal => limbs_hamming_distance_same_length(&xs[i + 1..], &ys[i + 1..]),
55 Less => {
56 let (ys_lo, ys_hi) = ys.split_at(xs_len);
57 limbs_hamming_distance_same_length(&ys_lo[i + 1..], &xs[i + 1..])
58 + limbs_count_ones(ys_hi)
59 }
60 Greater => {
61 let (xs_lo, xs_hi) = xs.split_at(ys_len);
62 limbs_hamming_distance_same_length(&xs_lo[i + 1..], &ys[i + 1..])
63 + limbs_count_ones(xs_hi)
64 }
65 }
66}
67
68// ```
69// xs: nnnnnnnb000
70// ys: nnb000000
71// ```
72//
73// or
74// ```
75// xs: nnnnnb000
76// ys: nnnnb000000
77// ```
78//
79// where 0 is a zero limb, n is a nonzero limb, and b is the boundary (least-significant) nonzero
80// limb. xs_i and ys_i are the indices of the boundary limbs in xs and ys. xs_i < ys_i but xs may be
81// shorter, longer, or the same length as ys.
82fn limbs_hamming_distance_neg_helper(xs: &[Limb], ys: &[Limb], xs_i: usize, ys_i: usize) -> u64 {
83 let mut distance = CountOnes::count_ones(xs[xs_i].wrapping_neg());
84 let xs_len = xs.len();
85 if xs_i == xs_len - 1 {
86 return distance + limbs_count_zeros_neg(&ys[xs_len..]);
87 }
88 if xs_len < ys_i {
89 return distance
90 + limbs_count_zeros(&xs[xs_i + 1..])
91 + limbs_count_zeros_neg(&ys[xs_len..]);
92 }
93 distance += limbs_count_zeros(&xs[xs_i + 1..ys_i]);
94 if xs_len == ys_i {
95 return distance + limbs_count_zeros_neg(&ys[xs_len..]);
96 }
97 distance += ys[ys_i].wrapping_neg().hamming_distance(!xs[ys_i]);
98 if xs_len == ys_i + 1 {
99 return distance + limbs_count_ones(&ys[xs_len..]);
100 }
101 distance + limbs_hamming_distance_neg_leading_limbs_helper(xs, ys, ys_i)
102}
103
104// Interpreting two equal-length slices of `Limb`s as the limbs of `Natural`s in ascending order,
105// returns the Hamming distance between their negatives (two's complement). Both have infinitely
106// many implicit leading ones. Neither slice may be empty or only contain zeros.
107//
108// # Worst-case complexity
109// $T(n) = O(n)$
110//
111// $M(n) = O(1)$
112//
113// where $T$ is time, $M$ is additional memory, and $n$ is `xs.len()`.
114//
115// # Panics
116// May panic if `xs` or `ys` only contain zeros.
117//
118// This is equivalent to `mpz_hamdist` from `mpz/hamdist.c`, GMP 6.2.1, where both arguments are
119// negative and have the same length.
120pub_test! {limbs_hamming_distance_neg(xs: &[Limb], ys: &[Limb]) -> u64 {
121 let xs_i = slice_leading_zeros(xs);
122 let ys_i = slice_leading_zeros(ys);
123 match xs_i.cmp(&ys_i) {
124 Equal => {
125 xs[xs_i]
126 .wrapping_neg()
127 .hamming_distance(ys[ys_i].wrapping_neg())
128 + limbs_hamming_distance_neg_leading_limbs_helper(xs, ys, xs_i)
129 }
130 Less => limbs_hamming_distance_neg_helper(xs, ys, xs_i, ys_i),
131 Greater => limbs_hamming_distance_neg_helper(ys, xs, ys_i, xs_i),
132 }
133}}
134
135impl Natural {
136 fn hamming_distance_neg_limb(&self, other: Limb) -> u64 {
137 match self {
138 Natural(Small(small)) => small.wrapping_neg().hamming_distance(other.wrapping_neg()),
139 Natural(Large(limbs)) => limbs_hamming_distance_limb_neg(limbs, other),
140 }
141 }
142
143 fn hamming_distance_neg(&self, other: &Natural) -> u64 {
144 match (self, other) {
145 (&Natural(Small(x)), _) => other.hamming_distance_neg_limb(x),
146 (_, &Natural(Small(y))) => self.hamming_distance_neg_limb(y),
147 (Natural(Large(xs)), Natural(Large(ys))) => limbs_hamming_distance_neg(xs, ys),
148 }
149 }
150}
151
152impl CheckedHammingDistance<&Integer> for &Integer {
153 /// Determines the Hamming distance between two [`Integer`]s.
154 ///
155 /// The two [`Integer`]s have infinitely many leading zeros or infinitely many leading ones,
156 /// depending on their signs. If they are both non-negative or both negative, the Hamming
157 /// distance is finite. If one is non-negative and the other is negative, the Hamming distance
158 /// is infinite, so `None` is returned.
159 ///
160 /// # Worst-case complexity
161 /// $T(n) = O(n)$
162 ///
163 /// $M(n) = O(1)$
164 ///
165 /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.significant_bits(),
166 /// other.significant_bits())`.
167 ///
168 /// # Examples
169 /// ```
170 /// use malachite_base::num::logic::traits::CheckedHammingDistance;
171 /// use malachite_nz::integer::Integer;
172 ///
173 /// assert_eq!(
174 /// Integer::from(123).checked_hamming_distance(&Integer::from(123)),
175 /// Some(0)
176 /// );
177 /// // 105 = 1101001b, 123 = 1111011
178 /// assert_eq!(
179 /// Integer::from(-105).checked_hamming_distance(&Integer::from(-123)),
180 /// Some(2)
181 /// );
182 /// assert_eq!(
183 /// Integer::from(-105).checked_hamming_distance(&Integer::from(123)),
184 /// None
185 /// );
186 /// ```
187 fn checked_hamming_distance(self, other: &Integer) -> Option<u64> {
188 match (self.sign, other.sign) {
189 (true, true) => Some(self.abs.hamming_distance(&other.abs)),
190 (false, false) => Some(self.abs.hamming_distance_neg(&other.abs)),
191 _ => None,
192 }
193 }
194}