Pular para o conteúdo

    Capítulo 7, Básico

    Tipos de dados: dtype

    Todo elemento de um array tem o mesmo tipo, e esse tipo decide a memória, a precisão e o que acontece quando um número não cabe. Aqui moram as surpresas mais caras do NumPy.

    Código deste capítulo: básico/cap07_tipos_dtype.py

    Os tipos mais comuns

    O NumPy descobre o tipo a partir dos valores, mas você pode (e muitas vezes deve) escolher:

    básico/cap07_tipos_dtype.pylinhas 10 a 15
    import numpy as np
    
    print(np.array([1, 2, 3]).dtype)
    print(np.array([1.0, 2, 3]).dtype)
    print(np.array([True, False]).dtype)
    print(np.array([1 + 2j]).dtype)
    
    Saída
    int64
    float64
    bool
    complex128
    
    TipoBytesFaixa ou uso
    int8, int16, int32, int641, 2, 4, 8Inteiros com sinal. O int64 é o padrão
    uint8, uint16, uint32, uint641, 2, 4, 8Inteiros sem sinal (o uint8 vai de 0 a 255, como os pixels)
    float32, float644, 8Decimais. O float64 é o padrão
    bool1Verdadeiro ou falso
    complex12816Números complexos

    Converter com astype

    O astype cria um array novo com outro tipo. A conversão de decimal para inteiro trunca (descarta a parte decimal), em vez de arredondar:

    básico/cap07_tipos_dtype.pylinhas 20 a 22
    notas = np.array([7.9, 8.5, 9.99])
    print(notas.astype(int))
    print(np.round(notas).astype(int))
    
    Saída
    [7 8 9]
    [ 8  8 10]
    

    O np.round usa o arredondamento "para o par mais próximo" quando o valor está exatamente no meio: o 8.5 virou 8. É o mesmo comportamento do round do Python.

    Quando o número não cabe

    Cada tipo tem uma faixa fixa. Dentro de uma conta, passar do limite dá a volta (wraparound), sem erro e sem aviso. Um uint8 que chega a 255 e recebe mais 10 volta ao começo:

    básico/cap07_tipos_dtype.pylinhas 27 a 28
    pequeno = np.array([250, 5], dtype=np.uint8)
    print(pequeno + 10)
    
    Saída
    [ 4 15]
    

    O 250 + 10 deu 4, e não 260. É um erro silencioso, e por isso um dos mais perigosos: nenhum aviso, e o resultado parece plausível. Já na criação, o NumPy 2 protege você quando o valor é impossível:

    básico/cap07_tipos_dtype.pylinhas 30 a 33
    try:
        np.array([300], dtype=np.uint8)
    except OverflowError as erro:
        print("OverflowError:", erro)
    
    Saída
    OverflowError: Python integer 300 out of bounds for uint8
    

    Precisão de decimais

    Os decimais binários não representam a maioria das frações exatamente, e o float32 tem ainda menos precisão do que o float64. A comparação por igualdade é uma armadilha, e a comparação com tolerância é o remédio:

    básico/cap07_tipos_dtype.pylinhas 38 a 40
    print(0.1 + 0.2 == 0.3, np.isclose(0.1 + 0.2, 0.3))
    print(np.finfo(np.float64).eps)
    print(np.float32(16_777_216) + np.float32(1))
    
    Saída
    False True
    2.220446049250313e-16
    1.6777216e+07
    

    O eps é a menor diferença que o float64 distingue perto de 1. E o último resultado (1.6777216e+07, ou seja, 16.777.216) mostra o limite do float32: a partir de 2²⁴ ele não consegue mais representar todos os inteiros, e somar 1 a 16.777.216 devolve o mesmo número.

    O que acontece quando tipos diferentes se encontram

    Quando você combina tipos em uma conta, o NumPy decide o tipo do resultado por regras de promoção. O NumPy 2 mudou uma regra importante (a NEP 50): um número Python puro ("escalar fraco") não promove o tipo do array, enquanto um escalar NumPy promove:

    básico/cap07_tipos_dtype.pylinhas 45 a 49
    a = np.array([1, 2, 3], dtype=np.int8)
    print((a + 1).dtype)
    print((a + np.int64(1)).dtype)
    print((np.float32(3) + 3.0).dtype)
    print(np.result_type(np.int8, np.float32), np.result_type(np.int64, np.float32))
    
    Saída
    int8
    int64
    float32
    float32 float64
    

    O a + 1 continua int8, porque o 1 é um inteiro Python e "se adapta" ao array. Mas o a + np.int64(1) virou int64. E o np.result_type responde, sem fazer conta, qual seria o tipo do resultado.

    Dados misturados e tipos pequenos

    Duas fontes de bug silencioso: usar tipos pequenos (uint8, int16) em contas que passam da faixa, e misturar números com texto em np.array, que converte tudo para texto (capítulo 4). Quando um resultado parece estranho, imprima o dtype primeiro.

    Exercício 1

    Economizar memória com o menor tipo possível

    Escreva economizar(idades) que converta um array de idades (inteiros positivos) para o menor tipo sem sinal que comporta o maior valor, usando np.min_scalar_type.