2012-05-02 6 views
9

Python fornisce una funzione per ottenere il valore a virgola mobile che risulta dall'incremento del bit meno significativo di un valore a virgola mobile esistente?Come si ottiene il valore successivo nella sequenza in virgola mobile?

Sto cercando qualcosa di simile alla funzione std::nextafter che è stata aggiunta in C++ 11.

+0

Credo di no, ma sto solo provando a vedere come 'std :: nextafter' è implementato e può darsi che possiamo inventare qualcosa di equivalente. – Abhijit

+0

La mia soluzione basata su cython dovrebbe funzionare su Windows (ho visto che sei un ragazzo di Windows nel tuo profilo). È possibile installare cython da pip. Richiede gcc/g ++. Potrebbe essere necessario impostare le librerie, le dir_serie e i percorsi include_dirs nel setup.py, sono i percorsi di file standard che si dovrebbero passare al compilatore per creare qualcosa che usi il cmath. Non sei sicuro di quali sarebbero quei percorsi su Windows o anche se sarebbero necessari (probabilmente no), ma non c'è motivo per cui non dovrebbe funzionare. – Endophage

+0

No, non è così. Il modo più semplice per fingere è usare il modulo struct per convertire in un int 8 byte, aggiungerne uno all'int e riconvertire. Questo funziona bene per numeri positivi, e ha bisogno di qualche ritocco per i numeri negativi. –

risposta

1

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

Proviamo questo un'implementazione che non sa molto di successivo dopo.

In primo luogo, abbiamo bisogno di estrarre la parte esagonale e l'esponente dalla stringa esadecimale:

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) 

Poi abbiamo bisogno di un modo per incrementare in direzione positiva o negativa (si suppone che la stringa esadecimale ha già stato convertito in un numero intero 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) 

Abbiamo bisogno di una funzione di supporto per riformattare una stringa esadecimale accettabile e esponente, che ho usato sopra:

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) 

Infine, per implementare 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? Com'è equivalente a 'std :: nextafter'? – phihag

+1

@phihag, non è affatto equivalente ma potrebbe essere la base per scrivere il tuo. –

2

UPDATE:

Risulta questa è una domanda duplicato (di cui si parla in google come risultato # 2 per la ricerca "C++ nextafter python"): Increment a python floating point value by the smallest possible amount

La risposta accettata fornisce alcune solide soluzioni.

RISPOSTA ORIGINALE:

Certamente questa non è la soluzione perfetta, ma utilizzando Cython poche righe vi permetterà di avvolgere la funzione esistente C++ e utilizzarlo in Python. Ho compilato il codice seguente e funziona sulla mia casella Ubuntu 11.10.

In primo luogo, un file .pyx (ho chiamato il mio nextafter.pyx) definisce l'interfaccia per il 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 

Poi un setup.py definisce come costruire l'estensione:

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 
) 

Assicurarsi che quelli siano nella stessa directory quindi creare con python setup.py build_ext --inplace. Spero che tu possa vedere come aggiungere le altre varianti di nextafter all'estensione (per i doppi, ecc ...). Una volta costruito, dovresti avere un nextafter.so. Accendi Python nella stessa directory (o metti nextafter.so sul tuo percorso da qualche parte) e dovresti essere in grado di chiamare from nextafter import pynextafter.

Divertiti!

+0

Stranamente ho una risposta a questa domanda. –

+0

@MarkRansom umm ... lol? – Endophage

+0

Penso che sia necessario sostituire 'float' con' double' ovunque nel file '.pyx'. (Oppure usa 'nextafterf' se vuoi davvero usare float, ma dato che i float Python corrispondono ai doppi C++, la doppia versione avrebbe più senso.) –

13

Per rispondere alla prima parte della domanda: no, Python non fornisce direttamente questa funzionalità. Ma è abbastanza facile scrivere una funzione Python che faccia questo, assumendo IEEE 754 a virgola mobile.

I formati binari in virgola mobile IEEE 754 sono progettati in modo intelligente in modo che lo spostamento da un numero in virgola mobile a quello successivo sia semplice come l'incremento della rappresentazione di bit. Funziona per qualsiasi numero nell'intervallo [0, infinity), oltre i confini esponenziali e le sottodominali. Per produrre una versione di nextUp che copre l'intero intervallo in virgola mobile, devi anche gestire numeri negativi, infiniti, nans e un caso speciale che coinvolge zero negativo. Di seguito è riportata una versione conforme agli standard della funzione nextUp di IEEE 754 in Python. Copre tutti i casi d'angolo.

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] 

Le implementazioni di nextDown e nextAfter quindi simile a questa. (Notare che nextAfter non è una funzione specificata da IEEE 754, quindi c'è un po 'di congetture su cosa dovrebbe accadere con i valori speciali IEEE.Qui sto seguendo lo standard IBM Decimal Arithmetic su cui si basa la classe decimal.Decimal di 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) 
Problemi correlati