range () per i galleggianti


140

Esiste un range()equivalente per i float in Python?

>>> range(0.5,5,1.5)
[0, 1, 2, 3, 4]
>>> range(0.5,5,0.5)

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    range(0.5,5,0.5)
ValueError: range() step argument must not be zero

1
Quelle non sono frazioni ma galleggianti. E i float sono ... beh, probabilmente daranno risultati diversi da quelli che ti aspetti.

6
Una soluzione alternativa sarebbe quella di trattare i numeri interi come decimali, ad esempio range(5, 50, 5)
:,

@delnan - aggiornato. Sono disposto ad accettare piccole imprecisioni per la comodità di avere una gamma float
Jonathan


@NullUserException - questo è solo un esempio - il vero codice è ovviamente parametrico :)
Jonathan

Risposte:


97

Non conosco una funzione integrata, ma scriverne una come questa non dovrebbe essere troppo complicata.

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

Come menzionato nei commenti, questo potrebbe produrre risultati imprevedibili come:

>>> list(frange(0, 100, 0.1))[-1]
99.9999999999986

Per ottenere il risultato atteso, è possibile utilizzare una delle altre risposte in questa domanda oppure, come indicato da @Tadhg, è possibile utilizzare decimal.Decimalcome jumpargomento. Assicurati di inizializzarlo con una stringa anziché un float.

>>> import decimal
>>> list(frange(0, 100, decimal.Decimal('0.1')))[-1]
Decimal('99.9')

O anche:

import decimal

def drange(x, y, jump):
  while x < y:
    yield float(x)
    x += decimal.Decimal(jump)

E poi:

>>> list(drange(0, 100, '0.1'))[-1]
99.9

34
Il motto di Python è in realtà ci dovrebbe essere uno - e preferibilmente solo un - modo evidente per farlo . Ma Python è fantastico comunque :)
Jonathan

3
>>> print list(frange(0,100,0.1))[-1]==100.0saràFalse
Volodimir Kopey il

frangepuò funzionare inaspettatamente. A causa della maledizione dell'aritmetica in virgola mobile , ad esempio frange(0.0, 1.0, 0.1)produce 11 valori, dove è l'ultimo valore 0.9999999999999999. Un miglioramento pratico sarebbe while x + sys.float_info.epsilon < y:anche se anche questo può probabilmente fallire con grandi numeri .
Akseli Palén,

10
-1 Si prega di non utilizzare questo codice , almeno non nei software che potrebbero mai influenzare la mia vita. Non c'è modo di farlo funzionare in modo affidabile. Non usare neanche la risposta di Akseli Palén. Usa la risposta di Xaerxess o di wim (tranne che ignora la parte riguardante arange).
ben

3
funziona alla grande se si utilizzadecimal.Decimal come passaggio anziché float.
Tadhg McDonald-Jensen,

112

Puoi usare:

[x / 10.0 for x in range(5, 50, 15)]

o usa lambda / mappa:

map(lambda x: x/10.0, range(5, 50, 15))

1
E array (range (5,50,15)) / 10.0 come array numpy hanno operatori per gestire divisione, moltiplicazione e così via
edvaldig

2
@edvaldig: hai ragione, non lo sapevo ... Tuttavia penso che l' arange(0.5, 5, 1.5)IMO sia più leggibile.
Xaerxess,

2
Preferisco questa risposta a quella accettata, perché le prime due soluzioni presentate si basano sull'iterazione su numeri interi e sulla derivazione dei float finali dagli interi. Questo è più robusto. Se lo fai direttamente con i float, rischi di avere strani errori una tantum a causa della rappresentazione interna dei float. Ad esempio, se ci provi list(frange(0, 1, 0.5)), funziona bene e 1 è escluso, ma se ci provi list(frange(0, 1, 0.1)), l'ultimo valore che ottieni è vicino a 1.0, che probabilmente non è quello che desideri. Le soluzioni qui presentate non presentano questo problema.
Blubberdiblub,

3
Non usare mai numpy.arange (la stessa documentazione numpy sconsiglia di farlo). Usa numpy.linspace come raccomandato da wim o uno degli altri suggerimenti in questa risposta.
ben

79

Prima usavo, numpy.arangema avevo alcune complicazioni nel controllo del numero di elementi che restituiva, a causa di errori in virgola mobile. Quindi ora uso linspace, ad esempio:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])

