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}