/ / Jak spakować cztery podpisane zmienne w jedną liczbę całkowitą? - python, bit-manipulation, python-3.5, pakowanie

Jak spakować cztery podpisane zmienne w jedną liczbę całkowitą? - python, bit-manipulation, python-3.5, pakowanie

Muszę być w stanie spakować 4 liczby zmiennoprzecinkowe do jednej liczby całkowitej, a następnie rozpakować liczbę całkowitą w moje 4 przepływy.

Przykład pływaków (nie więcej niż 8-cyfrowa precyzja):

-0.02513393, -0.02394553, 0.04248389, 0.02388026

Tak więc pomyślałem, że najpierw muszę rzucić te float na liczby całkowite przez pomnożenie ich przez 1000000000.

floats = [-0.02513393, -0.02394553, 0.04248389, 0.02388026]
integers = list(map(lambda i: int(i * 1000000000), floats))
# output: [-25133930, -23945530, 42483890, 23880260]

A następnie użyj operacji bitowych, aby dopasować cztery liczby do jednego, coś takiego:

a, b, c, d = integers
packed = (a << 24) | (b << 16) | (c << 8) | d

Jednak nie wydaje się to właściwe, ponieważ wartości, które próbuję spakować, są podpisane.


Czy mógłbyś poprosić mnie o odpowiednie rozwiązanie do pakowania takich podpisanych spławów w jedną liczbę całkowitą i prawidłowy sposób rozpakowania?


Myślałem o dodaniu 1 do końca każdej wartości ujemnej i 0 do końca każdej wartości dodatniej i do przywracania liczb całkowitych do wartości pływających, najpierw sprawdziłbym, czy istnieje "a" 1 Zaprzeczę wartości, a następnie podzielę ją przez 1000000000. Ale to wcale nie jest eleganckie.

Odpowiedzi:

1 dla odpowiedzi № 1

Jeśli, według komentarzy, szerokość spakowanych danych nie ma znaczenia, twoje ogólne podejście jest możliwe do zrealizowania z kilkoma poprawkami.

  • Po pierwsze, 8 bitów na każdą liczbę nie jest wystarczające, każdy z nich będzie nakładał się na siebie. cyfry precyzja, ale to nie oznacza, że ​​mają tylko 8 znaczących bity w reprezentacji binarnej. Dobrym sposobem, aby dowiedzieć się, jak szeroki powinien być, jest rozważenie liczby, o której wiesz, że wszystkie są niższe niż (w twoim przypadku 1000000000), wtedy długość bitowa tej liczby (30) jest wystarczająca. Więc mamy:

    packed = a << 90 | b << 60 | c << 30 | re

  • Jak podejrzewasz, nadal ma to problemy z liczbami ujemnymi. Z powyższego mogę z powodzeniem odzyskać d z packed & 2**30-1 i c z (packed & 2**30-1 << 30 ) >> 30, ale robiąc podobne rzeczy dla a i b daje mi nonsens. Więc zmniejsz go do problemu, który już rozwiązałeś. Jeśli dodasz do każdej z nich wystarczająco dużą liczbę, aby wszystkie były pozytywne, możesz traktować je jako niepodpisane - po raz kolejny wiesz, że są mniejsze niż 1000000000, więc istnieje magiczna liczba. niż 2000000000, więc musimy dostosować szerokość pola. Mamy więc:

    pułap = 1000000000 pakowany = (a + sufit) << 31 * 3 | (b + pułap) << 31 * 2 | (pułap c +) << 31 | re

I możemy odzyskać a tak jak ((packed & 2**31-1<< 31*3) >> 31*3) - ceiling. Ze względu na czytelność, możesz rozważyć napisanie tego jako pętli.


2 dla odpowiedzi nr 2

Używając NumPy, możesz widok 4-elementowa tablica dtype float16 jako tablica całkowita typu dtype int64:

In [125]: np.array([-0.02513393, -0.02394553, 0.04248389, 0.02388026], dtype=np.float16).view(np.int64)
Out[125]: array([2746396911566169711])

Aby rozpakować int, możesz użyć view(np.float16):

In [126]: np.array([2746396911566169711]).view(np.float16)
Out[126]: array([-0.02513123, -0.02394104,  0.04248047,  0.02388   ], dtype=float16)

Zwróć uwagę na utratę precyzji.


Korzystając z Python3.2 (lub wyżej) i bez NumPy, możesz spakować zmienne do bajtów, a następnie użyć int.from_bytes konwertować bajty na int. Aby rozpakować, użyj int.to_bytes i struct.unpack:

import struct

def floats_to_int(floats):
return int.from_bytes(struct.pack("4d", *floats), "big")

def int_to_floats(packed):
return struct.unpack("4d", packed.to_bytes(4*8, "big"))

floats = [-0.02513393, -0.02394553, 0.04248389, 0.02388026]
print(floats)
packed = floats_to_int(floats)
print(packed)
result = int_to_floats(packed)
print(result)

drukuje

[-0.02513393, -0.02394553, 0.04248389, 0.02388026]
3995686615650679380069295189325600154682811585786433559914521688689786263615
(-0.02513393, -0.02394553, 0.04248389, 0.02388026)

1 dla odpowiedzi nr 3

Jak wspomniano w komentarzach, twoja obecna strategia nie działa, ponieważ pomieszałeś 8 dziesiętny dokładność cyfrowa z 8 kawałek precyzja.

(a << 24) | (b << 16) | (c << 8) | d

działałoby, gdyby te zmienne zawierały 8-bitowe dane, tj. liczby całkowite w zakresie (256). Potrzebujesz około 32 bitów do przechowywania danych pływaka do 8 dziesiętny dokładność cyfrowa.

Zauważ, że używa standardowego Pythona (znanego też jako CPython) Podwójna precyzja IEEE 754 binary64 dla jego pływaków.

Można jednak przybliżać dane pływaka za pomocą 32-bitowych pojedynczych precyzji i pakować je za pomocą standardu struct moduł. Oto krótka wersja demonstracyjna:

from struct import pack, unpack

# Make a list of 4 Python floats.
a = [i**0.5 for i in range(5, 9)]
print(a)

# Convert the Python floats to 32 bit floats and pack them into 16 bytes, big endian
fmt = ">ffff"
b = pack(fmt, *a)
print(b, len(b))

# Unpack the bytes back into floats
z = unpack(fmt, b)
print(z)
print([u*u for u in z])

# Pack the bytes into an int, using big-endian packing
num = int.from_bytes(b, "big")
print(num)

# Convert the int back to bytes
newb = num.to_bytes(16, "big")
print(newb, newb == b)

wydajność

[2.23606797749979, 2.449489742783178, 2.6457513110645907, 2.8284271247461903]
b"@x0fx1bxbd@x1cxc4q@)Sxfd@5x04xf3" 16
(2.2360680103302, 2.4494898319244385, 2.6457512378692627, 2.8284270763397217)
[5.00000014682206, 6.000000436701214, 6.999999612686736, 7.999999726171666]
85149038802136470295784196693032240371
b"@x0fx1bxbd@x1cxc4q@)Sxfd@5x04xf3" True

Zauważ, że .from_bytes i .to_bytes są funkcje Pythona 3; te same operacje w Pythonie 2 są trochę bardziej szczegółowe.