Tuttavia, ci sono ancora errori in virgola mobile, senza l'uso decimal, ad esempio:np.linspace(-.1,10,num=5050)[0]
TNT del

2
@TNT No, non è un errore. Scoprirai che np.linspace(-.1,10,num=5050)[0] == -.1è vero. È solo che repr(np.float64('-0.1'))mostra più cifre.
Wim

1
Mentre quel particolare esempio non mostra alcun errore di arrotondamento in eccesso, ci sono casi di errore. Ad esempio, print(numpy.linspace(0, 3, 148)[49])stampa 0.9999999999999999quando sarebbe il risultato ideale 1.0. linspacefa un lavoro molto migliore di arange, ma non è garantito che produca il minimo errore di arrotondamento possibile.
user2357112 supporta Monica il

Si è garantito per eseguire la gestione degli endpoint corretto e sempre produce esattamente il numero richiesto di elementi.
user2357112 supporta Monica il

40

Pylab ha frange(un wrapper, in realtà, per matplotlib.mlab.frange):

>>> import pylab as pl
>>> pl.frange(0.5,5,0.5)
array([ 0.5,  1. ,  1.5,  2. ,  2.5,  3. ,  3.5,  4. ,  4.5,  5. ])

4
Frange è obsoleto dalla versione 2.2 di matplotlib. numpy.arange dovrebbe essere usato.
Kuzavas,

13

Valutato con impazienza (2.x range ):

[x * .5 for x in range(10)]

Valutato pigramente (2.x xrange , 3.x range):

itertools.imap(lambda x: x * .5, xrange(10)) # or range(10) as appropriate

In alternativa:

itertools.islice(itertools.imap(lambda x: x * .5, itertools.count()), 10)
# without applying the `islice`, we get an infinite stream of half-integers.

4
+1; ma perché non (x * .5 for x in range(10))come espressione generatrice di valutazione pigra?
Tim Pietzcker,

2
Perché sarebbe troppo facile, immagino? :)
Karl Knechtel,

11

utilizzando itertools: intervallo in virgola mobile valutato pigramente:

>>> from itertools import count, takewhile
>>> def frange(start, stop, step):
        return takewhile(lambda x: x< stop, count(start, step))

>>> list(frange(0.5, 5, 1.5))
# [0.5, 2.0, 3.5]

3
+1 per l'utilizzo itertools.takewhile. Tuttavia, itertools.count(start, step)soffre di errori in virgola mobile accumulati. (Valuta takewhile(lambda x: x < 100, count(0, 0.1))ad esempio.) Scriverei takewhile(lambda x: x < stop, (start + i * step for i in count()))invece.
musiphil

6

Ho aiutato ad aggiungere la funzione numeric_range al pacchetto more-itertools .

more_itertools.numeric_range(start, stop, step) si comporta come l'intervallo di funzioni incorporato ma può gestire i tipi float, decimali e frazionari.

>>> from more_itertools import numeric_range
>>> tuple(numeric_range(.1, 5, 1))
(0.1, 1.1, 2.1, 3.1, 4.1)

4

Non esiste una funzione integrata di questo tipo, ma è possibile utilizzare quanto segue (codice Python 3) per eseguire il lavoro in modo sicuro come consente Python.

from fractions import Fraction

def frange(start, stop, jump, end=False, via_str=False):
    """
    Equivalent of Python 3 range for decimal numbers.

    Notice that, because of arithmetic errors, it is safest to
    pass the arguments as strings, so they can be interpreted to exact fractions.

    >>> assert Fraction('1.1') - Fraction(11, 10) == 0.0
    >>> assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)

    Parameter `via_str` can be set to True to transform inputs in strings and then to fractions.
    When inputs are all non-periodic (in base 10), even if decimal, this method is safe as long
    as approximation happens beyond the decimal digits that Python uses for printing.


    For example, in the case of 0.1, this is the case:

    >>> assert str(0.1) == '0.1'
    >>> assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'


    If you are not sure whether your decimal inputs all have this property, you are better off
    passing them as strings. String representations can be in integer, decimal, exponential or
    even fraction notation.

    >>> assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
    >>> assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
    >>> assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
    >>> assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
    >>> assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
    >>> assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0

    """
    if via_str:
        start = str(start)
        stop = str(stop)
        jump = str(jump)
    start = Fraction(start)
    stop = Fraction(stop)
    jump = Fraction(jump)
    while start < stop:
        yield float(start)
        start += jump
    if end and start == stop:
        yield(float(start))

