2012-05-02 11 views
9

¿El Python proporciona una función para obtener el valor de coma flotante que resulta de incrementar el bit menos significativo de un valor de punto flotante existente?¿Cómo se obtiene el siguiente valor en la secuencia de coma flotante?

Estoy buscando algo similar a la función std::nextafter que se agregó en C++ 11.

+0

Creo que no, pero solo tratando de ver cómo se implementa 'std :: nextafter' y es posible que podamos encontrar algo equivalente. – Abhijit

+0

Mi solución basada en cython debería funcionar en Windows (vi que eres un tipo de Windows en tu perfil). Puede instalar cython desde pip. Requiere gcc/g ++. Puede que tenga que establecer las rutas de las bibliotecas, los archivos de biblioteca y los caminos de inclusión en setup.py, que son las rutas de archivos estándar que pasaría a su compilador para crear algo que use cmath. No estoy seguro de cómo se verían esas rutas en Windows o incluso si fueran necesarias (probablemente no), pero no hay razón por la que no debería funcionar. – Endophage

+0

No, no es así. La forma más fácil de falsificarlo es usar el módulo struct para convertir a un int de 8 bytes, agregar uno al int y convertir de nuevo. Eso funciona bien para números positivos, y necesita algunos ajustes para números negativos. –

Respuesta

1

Salida http://docs.python.org/library/stdtypes.html#float.hex

Vamos a probar esto una implementación que no sabe mucho acerca siguiente después.

En primer lugar, tenemos que extraer la parte hexagonal y el exponente de la cadena hexadecimal:

def extract_parts(hex_val): 
    if not hex_val.startswith('0x1.'): 
     return None 
    relevant_chars = hex_val[4:] 
    if not len(relevant_chars) > 14 and relevant_chars[13] == 'p': 
     return None 
    hex_portion = int(relevant_chars[:13], 16) 
    if relevant_chars[14] == '+': 
     p_val = int(relevant_chars[15:]) 
    elif relevant_chars[14] == '-': 
     p_val = -int(relevant_chars[15:]) 
    else: 
     return None 
    return (hex_portion, p_val) 

entonces necesitamos una manera de incrementar en sentido positivo o negativo (vamos a asumir la cadena hexadecimal tiene ya ha convertido a un número entero hex_portion):

def increment_hex(hex_portion, p_val, direction): 
    if hex_portion == 0 and direction == -1: 
     new_hex = 'f' * 13 
     p_val -= 1 
    elif hex_portion == int('f' * 13, 16) and direction == 1: 
     new_hex = '0' * 13 
     p_val += 1 
    else: 
     new_hex = hex(hex_portion + direction)[2:].rstrip('L').zfill(13) 

    if len(new_hex) != 13: 
     return None 
    return format_hex(new_hex, p_val) 

necesitamos una función auxiliar para cambiar el formato de una cadena aceptable hexagonal y exponente, que utilicé anteriormente:

def format_hex(hex_as_str, p_val): 
    sign = '-' if p_val < 0 else '+' 
    return '0x1.%sp%s%d' % (hex_as_str, sign, p_val) 

Por último, para poner en práctica nextafter:

def nextafter(float_val): 
    hex_equivalent = float_val.hex() 
    hex_portion, p_val = extract_parts(hex_equivalent) 

    direction = 1 
    new_hex_equiv = increment_hex(hex_portion, p_val, direction) 
    return float.fromhex(new_hex_equiv) 
+1

¿Eh? ¿Cómo es eso equivalente a 'std :: nextafter'? – phihag

+1

@phihag, no es equivalente en absoluto, pero podría ser la base para escribir el suyo. –

2

ACTUALIZACIÓN:

