HugeNats
Wrapper inmutable sobre int de Python para numeros naturales grandes (N >= 0) con indexado y slicing de bits, vistas NumPy y un nucleo listo para Numba.
Internamente es un int de Python. No reinventa aritmetica: delega en CPython y solo anade acceso por bits, vistas de limbs uint64[], y kernels Numba para pipelines numericos sin overhead de objetos.
Repositorio: https://github.com/nand0san/hugenats
Instalacion
pip install HugeNats
Dependencias: numpy, numba.
Creacion
import numpy as np
from hugenats import HugeNats
Desde int
x = HugeNats(42)
x = HugeNats(0) # cero es valido
# HugeNats(-1) # ValueError: solo naturales
Desde float
Acepta floats que sean enteros exactos, no negativos y <= 2**53.
x = HugeNats(3.0) # HugeNats(3)
# HugeNats(3.5) # ValueError: debe ser entero exacto
# HugeNats(float(2**54)) # ValueError: demasiado grande
Desde lista o tupla (big-endian)
Las listas y tuplas se interpretan en orden big-endian: el primer elemento es el limb mas significativo. Es la convencion natural de lectura izquierda-a-derecha.
x = HugeNats([1, 2, 3])
# Equivale a: 1 * 2^128 + 2 * 2^64 + 3
x = HugeNats([0xDEAD, 0xBEEF])
# limb MS = 0xDEAD, limb LS = 0xBEEF
Los ceros a la izquierda (mayor peso) se recortan automaticamente:
HugeNats([0, 0, 5]) == HugeNats(5) # True
Desde np.ndarray (little-endian)
Los arrays NumPy se interpretan en orden little-endian: el elemento 0 es el limb menos significativo. Es el formato interno de HugeNats.
limbs = np.array([0xFFFFFFFFFFFFFFFF, 0x1], dtype=np.uint64)
x = HugeNats(limbs)
# limb 0 (LS) = 0xFFFF...F, limb 1 (MS) = 0x1
Desde otro HugeNats
Copia el valor y hereda bit_length fijo (si lo tiene), salvo que se pase uno nuevo.
a = HugeNats(42, bit_length=16)
b = HugeNats(a) # copia, hereda bit_length=16
c = HugeNats(a, bit_length=32) # copia, sobreescribe a bit_length=32
Con anchura logica fija (bit_length)
Por defecto la anchura es int(x).bit_length(). Si se pasa bit_length, se fija:
- Si el valor tiene mas bits, se trunca por arriba.
- Si tiene menos, se consideran ceros a la izquierda.
bit_length()ylen(x)devuelven siempre la anchura fijada.
padded = HugeNats(0b1011, bit_length=8) # 00001011, ancho logico 8
cut = HugeNats(0b110101, bit_length=4) # 0101, truncado a 4 bits
padded.bit_length() # 8
len(padded) # 8 (equivale a bit_length())
Desde bytes
x = HugeNats.from_bytes(b'\x10\x00\x7b', byteorder="big")
b = x.to_bytes(length=3, byteorder="big", signed=False)
Desde limbs big-endian + bit_length
words_be = np.array([0x0001, 0xFFFFFFFFFFFFFFFF], dtype=np.uint64)
x = HugeNats.from_words_be(words_be, bit_length=128)
Desde core Numba (limbs LE + nbits)
x = HugeNats.from_core(limbs, nbits) # LE (default)
x = HugeNats.from_core(limbs_be, nbits, word_order="be") # BE
Conversiones
x = HugeNats(42)
int(x) # 42
float(x) # 42.0
bool(x) # True (False solo para HugeNats(0))
str(x) # '42'
hash(x) # consistente con int
len(x) # bit_length()
HugeNats(0b1011).weight # 3 (bits a 1, atajo de bit_count())
Aritmetica
Todas las operaciones aceptan HugeNats o int >= 0 y devuelven HugeNats. Los operadores reflejados estan implementados (2 + HugeNats(3) funciona).
a = HugeNats(10)
b = HugeNats(7)
a + b # HugeNats(17)
a - b # HugeNats(3) -- ValueError si resultado < 0
a * b # HugeNats(70)
a // b # HugeNats(1)
a % b # HugeNats(3)
divmod(a,b) # (HugeNats(1), HugeNats(3))
a ** 3 # HugeNats(1000)
pow(a, 3, 7)# HugeNats(6) -- exponenciacion modular
2 ** a # HugeNats(1024) -- reflejado
abs(a) # HugeNats(10) -- identidad
+a # HugeNats(10) -- identidad
-a # ValueError: no permite negativos
Nota sobre pow: pow(base, exp, mod) usa la exponenciacion modular nativa de Python, muy eficiente para valores grandes (RSA, Diffie-Hellman, etc.).
Comparaciones
Soporta ==, !=, <, <=, >, >= entre HugeNats, int, y para == tambien listas/tuplas/arrays.
HugeNats(100) < HugeNats(200) # True
HugeNats(100) >= 50 # True
HugeNats([1, 2]) == [1, 2] # True (lista BE)
Operaciones bitwise
a = HugeNats(0b1100)
b = HugeNats(0b1010)
a & b # HugeNats(0b1000) AND
a | b # HugeNats(0b1110) OR
a ^ b # HugeNats(0b0110) XOR
a << 2 # HugeNats(0b110000) shift left
a >> 1 # HugeNats(0b110) shift right
~a # NOT (ver seccion dedicada)
Los shifts no aceptan bool como argumento; usar int explicito.
Preservacion de bit_length fijo
Las operaciones bitwise preservan _fixed_nbits automaticamente:
- Si ambos operandos tienen el mismo ancho fijo: el resultado lo hereda.
- Si solo
selflo tiene: el resultado hereda deself. - Si ambos difieren: resultado sin ancho fijo.
- Los shifts preservan siempre el ancho de
selfy aplican mascara.
a = HugeNats(0xFF, bit_length=8)
b = HugeNats(0x0F, bit_length=8)
(a & b).bit_length() # 8 (heredado)
(HugeNats(0x80, bit_length=8) << 1) # HugeNats(0, bit_length=8) -- truncado
La aritmetica (+, -, *, //, %, **) no preserva bit_length fijo.
NOT bit a bit (~)
~x voltea todos los bits dentro del ancho logico y devuelve un HugeNats con ese ancho fijado.
- Con
bit_lengthdeclarado: voltea dentro de esa anchura. - Sin
bit_lengthdeclarado: usa elbit_length()natural. ~~x == xsiempre.HugeNats(0)sin anchura:bit_length()=0, no hay bits que voltear, devuelveHugeNats(0, bit_length=0).
~HugeNats(0b1011, bit_length=4) # HugeNats(4, bit_length=4) -> 0b0100
~HugeNats(0, bit_length=8) # HugeNats(255, bit_length=8) -> todo unos
~HugeNats(0xFF, bit_length=8) # HugeNats(0, bit_length=8)
x = HugeNats(0b10110010, bit_length=8)
int(x & ~x) # 0 (siempre)
Rotaciones de bits
Rotan dentro del ancho logico (bit_length()). Shift grande se normaliza con mod bit_length().
x = HugeNats(0b100101)
x.rotl(2) # HugeNats(22, bit_length=6) rota a la izquierda
x.rotr(2) # HugeNats(25, bit_length=6) rota a la derecha
HugeNats(0).rotl(5) # HugeNats(0), sin error
Indexado de bits
Convencion: LSB = indice 0. Indices negativos cuentan desde el MSB. Fuera de rango devuelve 0 (no lanza excepcion).
x = HugeNats(0b1101101) # 109
x[0] # 1 (LSB)
x[-1] # 1 (MSB)
x[100] # 0 (fuera de rango)
x[-100] # 0 (fuera de rango)
Slicing de bits
El bit start pasa a ser el bit 0 del resultado.
step=Noneo1: ruta rapida, normaliza limites como Python.step != 0, != 1: ruta general con semantica completa de slicing de listas.- Bits por encima de
bit_length()se consideran ceros implicitos.
x = HugeNats(0b1101101)
int(x[0:3]) # 5 (bits 0..2 -> 0b101)
int(x[2:5]) # 6 (0b110)
int(x[0:7:2]) # 11 (cada 2 bits -> 0b1011)
int(x[5:0:-2]) # paso negativo
Division en trozos (split)
split(n) divide en n trozos de igual cantidad de bits. Devuelve tupla LSB-primero.
bit_length()debe ser divisible porn.- Cada trozo conserva su
bit_lengthfijo.
x = HugeNats(0b1011010110010001, bit_length=16)
parts = x.split(4)
[int(p) for p in parts] # [1, 9, 5, 11] (LSB -> MSB)
[p.bit_length() for p in parts] # [4, 4, 4, 4]
Concatenacion de bits (append)
append(B, C, ...) coloca los bits de self en las posiciones bajas (LSB), los de B encima, etc. Inversa de split.
a = HugeNats(0b11, bit_length=2)
b = HugeNats(0b01, bit_length=2)
r = a.append(b) # bits: 01|11 -> 7, bit_length=4
# Reconstruir desde split
x = HugeNats(0xDEADBEEF, bit_length=32)
parts = x.split(4)
assert int(parts[0].append(*parts[1:])) == int(x)
Vista de bits
Array NumPy (bits)
bits(order="msb->lsb", length=None) devuelve np.ndarray[uint8].
x = HugeNats(0b1011)
x.bits() # array([1, 0, 1, 1], uint8) MSB primero
x.bits(order="lsb->msb") # array([1, 1, 0, 1], uint8) LSB primero
x.bits(length=8) # padding: array([0, 0, 0, 0, 1, 0, 1, 1])
Cadena de texto (bits_str)
bits_str(order="msb->lsb", group=64, sep=" ") -- los grupos se alinean desde la derecha (el primer grupo puede ser mas corto).
x = HugeNats(0b101101)
x.bits_str(group=4) # '10 1101' (grupo corto a la izquierda)
x.bits_str(group=8) # '101101' (cabe en un grupo)
Nucleo Numba-friendly (to_core / from_core)
HugeNats es inmutable. Los limbs uint64[] se cachean internamente; llamadas repetidas con copy=False son O(1).
to_core(copy=True, word_order="le")
Devuelve (limbs: uint64[::1], nbits: int64).
copy=True(default): copia independiente, escribible.copy=False: vista read-only de la cache interna (sin alloc).word_order="le"(default): limb 0 = bits 0..63.word_order="be": MSB primero.
x = HugeNats(2**127 + 0xF00D)
limbs, nbits = x.to_core()
# Para Numba, asegurar contiguidad y tipos exactos:
limbs = np.ascontiguousarray(limbs, dtype=np.uint64)
nbits = np.int64(nbits)
from_core(limbs, nbits, word_order="le")
Reconstruye HugeNats desde (limbs, nbits). Fija bit_length a nbits.
y = HugeNats.from_core(limbs, nbits)
y = HugeNats.from_core(limbs_be, nbits, word_order="be")
API big-endian de palabras
to_words_be() / from_words_be(words_be, bit_length)
Para pipelines 100% big-endian. to_words_be() devuelve uint64[] con MSW primero. from_words_be requiere bit_length explicito.
x = HugeNats(0xAAAABBBBCCCCDDDD_1111222233334444, bit_length=128)
be = x.to_words_be()
y = HugeNats.from_words_be(be, 128)
assert int(y) == int(x)
Extraccion directa a uint64[]
APIs que evitan crear objetos HugeNats por trozo, devolviendo arrays directamente.
split_core(parts, word_order="le")
Array 2D (parts, words_per_part). Fila 0 = chunk LSB. Default LE.
split_array(parts, word_order="be")
Identico a split_core pero con default BE. Para pipelines big-endian.
split_u64(parts)
Array 1D (parts,) de uint64. Para chunks de hasta 64 bits.
extract_bits_core(start, width, word_order="le")
Extrae width bits desde posicion start como array 1D de uint64.
extract_bits_u64(start, width)
Extrae hasta 64 bits como un solo np.uint64.
x = HugeNats(0xDEADBEEFCAFEBABE, bit_length=64)
x.split_core(4) # shape (4, 1), LE
x.split_array(4) # shape (4, 1), BE
x.split_u64(4) # array([0xBABE, 0xCAFE, 0xBEEF, 0xDEAD])
x.extract_bits_core(0, 16) # array([0xBABE])
x.extract_bits_u64(48, 16) # np.uint64(0xDEAD)
Operaciones estaticas sobre arrays BE
Metodos estaticos que operan sobre uint64[] big-endian sin crear objetos HugeNats. Para encadenar operaciones sobre arrays crudos.
HugeNats.bitwise_or(a_be, b_be, bit_length)
OR element-wise. Entrada y salida BE.
HugeNats.shift_left(a_be, k, bit_length)
Shift left por k bits. Trunca al bit_length. Entrada y salida BE.
HugeNats.extract_bits(a_be, start, width)
Extrae width bits desde posicion start. Entrada y salida BE.
HugeNats.concat_array(parts_2d, part_bits, word_order="be")
Inversa de split_array. Concatena array 2D de partes en array 1D.
# Pipeline encadenado sin objetos HugeNats
arr = x.to_words_be()
shifted = HugeNats.shift_left(arr, 32, 256)
combined = HugeNats.bitwise_or(shifted, arr, 256)
chunk = HugeNats.extract_bits(combined, 64, 128)
result = HugeNats.from_words_be(chunk, 128)
Kernels Numba (hugenats.numba_core)
Funciones @njit que operan sobre (limbs, nbits) en little-endian. Pueden llamarse desde otras funciones @njit para componer pipelines sin overhead de objetos Python.
from hugenats.numba_core import (
split_equal_parts_core,
extract_range_core,
concat_parts_core,
bitwise_or_core,
shift_left_core,
)
| Kernel | Entrada | Salida | Descripcion |
|---|---|---|---|
split_equal_parts_core(limbs, nbits, parts) |
limbs LE, nbits, parts | array 2D (parts, words) |
Divide en partes iguales |
extract_range_core(limbs, nbits, start, width) |
limbs LE, nbits, start, width | (limbs_out, nbits_out) |
Extrae rango de bits |
concat_parts_core(parts_2d, chunk_bits) |
array 2D LE, chunk_bits | (limbs_out, nbits_out) |
Concatena partes |
bitwise_or_core(a, b) |
dos arrays LE | (limbs_out, nbits_out) |
OR element-wise |
shift_left_core(limbs, k) |
limbs LE, k bits | (limbs_out, nbits_out) |
Shift left |
Convencion de endianness
| Entrada | Orden | Razon |
|---|---|---|
list / tuple al constructor |
Big-endian (elemento 0 = MS) | Lectura natural izquierda-a-derecha |
np.ndarray al constructor |
Little-endian (elemento 0 = LS) | Formato interno de HugeNats |
to_core() default |
Little-endian | Formato interno |
to_words_be() |
Big-endian | API BE explicita |
split_core() default |
Little-endian | Coherente con to_core |
split_array() default |
Big-endian | Coherente con to_words_be |
Metodos estaticos (bitwise_or, etc.) |
Big-endian | API BE explicita |
Kernels Numba (numba_core) |
Little-endian | Formato nativo de computo |
Referencia rapida
| Metodo | Tipo | Devuelve | Notas |
|---|---|---|---|
HugeNats(value, bit_length=None) |
constructor | HugeNats | int, float, list(BE), ndarray(LE), HugeNats |
int(x), float(x), bool(x), str(x) |
conversion | tipo nativo | |
len(x) |
propiedad | int | = bit_length() |
bit_length() |
instancia | int | anchura logica |
bit_count() |
instancia | int | bits a 1 |
weight |
propiedad | int | bits a 1 (peso de Hamming, atajo de bit_count()) |
bits(order, length) |
instancia | uint8[] |
vista de bits |
bits_str(order, group, sep) |
instancia | str | texto agrupado |
to_bytes(length, byteorder, signed) |
instancia | bytes | como int.to_bytes |
from_bytes(data, byteorder, signed) |
classmethod | HugeNats | como int.from_bytes |
to_core(copy, word_order) |
instancia | (uint64[], int) |
LE o BE |
from_core(limbs, nbits, word_order) |
classmethod | HugeNats | LE o BE |
to_words_be() |
instancia | uint64[] |
solo BE |
from_words_be(words, bit_length) |
classmethod | HugeNats | solo BE |
split(n) |
instancia | tuple[HugeNats] | LSB primero |
append(*others) |
instancia | HugeNats | inversa de split |
split_core(parts, word_order) |
instancia | uint64[n, w] |
default LE |
split_array(parts, word_order) |
instancia | uint64[n, w] |
default BE |
split_u64(parts) |
instancia | uint64[n] |
chunks <= 64b |
extract_bits_core(start, width, word_order) |
instancia | uint64[] |
default LE |
extract_bits_u64(start, width) |
instancia | uint64 |
width <= 64 |
rotl(shift) / rotr(shift) |
instancia | HugeNats | rotacion |
bitwise_or(a_be, b_be, bit_length) |
static | uint64[] |
BE |
shift_left(a_be, k, bit_length) |
static | uint64[] |
BE |
extract_bits(a_be, start, width) |
static | uint64[] |
BE |
concat_array(parts_2d, part_bits, word_order) |
static | uint64[] |
default BE |
Contrato de dominio
- Solo enteros >= 0. Valores negativos o restas con resultado negativo lanzan
ValueError. boolno se acepta como shift en<<,>>,rotl,rotr.- Sin
bit_lengthexplicito, los ceros de mayor peso se recortan. - Con
bit_lengthexplicito, la anchura logica se conserva siempre. - HugeNats es inmutable (usa
__slots__, sin__dict__). Los limbs se cachean internamente. split(n)requiere quebit_length()sea divisible porn.
Desarrollo
pip install -r requirements.txt
pytest -q
Demostraciones interactivas en HugeNats_demo.ipynb.
Metadata
Release files for HugeNats 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hugenats-0.5.0.tar.gz | 36.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hugenats-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.7 kB
Release files / hugenats-0.5.0.tar.gz
| Download URL | hugenats-0.5.0.tar.gz |
|---|---|
| Size | 36.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e26aac4e9d26e9a54477fbe21000c8ac1276ed62a983198699b9543908be299e
|
|
BLAKE2b-256 checksum How to use checksums |
09c799953bffbac34b68a530dea816d328365c730712eaea956de37391809db1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / hugenats-0.5.0-py3-none-any.whl
| Download URL | hugenats-0.5.0-py3-none-any.whl |
|---|---|
| Size | 24.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
30a9c60c2f8406de303f5e25980b89d8e415788099f8aeb726991f9a897a7067
|
|
BLAKE2b-256 checksum How to use checksums |
5f8b27d0f08e94ffb74d97904e9980ed094d9891a2e18ce85c9567493ef726b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|