Puoi verificarlo tutto eseguendo alcune asserzioni:

assert Fraction('1.1') - Fraction(11, 10) == 0.0
assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)

assert str(0.1) == '0.1'
assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'

assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0

assert list(frange(2, 3, '1/6', end=True))[-1] == 3.0
assert list(frange(0, 100, '1/3', end=True))[-1] == 100.0

Codice disponibile su GitHub


4

Perché non esiste un'implementazione dell'intervallo in virgola mobile nella libreria standard?

Come chiarito da tutti i post qui, non esiste una versione in virgola mobile di range(). Detto questo, l'omissione ha senso se consideriamo che la range()funzione viene spesso utilizzata come generatore di indice (e, naturalmente, ciò significa un accessore ). Quindi, quando chiamiamo range(0,40), stiamo in effetti dicendo che vogliamo 40 valori a partire da 0, fino a 40, ma non inclusivi del 40 stesso.

Se consideriamo che la generazione di indici riguarda tanto il numero di indici quanto i loro valori, l'uso di un'implementazione float range()nella libreria standard ha meno senso. Ad esempio, se abbiamo chiamato la funzionefrange(0, 10, 0.25) , ci saremmo aspettati che fossero inclusi sia 0 che 10, ma ciò avrebbe prodotto un vettore con 41 valori.

Pertanto, una frange()funzione che dipende dal suo uso mostrerà sempre un comportamento contro intuitivo; o ha troppi valori come percepiti dalla prospettiva dell'indicizzazione o non è comprensivo di un numero che ragionevolmente dovrebbe essere restituito dalla prospettiva matematica.

Il caso di uso matematico

Detto questo, come discusso, numpy.linspace()esegue bene la generazione con la prospettiva matematica:

numpy.linspace(0, 10, 41)
array([  0.  ,   0.25,   0.5 ,   0.75,   1.  ,   1.25,   1.5 ,   1.75,
         2.  ,   2.25,   2.5 ,   2.75,   3.  ,   3.25,   3.5 ,   3.75,
         4.  ,   4.25,   4.5 ,   4.75,   5.  ,   5.25,   5.5 ,   5.75,
         6.  ,   6.25,   6.5 ,   6.75,   7.  ,   7.25,   7.5 ,   7.75,
         8.  ,   8.25,   8.5 ,   8.75,   9.  ,   9.25,   9.5 ,   9.75,  10.
])

Il caso d'uso dell'indicizzazione

E per la prospettiva di indicizzazione, ho scritto un approccio leggermente diverso con alcune magiche stringhe che ci permettono di specificare il numero di cifre decimali.

# Float range function - string formatting method
def frange_S (start, stop, skip = 1.0, decimals = 2):
    for i in range(int(start / skip), int(stop / skip)):
        yield float(("%0." + str(decimals) + "f") % (i * skip))

Allo stesso modo, possiamo anche usare la roundfunzione integrata e specificare il numero di decimali:

# Float range function - rounding method
def frange_R (start, stop, skip = 1.0, decimals = 2):
    for i in range(int(start / skip), int(stop / skip)):
        yield round(i * skip, ndigits = decimals)

Un rapido confronto e prestazioni

Naturalmente, vista la discussione di cui sopra, queste funzioni hanno un caso d'uso piuttosto limitato. Tuttavia, ecco un rapido confronto:

def compare_methods (start, stop, skip):

    string_test  = frange_S(start, stop, skip)
    round_test   = frange_R(start, stop, skip)

    for s, r in zip(string_test, round_test):
        print(s, r)

compare_methods(-2, 10, 1/3)

I risultati sono identici per ciascuno:

-2.0 -2.0
-1.67 -1.67
-1.33 -1.33
-1.0 -1.0
-0.67 -0.67
-0.33 -0.33
0.0 0.0
...
8.0 8.0
8.33 8.33
8.67 8.67
9.0 9.0
9.33 9.33
9.67 9.67

E alcuni tempi:

