Numpy: round() retorna ponto flutuante, não int, para alguns floats numpy quando não há segundo argumento

Criado em 23 ago. 2018  ·  14Comentários  ·  Fonte: numpy/numpy

A semântica de round() mudou no Python 3:

round(número[, ndígitos])
Número de retorno arredondado para precisão de n dígitos após o ponto decimal. Se ndigits for omitido ou for None, ele retornará o inteiro mais próximo de sua entrada.

Isso funciona incorretamente no caso de np.float64, que retorna um float. Acredito que o método __round__ esteja chamando __rint__, que deve retornar um inteiro, mas não retorna.

Exemplo de código de reprodução:

import numpy as np
In [50]: round(1.0)
Out[50]: 1

In [51]: round(float(1.0))
Out[51]: 1

In [52]: round(np.float(1.0))
Out[52]: 1

In [53]: round(np.float64(1.0))
Out[53]: 1.0

Esse comportamento é o mesmo para float16, float32 e float128.

Comentários muito úteis

Outro pensamento sobre este problema: como isinstance(np.float64(1), float) é verdadeiro, a implementação atual quebra o princípio de substituição de Liskov, tornando o uso de escalares numpy muito un SOLID .

O problema é que há muitos caminhos (às vezes inesperados) nos quais os valores de numpy.float64 se infiltram no código existente, o que torna o teste de unidade e a manutenção desnecessariamente complicados.

Todos 14 comentários

que tipo está sendo retornado? np.float é o mesmo que float , np.float64 retorna um escalar numpy:

>>> type(np.float(1.0))
<class 'float'>
>>> type(np.float64(1.0))
<class 'numpy.float64'>

Seus exemplos com np.float não estão usando numpy. A chamada para round(np.float64(1)) na verdade vai para np.round , a documentação afirma: (na verdade documentado em np.around ) "retorna um array do mesmo tipo)" então se você verificar o type de Out[53] você verá que é um np.float64

que tipo está sendo retornado? np.float é o mesmo que float, np.float64 retorna um escalar numpy:

type(np.float(1.0))

type(np.float64(1.0))

Seus exemplos com np.float não estão usando numpy. A chamada para round(np.float64(1)) na verdade vai para np.round, a documentação afirma: (na verdade documentado em np.around) "retorna um array do mesmo tipo)" então se você verificar o tipo de Out[ 53] você verá que é um np.float64

Você está explicando o que o código faz. Eu tenho que concordar: sim, é isso que ele faz.

A rodada NumPy aplicada a floats numpy não retorna inteiros. É um recurso, não um bug. O comportamento do Python que você ilustra é novo no Python 3.

Só para elaborar um pouco mais: o problema é com números muito grandes; em python, pode-se retornar um inteiro longo, mas em numpy não podemos (para o caso geral de arrays). No entanto, discutimos se isso deveria mudar pelo menos para __round__ , ou seja, se alguém fizer round(array) do python.

A rodada NumPy aplicada a floats numpy não retorna inteiros. É um recurso, não um bug.

@charris : Acho que não estamos falando de np.round aqui, mas do outro round . Eu consideraria não cumprir a API de round um bug, mas suspeito que já tenha sido relatado em outro lugar no github.

Eu entendo a situação: round() do Python delega a responsabilidade para np.__round__ , que por sua vez chama np.round() , que não obedece à semântica do round() do Python. E não há razão para isso, especialmente porque (como você diz) o comportamento round é novo com o Python 3. Seria bom se np.__round__ verificasse seu segundo argumento e chamasse np.rint quando é zero, então está em conformidade com a nova semântica do Python round , mas posso entender se há razões para você não querer fazer isso.

É apenas confuso ter código como:
mylist = [0] * round(x + y)
que funciona na maioria das vezes, mas dá uma mensagem confusa quando x ou y é retirado de uma estrutura numpy. Mas os tipos de dados do numpy não são os do Python, e aí estamos.

Cumprimentos,
-Tom

Depois de descartarmos o Python 2.7, podemos querer dar uma segunda olhada nisso. No entanto, a compatibilidade com versões anteriores é sempre uma consideração. Embora neste caso eu espere que as pessoas queiram um número inteiro, especialmente para indexação. Houve uma discussão semelhante sobre ceil e floor . Uma opção seria criar novas funções, iround , iceil e ifloor , embora decidir o tipo de retorno possa ser problemático com np.intp ou np.int64 sendo possibilidades .

Estou corrigido --- np.rint retorna um valor inteiro arredondado do tipo passado, então chamá-lo não corrigiria nada. Talvez, já que a função round do Python mudou sua semântica, deveria ser responsabilidade de round fazer qualquer conversão necessária para garantir essa semântica. Ah bem.

De PEP3141 :

<strong i="7">@abstractmethod</strong>
    def __round__(self, ndigits:Integral=None):
        """Rounds self to ndigits decimal places, defaulting to 0.

        If ndigits is omitted or None, returns an Integral,
        otherwise returns a Real, preferably of the same type as
        self. Types may choose which direction to round half. For
        example, float rounds half toward even.

        """
        raise NotImplementedError

Um teste rápido (Python 3.7.0) mostra:

>>> import numpy as np; print(np.__version__)
1.15.2
>>> 1e+24.__round__(None)
999999999999999983222784
>>> 1e+24.__round__(0)
1e+24
>>> 1e+24.__round__()
999999999999999983222784
>>> np.float64(1e24).__round__(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: an integer is required (got type NoneType)
>>> np.float64(1e24).__round__(0)
1e+24
>>> np.float64(1e24).__round__()
1e+24

Se entendi certo: a implementação atual __round__ não é compatível com PEP3141, já que np.float64.__round__ não permite NoneType para o argumento ndigits e padroniza seu valor para 0 e não None quando chamado sem argumentos.

Eu acho que seria sensato aderir imediatamente à assinatura de chamada PEP3141. Quando np.float64.__round__ é chamado com ndigits=None , sugiro alertar o usuário que o resultado não é compatível com Python 3, por qualquer

  • levantando NotImplementedError (opção difícil)
  • levantando UserWarning (opção suave).

EDITAR:

Acabei de notar que isso já foi discutido em #11557, #5700, #3511. Desculpe por adicionar ruído à discussão, mas sinto que uma referência ao PEP3141 é importante.

Outro pensamento sobre este problema: como isinstance(np.float64(1), float) é verdadeiro, a implementação atual quebra o princípio de substituição de Liskov, tornando o uso de escalares numpy muito un SOLID .

O problema é que há muitos caminhos (às vezes inesperados) nos quais os valores de numpy.float64 se infiltram no código existente, o que torna o teste de unidade e a manutenção desnecessariamente complicados.

Eu também encontrei esse problema. Pelo menos uma vez que meu próprio código quebrou desde round(np.int32 / float) == np.float64 que não pode ser usado para dimensões de matriz/etc.

Eu também encontrei esse problema. Pelo menos uma vez que meu próprio código quebrou desde round(np.int32 / float) == np.float64 que não pode ser usado para dimensões de matriz/etc.

Solução alternativa para mim:

width = round(float(np.sqrt(x)))

Oi Daniel :). A solução alternativa é boa, fechando o problema, pois agora retornará um inteiro python para a versão NumPy 1.19 e posterior (corrigido em gh-15840).

Talvez eu devesse puxar com pip então. :sorrindo:

Esta página foi útil?
0 / 5 - 0 avaliações