Как создать пользовательский NaN (одинарной точности) в Python без установки 23-го бита? - PullRequest
1 голос
/ 24 мая 2019

Я пытаюсь создать NaN с плавающей точкой, выбрав дробь бит.Но похоже, что python float всегда устанавливает 23-й бит дроби (IEEE754 single), когда он интерпретирует NaN.

Итак, мой вопрос: возможно ли определить float nan в python без установки 23-го бита?

(я использую Python 2.7)

NaN в IEEE 754 имеют следующий формат:
знак = либо 0, либо 1.
смещенный показатель = все 1 бит.
дробь = все, кроме всех 0 бит (поскольку все 0 бит представляют бесконечность).

Итак, шестнадцатеричное представление для NaN может быть 0x7F800001, но при интерпретации этогоint в виде числа с плавающей запятой и интерпретация его обратно в int дает 0x7FC00001

1-я попытка: struct.pack / unpack:

import struct

def hex_to_float(value):
    return struct.unpack( '@f', struct.pack( '@L', value) )[0]

def float_to_hex(value):
    return struct.unpack( '@L', struct.pack( '@f', value) )[0]

print hex(float_to_hex(hex_to_float(0x7f800001)))
# 0x7fc00001

2-я попытка: ctypes

import ctypes

def float2hex(float_input):
    INTP = ctypes.POINTER(ctypes.c_uint)
    float_value = ctypes.c_float(float_input)
    my_pointer = ctypes.cast(ctypes.addressof(float_value), INTP)
    return my_pointer.contents.value

def hex2float(hex_input):
    FLOATP = ctypes.POINTER(ctypes.c_float)
    int_value = ctypes.c_uint(hex_input)
    my_pointer = ctypes.cast(ctypes.addressof(int_value), FLOATP)
    return my_pointer.contents.value

print hex(float2hex(hex2float(0x7f800001)))
# 0x7fc00001L

3-йпопробуйте: упаковщики xdrlib.Тот же результат.

Ответы [ 2 ]

2 голосов
/ 26 мая 2019

Основная проблема заключается в том, что вы конвертируете C-float (который имеет 32 бита) в Python-float (который имеет 64-битный, то есть double на C-языке), а затем обратно в C-float.

Выполнение обеих c-конверсий друг за другом не всегда приводит к исходному вводу - вы наблюдаете такой случай.

Если важен точный битовый шаблон, вы должны избегать вышеуказанных преобразований в любомСтоимость.


Вот некоторые подробности:

Поэтому, когда struct.unpack('=f', some_bytes) (обратите внимание, что я использую символ формата = в стандартном размере по сравнению с вашим использованием нативного размера('@'), например @L означает разные вещи в Windows и Linux), происходит следующее:

Onx86-64 последнее преобразование означает операцию VCVTSS2SD (т.е. преобразование скалярного значения с плавающей запятой одинарной точности в скалярное значение с плавающей запятой двойной точности), и эта операция приводит к

0x7f800001 becomming 0x7ff8000020000000.

Как видите, уже результат операции struct.unpack( '=f', struct.pack( '=L', value) )[0] не тот, который был вставлен.

Однако, вызов struct.pack( = f , value)для python-float value (который является оберткой вокруг C double), приведет нас к _PyFloat_Pack4, , где преобразование из double в float происходит, то есть CVTSD2SS (Преобразование скалярного значения с плавающей запятой двойной точности в скалярное значение с плавающей запятой одинарной точности) и

0x7ff8000020000000 становится 0x7fc00001.

1 голос
/ 24 мая 2019

Что вы действительно пытаетесь сделать?

Любые плавающие коды Python будут игнорировать «специально созданный» NaN в лучшем случае и падать в худшем случае.

Если вы передаете это значение чему-то вне кода Python - сериализация,или вызывая C API, просто определите его с точными байтами, которые вы хотите, используя struct, и отправьте эти байты в желаемое место назначения.

Кроме того, если вы используете NumPy, то да, вы можете создать специальныйNaNs и ожидайте, что затем будете повторно объединены в ndarray - но способ сделать это также через диктовку точных байтов, которые вы хотите с struct, и каким-то образом преобразовать тип данных при сохранении содержимого буфера.

Проверьте этоответ о создании 80-битных двойных чисел для использования с NumPy, чтобы получить обходной путь: Longdouble (1e3000) становится inf: Что я могу сделать?

(я попробовал numpy.frombuffer здесь, и этоинтерпретирует последовательность байтов, которую вы там создали, как 32-битную, если вам это подходит:

import numpy as np
import binascii
a = "7f800001"
b = binascii.unhexlify(a) # in Python 2 a.decode("hex") would work, but not Python3
# little endian format we need to revert the byte order
c = "".join(b[::-1])
x = np.frombuffer(c, dtype="float32")
x.tobytes()

напечатает оригинал -

'\x01\x00\x80\x7f'

И проверка массива x покажет, что это на самом деле NaN:

>>> x
array([nan], dtype=float32) 

Однако по указанным выше причинам, если вы извлекаете значение из массива numpy с помощью x [0], онобудет преобразован в "pasteurizd" float64 NaN со значением по умолчанию.

...