>>> import timeit

>>> setup = """
... def frange_s (start, stop, skip = 1.0, decimals = 2):
...     for i in range(int(start / skip), int(stop / skip)):
...         yield float(("%0." + str(decimals) + "f") % (i * skip))
... def frange_r (start, stop, skip = 1.0, decimals = 2):
...     for i in range(int(start / skip), int(stop / skip)):
...         yield round(i * skip, ndigits = decimals)
... start, stop, skip = -1, 8, 1/3
... """

>>> min(timeit.Timer('string_test = frange_s(start, stop, skip); [x for x in string_test]', setup=setup).repeat(30, 1000))
0.024284090992296115

>>> min(timeit.Timer('round_test = frange_r(start, stop, skip); [x for x in round_test]', setup=setup).repeat(30, 1000))
0.025324633985292166

Sembra che il metodo di formattazione delle stringhe vince da un capello sul mio sistema.

Le limitazioni

E infine, una dimostrazione del punto della discussione sopra e un'ultima limitazione:

# "Missing" the last value (10.0)
for x in frange_R(0, 10, 0.25):
    print(x)

0.25
0.5
0.75
1.0
...
9.0
9.25
9.5
9.75

Inoltre, quando il skipparametro non è divisibile per il stopvalore, può esserci un divario di sbadiglio dato quest'ultimo problema:

# Clearly we know that 10 - 9.43 is equal to 0.57
for x in frange_R(0, 10, 3/7):
    print(x)

0.0
0.43
0.86
1.29
...
8.14
8.57
9.0
9.43

Ci sono modi per affrontare questo problema, ma alla fine, l'approccio migliore sarebbe probabilmente quello di usare solo Numpy.


Questo è un argomento piuttosto contorto. range () dovrebbe semplicemente essere guardato al generatore di iterazioni e se è usato per loop o per indicizzare qualcosa dovrebbe essere lasciato ai chiamanti. Le persone hanno usato i float in for loop per millenni e soprattutto le giustificazioni sono insensate. La gente nei comitati Python ha fatto un casino qui alla grande e probabilmente una buona discussione è stata soffocata da alcune giustificazioni contorte come sopra. È così semplice e chiaro. Ora ci sono troppe decisioni come quelle sopra riportate nel linguaggio Python.
Shital Shah

3

Una soluzione senza dipendenze numpy ecc. È stata fornita da Kichik ma a causa dell'aritmetica in virgola mobile , spesso si comporta in modo imprevisto. Come notato da me e da Blubberdiblub , elementi aggiuntivi si intrufolano facilmente nel risultato. Ad esempio naive_frange(0.0, 1.0, 0.1), cederebbe 0.999...come ultimo valore e quindi darebbe 11 valori in totale.

Una versione robusta è fornita qui:

def frange(x, y, jump=1.0):
    '''Range for floats.'''
    i = 0.0
    x = float(x)  # Prevent yielding integers.
    x0 = x
    epsilon = jump / 2.0
    yield x  # yield always first value
    while x + epsilon < y:
        i += 1.0
        x = x0 + i * jump
        yield x

A causa della moltiplicazione, gli errori di arrotondamento non si accumulano. L'uso di si epsilonprende cura del possibile errore di arrotondamento della moltiplicazione, anche se ovviamente i problemi potrebbero sorgere nelle estremità molto piccole e molto grandi. Ora, come previsto:

> a = list(frange(0.0, 1.0, 0.1))
> a[-1]
0.9
> len(a)
10

E con numeri un po 'più grandi:

> b = list(frange(0.0, 1000000.0, 0.1))
> b[-1]
999999.9
> len(b)
10000000

Il codice è disponibile anche come GitHub Gist .


Questo fallisce con frange (2.0, 17.0 / 6.0, 1.0 / 6.0). Non è mai possibile renderlo robusto.
ben

@benrg Grazie per averlo segnalato! Mi ha portato a capire che epsilon dovrebbe dipendere dal salto, quindi ho rivisto l'algoritmo e risolto il problema. Questa nuova versione è molto più robusta, vero?
Akseli Palén,

2

Una versione senza libreria più semplice

Oh, diamine - lancerò in una semplice versione senza libreria. Sentiti libero di migliorarci [*]:

