|
1 | | -//! Lean `Nat` (arbitrary-precision natural number) representation. |
| 1 | +//! Lean `Nat` (arbitrary-precision natural number) FFI surface. |
| 2 | +//! |
| 3 | +//! The generic `Nat = BigUint` newtype lives in the `bignat` crate and is |
| 4 | +//! re-exported here. This module adds the Lean-side encode/decode operations |
| 5 | +//! as inherent methods on [`LeanNat<LeanOwned>`], plus the GMP-backed limb |
| 6 | +//! constructor used to build big Nats. |
2 | 7 | //! |
3 | 8 | //! Lean stores small naturals as tagged scalars and large ones as GMP |
4 | | -//! `mpz_object`s on the heap. This module handles both representations. |
| 9 | +//! `mpz_object`s on the heap; both representations are handled here. |
5 | 10 |
|
6 | 11 | use std::ffi::c_int; |
7 | | -use std::fmt; |
8 | 12 | use std::mem::MaybeUninit; |
9 | 13 |
|
10 | 14 | use num_bigint::BigUint; |
11 | 15 |
|
12 | | -use crate::object::{LeanNat, LeanOwned, LeanRef}; |
13 | | - |
14 | | -/// Arbitrary-precision natural number, wrapping `BigUint`. |
15 | | -#[derive(Hash, PartialEq, Eq, Debug, Clone, PartialOrd, Ord)] |
16 | | -pub struct Nat(pub BigUint); |
| 16 | +pub use bignat::Nat; |
17 | 17 |
|
18 | | -impl fmt::Display for Nat { |
19 | | - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
20 | | - write!(f, "{}", self.0) |
21 | | - } |
22 | | -} |
23 | | - |
24 | | -impl From<u64> for Nat { |
25 | | - fn from(x: u64) -> Self { |
26 | | - Nat(BigUint::from(x)) |
27 | | - } |
28 | | -} |
29 | | - |
30 | | -impl Nat { |
31 | | - pub const ZERO: Self = Self(BigUint::ZERO); |
32 | | - |
33 | | - /// Try to convert to u64, returning None if the value is too large. |
34 | | - #[inline] |
35 | | - pub fn to_u64(&self) -> Option<u64> { |
36 | | - u64::try_from(&self.0).ok() |
37 | | - } |
| 18 | +use crate::include::lean_uint64_to_nat; |
| 19 | +use crate::object::{LeanNat, LeanOwned, LeanRef}; |
38 | 20 |
|
39 | | - /// Decode a `Nat` from any Lean reference. Handles both scalar (unboxed) |
| 21 | +impl LeanNat<LeanOwned> { |
| 22 | + /// Decode a [`Nat`] from any Lean reference. Handles both scalar (unboxed) |
40 | 23 | /// and heap-allocated (GMP `mpz_object`) representations. |
41 | | - pub fn from_obj(obj: &impl LeanRef) -> Nat { |
| 24 | + pub fn to_nat(obj: &impl LeanRef) -> Nat { |
42 | 25 | if obj.is_scalar() { |
43 | 26 | Nat(BigUint::from(obj.unbox_usize() as u64)) |
44 | 27 | } else { |
45 | | - // Heap-allocated big integer (mpz_object) |
46 | 28 | let mpz: &MpzObject = unsafe { &*obj.as_raw().cast() }; |
47 | 29 | Nat(mpz.m_value.to_biguint()) |
48 | 30 | } |
49 | 31 | } |
50 | 32 |
|
51 | | - #[inline] |
52 | | - pub fn from_le_bytes(bytes: &[u8]) -> Nat { |
53 | | - Nat(BigUint::from_bytes_le(bytes)) |
54 | | - } |
55 | | - |
56 | | - #[inline] |
57 | | - pub fn to_le_bytes(&self) -> Vec<u8> { |
58 | | - self.0.to_bytes_le() |
59 | | - } |
60 | | - |
61 | | - /// Convert this `Nat` into a Lean `Nat` (returns an owned reference). |
62 | | - pub fn to_lean(&self) -> LeanNat<LeanOwned> { |
63 | | - // Try to get as u64 first |
64 | | - if let Some(val) = self.to_u64() { |
65 | | - // For small values that fit in a boxed scalar (max value is usize::MAX >> 1) |
66 | | - if val <= (usize::MAX >> 1) as u64 { |
| 33 | + /// Convert a [`Nat`] into a Lean `Nat` (owned reference). |
| 34 | + pub fn from_nat(n: &Nat) -> Self { |
| 35 | + let raw = match n.to_u64() { |
| 36 | + Some(val) if val <= (usize::MAX >> 1) as u64 => { |
67 | 37 | #[allow(clippy::cast_possible_truncation)] |
68 | | - return LeanNat::new(LeanOwned::box_usize(val as usize)); |
| 38 | + let scalar = val as usize; |
| 39 | + LeanOwned::box_usize(scalar) |
69 | 40 | } |
70 | | - return LeanNat::new(LeanOwned::from_nat_u64(val)); |
71 | | - } |
72 | | - // For values larger than u64, access limbs directly (no byte round-trip) |
73 | | - let limbs = self.0.to_u64_digits(); |
74 | | - LeanNat::new(unsafe { lean_nat_from_limbs(limbs.len(), limbs.as_ptr()) }) |
| 41 | + Some(val) => LeanOwned::from_nat_u64(val), |
| 42 | + None => { |
| 43 | + let limbs = n.0.to_u64_digits(); |
| 44 | + unsafe { lean_nat_from_limbs(limbs.len(), limbs.as_ptr()) } |
| 45 | + } |
| 46 | + }; |
| 47 | + LeanNat::new(raw) |
75 | 48 | } |
76 | 49 | } |
77 | 50 |
|
@@ -113,8 +86,6 @@ impl Mpz { |
113 | 86 | // GMP interop for building Lean Nat objects from limbs |
114 | 87 | // ============================================================================= |
115 | 88 |
|
116 | | -use crate::include::lean_uint64_to_nat; |
117 | | - |
118 | 89 | /// LEAN_MAX_SMALL_NAT = SIZE_MAX >> 1 |
119 | 90 | const LEAN_MAX_SMALL_NAT: u64 = (usize::MAX >> 1) as u64; |
120 | 91 |
|
@@ -145,27 +116,29 @@ unsafe extern "C" { |
145 | 116 | /// # Safety |
146 | 117 | /// `limbs` must be valid for reading `num_limbs` elements. |
147 | 118 | pub unsafe fn lean_nat_from_limbs(num_limbs: usize, limbs: *const u64) -> LeanOwned { |
148 | | - if num_limbs == 0 { |
149 | | - return LeanOwned::box_usize(0); |
150 | | - } |
151 | | - let first = unsafe { *limbs }; |
152 | | - if num_limbs == 1 && first <= LEAN_MAX_SMALL_NAT { |
153 | | - #[allow(clippy::cast_possible_truncation)] // only targets 64-bit |
154 | | - return LeanOwned::box_usize(first as usize); |
155 | | - } |
156 | | - if num_limbs == 1 { |
157 | | - return unsafe { LeanOwned::from_raw(lean_uint64_to_nat(first)) }; |
158 | | - } |
159 | | - // Multi-limb: use GMP |
160 | | - unsafe { |
161 | | - let mut value = MaybeUninit::<Mpz>::uninit(); |
162 | | - mpz_init(value.as_mut_ptr()); |
163 | | - // order = -1 (least significant limb first) |
164 | | - // size = 8 bytes per limb, endian = 0 (native), nails = 0 |
165 | | - mpz_import(value.as_mut_ptr(), num_limbs, -1, 8, 0, 0, limbs); |
166 | | - // lean_alloc_mpz deep-copies; we must free the original |
167 | | - let result = lean_alloc_mpz(value.as_mut_ptr()); |
168 | | - mpz_clear(value.as_mut_ptr()); |
169 | | - LeanOwned::from_raw(result.cast()) |
| 119 | + match num_limbs { |
| 120 | + 0 => LeanOwned::box_usize(0), |
| 121 | + 1 => { |
| 122 | + let first = unsafe { *limbs }; |
| 123 | + if first <= LEAN_MAX_SMALL_NAT { |
| 124 | + #[allow(clippy::cast_possible_truncation)] // only targets 64-bit |
| 125 | + let scalar = first as usize; |
| 126 | + LeanOwned::box_usize(scalar) |
| 127 | + } else { |
| 128 | + unsafe { LeanOwned::from_raw(lean_uint64_to_nat(first)) } |
| 129 | + } |
| 130 | + } |
| 131 | + // Multi-limb: use GMP |
| 132 | + _ => unsafe { |
| 133 | + let mut value = MaybeUninit::<Mpz>::uninit(); |
| 134 | + mpz_init(value.as_mut_ptr()); |
| 135 | + // order = -1 (least significant limb first) |
| 136 | + // size = 8 bytes per limb, endian = 0 (native), nails = 0 |
| 137 | + mpz_import(value.as_mut_ptr(), num_limbs, -1, 8, 0, 0, limbs); |
| 138 | + // lean_alloc_mpz deep-copies; we must free the original |
| 139 | + let result = lean_alloc_mpz(value.as_mut_ptr()); |
| 140 | + mpz_clear(value.as_mut_ptr()); |
| 141 | + LeanOwned::from_raw(result.cast()) |
| 142 | + }, |
170 | 143 | } |
171 | 144 | } |
0 commit comments