Resulta que esta es una pregunta duplicado (que aparece en Google como resultado # 2 para la búsqueda "C++ nextafter pitón"): Increment a python floating point value by the smallest possible amount

La respuesta aceptada proporciona algunas soluciones sólidas.

respuesta original:

Ciertamente, esta no es la solución perfecta, pero utilizando Cython sólo unas pocas líneas le permitirá envolver la función existente C++ y utiliza en Python. He compilado el código a continuación y funciona en mi cuadro ubuntu 11.10.

En primer lugar, un archivo .pyx (I llama mina de nextafter.pyx) define el interfaz para el C++:

cdef extern from "cmath": 
    float nextafter(float start, float to) 

def pynextafter(start, to): 
    cdef float float_start = float(start) 
    cdef float float_to = float(to) 
    result = nextafter(start, to) 
    return result 

A continuación, un setup.py define cómo construir la extensión:

from distutils.core import setup 
from distutils.extension import Extension 
from Cython.Distutils import build_ext 

ext_modules=[ 
    Extension("nextafter", 
     ["nextafter.pyx"], 
     libraries=[], 
     library_dirs=[], 
     include_dirs=[], 
     language="c++", 
    ) 
] 

setup(
    name = "nextafter", 
    cmdclass = {"build_ext": build_ext}, 
    ext_modules = ext_modules 
) 

Asegúrate de que estén en el mismo directorio y compila con python setup.py build_ext --inplace. Espero que puedas ver cómo agregarías las otras variaciones de nextafter a la extensión (para dobles, etc ...). Una vez construido, debe tener un nextafter.so. Encienda python en el mismo directorio (o ponga nextafter.so en su ruta en alguna parte) y debería poder llamar al from nextafter import pynextafter.

¡Disfrútalo!

+0

Por extraño que parezca, tengo una respuesta a esa pregunta. –

+0

@MarkRansom umm ... ¿lol? – Endophage

+0

Creo que necesitas reemplazar 'float' con' double' en todas partes en tu archivo '.pyx'. (O use 'nextafterf' si realmente desea usar flotantes, pero dado que las carrozas de Python corresponden a dobles de C++, la versión doble tendría más sentido). –

13

Para responder la primera parte de su pregunta: no, Python no proporciona esta funcionalidad directamente. Pero es bastante fácil escribir una función de Python que hace esto, suponiendo que IEEE 754 coma flotante.

Los formatos binarios flotantes IEEE 754 están diseñados de forma bastante inteligente, de modo que pasar de un número de coma flotante a uno "siguiente" es tan simple como incrementar la representación de bits. Esto funciona para cualquier número en el rango [0, infinity), justo a través de los límites y subnormales del exponente. Para producir una versión de nextUp que cubra todo el rango de coma flotante, también necesita lidiar con números negativos, infinitos, nans y un caso especial que involucra cero negativo. A continuación se muestra una versión compatible con los estándares de la función nextUp de IEEE 754 en Python. Cubre todos los casos de esquina.

import math 
import struct 

def next_up(x): 
    # NaNs and positive infinity map to themselves. 
    if math.isnan(x) or (math.isinf(x) and x > 0): 
     return x 

    # 0.0 and -0.0 both map to the smallest +ve float. 
    if x == 0.0: 
     x = 0.0 

    n = struct.unpack('<q', struct.pack('<d', x))[0] 
    if n >= 0: 
     n += 1 
    else: 
     n -= 1 
    return struct.unpack('<d', struct.pack('<q', n))[0] 

Las implementaciones de nextDown y nextAfter el siguiente aspecto. (Tenga en cuenta que nextAfter no es una función especificada por IEEE 754, por lo que hay un poco de conjeturas sobre lo que debería suceder con los valores especiales IEEE. Aquí estoy siguiendo el estándar IBM Decimal Arithmetic en el que se basa la clase decimal.Decimal de Python)

def next_down(x): 
    return -next_up(-x) 

def next_after(x, y): 
    # If either argument is a NaN, return that argument. 
    # This matches the implementation in decimal.Decimal 
    if math.isnan(x): 
     return x 
    if math.isnan(y): 
     return y 

    if y == x: 
     return y 
    elif y > x: 
     return next_up(x) 
    else: 
     return next_down(x) 
Cuestiones relacionadas