def frange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    dy = stop-start
    # f(i) goes from start to stop as i goes from 0 to nsteps
    return [start + float(i)*dy/nsteps for i in range(nsteps)]

L'idea principale è che nstepsè il numero di passaggi che ti portano dall'inizio alla fine ed range(nsteps)emette sempre numeri interi in modo da non perdere la precisione. Il passaggio finale consiste nel mappare [0..nsteps] linearmente su [start..stop].

modificare

Se, come alancalvitti, desideri che la serie abbia una rappresentazione razionale esatta, puoi sempre usare Frazioni :

from fractions import Fraction

def rrange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    return [Fraction(i, nsteps) for i in range(nsteps)]

[*] In particolare, frange()restituisce un elenco, non un generatore. Ma è bastato per le mie esigenze.


Se vuoi includere il valore di stop nell'output, aggiungendo stop + jump, questo metodo ritorna al risultato ingenuo con cattivi punti fluttuanti nel mezzo, prova frange(0,1.1,0.1)e anche più di quelli con una scelta comefrange(0,1.05,0.1)
alancalvitti

@alancalvitti: Qual è la tua definizione di "cattivo" virgola mobile? Sì, i risultati potrebbero non essere stampati correttamente, ma frange () fornisce l'insieme più vicino di valori equidistanti entro i limiti della rappresentazione in virgola mobile. Come lo miglioreresti?
fearless_fool

buon punto, sono così abituato a un linguaggio di alto livello in cui dovresti spaziare su numeri razionali per un tale compito, che Py si sente come un assemblaggio.
alancalvitti,

Assemblea? Hrrumph! ;) Naturalmente Python PUO 'fornire una rappresentazione esatta con Fractions: docs.python.org/3/library/fractions.html
fearless_fool

Bene, grazie, ma ad esempio, il linguaggio che mi piace converte automaticamente questi tipi, quindi 1/2 è un razionale, mentre 1 / 2.0 è float, non è necessario dichiararli come tali - lasciare dichiarazioni su Java, che è ancora di più inferiore / assemblaggio di Py.
alancalvitti,

2

Questo può essere fatto con numpy.arange (start, stop, stepsize)

import numpy as np

np.arange(0.5,5,1.5)
>> [0.5, 2.0, 3.5, 5.0]

# OBS you will sometimes see stuff like this happening, 
# so you need to decide whether that's not an issue for you, or how you are going to catch it.
>> [0.50000001, 2.0, 3.5, 5.0]

Nota 1: Dalla discussione nella sezione commenti qui, "non usare mai numpy.arange()(la stessa documentazione numpy sconsiglia di farlo). Usa numpy.linspace come raccomandato da wim, o uno degli altri suggerimenti in questa risposta"

Nota 2: ho letto la discussione in alcuni commenti qui, ma dopo essere tornato a questa domanda per la terza volta ora, ritengo che queste informazioni dovrebbero essere collocate in una posizione più leggibile.


2

Come ha scritto Kichik , questo non dovrebbe essere troppo complicato. Tuttavia questo codice:

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

È inappropriato a causa dell'effetto cumulativo di errori quando si lavora con float. Ecco perché ricevi qualcosa del tipo:

>>>list(frange(0, 100, 0.1))[-1]
99.9999999999986

Mentre il comportamento previsto sarebbe:

>>>list(frange(0, 100, 0.1))[-1]
99.9

Soluzione 1

L'errore cumulativo può essere semplicemente ridotto utilizzando una variabile indice. Ecco l'esempio:

from math import ceil

    def frange2(start, stop, step):
        n_items = int(ceil((stop - start) / step))
        return (start + i*step for i in range(n_items))

Questo esempio funziona come previsto.

Soluzione 2

Nessuna funzione nidificata. Solo un po 'e una variabile contatore:

def frange3(start, stop, step):
    res, n = start, 1

    while res < stop:
        yield res
        res = start + n * step
        n += 1

Anche questa funzione funzionerà bene, ad eccezione dei casi in cui si desidera l'intervallo inverso. Per esempio:

>>>list(frange3(1, 0, -.1))
[]

La soluzione 1 in questo caso funzionerà come previsto. Per far funzionare questa funzione in tali situazioni, è necessario applicare un hack, simile al seguente:

from operator import gt, lt

def frange3(start, stop, step):
    res, n = start, 0.
    predicate = lt if start < stop else gt
    while predicate(res, stop):
        yield res
        res = start + n * step
        n += 1

Con questo trucco puoi usare queste funzioni con passaggi negativi:

>>>list(frange3(1, 0, -.1))
[1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.3999999999999999, 0.29999999999999993, 0.19999999999999996, 0.09999999999999998]

Soluzione 3

Puoi andare ancora oltre con la semplice libreria standard e comporre una funzione di intervallo per la maggior parte dei tipi numerici:

from itertools import count
from itertools import takewhile

def any_range(start, stop, step):
    start = type(start + step)(start)
    return takewhile(lambda n: n < stop, count(start, step))

Questo generatore è adattato dal libro Fluent Python (Capitolo 14. Iterabili, Iteratori e generatori). Non funzionerà con intervalli decrescenti. Devi applicare un hack, come nella soluzione precedente.

È possibile utilizzare questo generatore come segue, ad esempio:

>>>list(any_range(Fraction(2, 1), Fraction(100, 1), Fraction(1, 3)))[-1]
299/3
>>>list(any_range(Decimal('2.'), Decimal('4.'), Decimal('.3')))
[Decimal('2'), Decimal('2.3'), Decimal('2.6'), Decimal('2.9'), Decimal('3.2'), Decimal('3.5'), Decimal('3.8')]

E ovviamente puoi usarlo anche con float e int .

Stai attento

Se si desidera utilizzare queste funzioni con passaggi negativi, è necessario aggiungere un segno di spunta per il segno di passaggio, ad esempio:

no_proceed = (start < stop and step < 0) or (start > stop and step > 0)
if no_proceed: raise StopIteration

L'opzione migliore qui è aumentare StopIteration, se si desidera imitare la rangefunzione stessa.

Gamma mimica

Se desideri imitare l' rangeinterfaccia di funzione, puoi fornire alcuni controlli degli argomenti:

def any_range2(*args):
    if len(args) == 1:
        start, stop, step = 0, args[0], 1.
    elif len(args) == 2:
        start, stop, step = args[0], args[1], 1.
    elif len(args) == 3:
        start, stop, step = args
    else:
        raise TypeError('any_range2() requires 1-3 numeric arguments')

    # here you can check for isinstance numbers.Real or use more specific ABC or whatever ...

    start = type(start + step)(start)
    return takewhile(lambda n: n < stop, count(start, step))

Penso che tu abbia ragione. Puoi usare una di queste funzioni (tranne la prima) e tutto ciò che ti serve è una libreria standard di Python.


1

ho scritto una funzione che restituisce una tupla di un intervallo di numeri in virgola mobile a precisione doppia senza decimali oltre i centesimi. si trattava semplicemente di analizzare i valori dell'intervallo come stringhe e di dividere l'eccesso. Lo uso per visualizzare intervalli da selezionare all'interno di un'interfaccia utente. Spero che qualcun altro lo trovi utile.

def drange(start,stop,step):
    double_value_range = []
    while start<stop:
        a = str(start)
        a.split('.')[1].split('0')[0]
        start = float(str(a))
        double_value_range.append(start)
        start = start+step
    double_value_range_tuple = tuple(double_value_range)
   #print double_value_range_tuple
    return double_value_range_tuple

1

uso

# Counting up
drange(0, 0.4, 0.1)
[0, 0.1, 0.2, 0.30000000000000004, 0.4]

# Counting down
drange(0, -0.4, -0.1)
[0, -0.1, -0.2, -0.30000000000000004, -0.4]

Per arrotondare ogni passaggio a N decimali

drange(0, 0.4, 0.1, round_decimal_places=4)
[0, 0.1, 0.2, 0.3, 0.4]

drange(0, -0.4, -0.1, round_decimal_places=4)
[0, -0.1, -0.2, -0.3, -0.4]

Codice

def drange(start, end, increment, round_decimal_places=None):
    result = []
    if start < end:
        # Counting up, e.g. 0 to 0.4 in 0.1 increments.
        if increment < 0:
            raise Exception("Error: When counting up, increment must be positive.")
        while start <= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    else:
        # Counting down, e.g. 0 to -0.4 in -0.1 increments.
        if increment > 0:
            raise Exception("Error: When counting down, increment must be negative.")
        while start >= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    return result

Perché scegliere questa risposta?

  • Molte altre risposte si bloccheranno quando verrà chiesto di fare il conto alla rovescia.
  • Molte altre risposte daranno risultati arrotondati in modo errato.
  • Altre risposte basate su np.linspacesono incostante, possono o meno funzionare a causa della difficoltà nella scelta del numero corretto di divisioni.np.linspacelotta davvero con incrementi decimali di 0,1 e l'ordine delle divisioni nella formula per convertire l'incremento in un numero di divisioni può comportare il codice corretto o rotto.
  • Altre risposte basate su np.arangesono obsolete.

In caso di dubbi, prova i quattro casi di test sopra riportati.


0
def Range(*argSequence):
    if len(argSequence) == 3:
        imin = argSequence[0]; imax = argSequence[1]; di = argSequence[2]
        i = imin; iList = []
        while i <= imax:
            iList.append(i)
            i += di
        return iList
    if len(argSequence) == 2:
        return Range(argSequence[0], argSequence[1], 1)
    if len(argSequence) == 1:
        return Range(1, argSequence[0], 1)

Si prega di notare che la prima lettera di Range è maiuscola. Questo metodo di denominazione non è consigliato per le funzioni in Python. Puoi cambiare Range in qualcosa come drange o frange se vuoi. La funzione "Range" si comporta esattamente come lo desideri. Puoi controllare il suo manuale qui [ http://reference.wolfram.com/language/ref/Range.html ].


0

Penso che ci sia una risposta molto semplice che emula davvero tutte le funzionalità di range ma sia per float che per intero. In questa soluzione, supponi che l'approssimazione predefinita sia 1e-7 (o quella che scegli) e puoi cambiarla quando chiami la funzione.

def drange(start,stop=None,jump=1,approx=7): # Approx to 1e-7 by default
  '''
  This function is equivalent to range but for both float and integer
  '''
  if not stop: # If there is no y value: range(x)
      stop= start
      start= 0
  valor= round(start,approx)
  while valor < stop:
      if valor==int(valor):
          yield int(round(valor,approx))
      else:
          yield float(round(valor,approx))
      valor += jump
  for i in drange(12):
      print(i)

0

Naturalmente ci saranno alcuni errori di arrotondamento, quindi questo non è perfetto, ma questo è quello che uso generalmente per le applicazioni, che non richiedono alta precisione. Se si desidera renderlo più accurato, è possibile aggiungere un ulteriore argomento per specificare come gestire gli errori di arrotondamento. Forse il passaggio di una funzione di arrotondamento potrebbe renderlo estensibile e consentire al programmatore di specificare come gestire gli errori di arrotondamento.

arange = lambda start, stop, step: [i + step * i for i in range(int((stop - start) / step))]

Se scrivo:

arange(0, 1, 0.1)

Produrrà:

[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9]

-1

Esiste un intervallo () equivalente per i float in Python? NO Utilizzare questo:

def f_range(start, end, step):
    a = range(int(start/0.01), int(end/0.01), int(step/0.01))
    var = []
    for item in a:
        var.append(item*0.01)
    return var

3
Soluzione piuttosto negativa, prova f_range(0.01,0.02,0.001)... Per la maggior parte degli scopi pratici, arangeda Numpy è una soluzione semplice, sicura e veloce.
Bart,

Hai ragione. Con numpy è 1,8 più veloce del mio codice.
Grigor Kolev,

Hai ragione. Con numpy è 1,8 più veloce del mio codice. Ma il sistema in cui lavoro è completamente chiuso. Solo Python e Pyserial non più.
Grigor Kolev,

-2

Ci sono diverse risposte qui che non gestiscono casi limite semplici come passo negativo, avvio errato, arresto ecc. Ecco la versione che gestisce molti di questi casi dando lo stesso comportamento nativo range():

def frange(start, stop=None, step=1):
  if stop is None:
    start, stop = 0, start
  steps = int((stop-start)/step)
  for i in range(steps):
    yield start
    start += step  

Si noti che questo errore errore step = 0 proprio come nativo range . Una differenza è che l'intervallo nativo restituisce un oggetto che è indicizzabile e reversibile mentre sopra non lo fa.

Puoi giocare con questo codice e testare i casi qui.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.