Aritmética

    Aritmética é sobre as coisas que você aprende a fazer com números na escola primária: Adição, Subtração, Multiplicação, Divisão e assim por diante. É canja. Todos nós sabemos essas coisas. No entanto, queremos gastar uma seção inteira neste tópico, porque quando se trata de computadores, o diabo está nos detalhes.

    Como introduzido em Primitives tipos numéricos em Pony são representados como um tipo especial de primitivo que mapeia as palavras das máquinas. Tanto os tipos inteiros quanto os tipos de ponto flutuante suportam um rico conjunto de operações aritméticas e de nível de bits. Estes são expressos como Operadores Infix que são implementados como funções simples nos tipos primitivos numéricos.

    O Pony concentra-se em dois objetivos, desempenho e segurança. De tempos em tempos, estes dois objetivos colidem. Isto é verdade especialmente para a aritmética sobre números inteiros e números de ponto flutuante. O código de segurança deve verificar se há transbordo, divisão por zero e outras condições de erro em cada operação onde isso pode acontecer.

    O Pony tenta impor o maior número possível de invariantes de segurança em tempo de compilação, mas a verificação das operações aritméticas só pode acontecer em tempo de execução. O código do executante deve executar a aritmética inteira o mais rápido e com o menor número possível de ciclos de CPU. A verificação de transbordamento é cara, fazer uma aritmética simples e perigosa que possivelmente esteja sujeita a transbordamento é barato.
      O Pony fornece diferentes maneiras de fazer aritmética para dar aos programadores a liberdade de escolher a operação que melhor lhes convém, a operação segura mas lenta ou a rápida, porque o desempenho é crucial para o caso de uso.

    Inteiros


    Aritmética padrão de Ponys

      Fazendo aritmética sobre tipos inteiros em Pony com os operadores bem conhecidos como +, -, *, / etc., tenta equilibrar as necessidades de desempenho e correção. Todas as operações aritméticas padrão não expõem nenhum comportamento indefinido ou condições de erro. Isso significa que ele trata tanto os casos de overflow/underflow quanto a divisão por zero.
      O transbordo/ subfluxo são tratados com um envoltório adequado em torno da semântica, usando a complementação de cada um em inteiros assinados. A esse respeito, obtemos comportamentos como:

    // unsigned wrap-around em overflow
    U32.max_value() + 1 == 0
    
    // signed wrap-around em overflow/underflow
    I32.min_value() - 1 == I32.max_value()

      A divisão por zero é um caso especial, que afeta a divisão / e os demais % operadores. Em Matemática, a divisão por zero é indefinida. A fim de evitar ou definir divisão como parcial, lançar um erro na divisão por zero ou introduzir comportamento indefinido para esse caso, a divisão normal é definida como 0 quando o divisor é 0. Isso pode levar a erros silenciosos, quando usado sem cuidado. Escolher Parcial e verificar Aritmética para detectar a divisão por zero. Em contraste com a Aritmética Insegura, a aritmética padrão vem com uma pequena sobrecarga de tempo de execução porque, ao contrário das variantes inseguras, ela detecta e manipula o transbordo e a divisão por zero.

    Operador MétodoDescrição
    + add()envolver em over-/underflow
    - sub()envolver em over-/underflow
    * mul()envolver em over-/underflow
    / div()x/0=0
    % rem()x%0=0
    %% mod()x%%0=0
    - neg()envolver em over-/underflow
    >> shr()preenchido com zeros,então x>>1=x/2 é verdade
    << shl()preenchido com zeros,então x<<1=x*2 é verdade

    Aritmética insegura

      A aritmética inteira insegura chega perto do que se pode esperar da aritmética inteira em C. Sem verificações, velocidade bruta, possibilidades de transbordo, subfluxo ou divisão por zero. Como em C, o estouro, o subfluxo e a divisão por zero cenários são indefinidos. Não confie nos resultados nestes casos. Pode ser qualquer coisa e é altamente específico da plataforma. A divisão por zero pode até mesmo travar seu programa com um SIGFPE. Nossa sugestão é usar estes operadores somente se você puder ter certeza de que pode excluir estes casos.
        Aqui está uma lista com todas as operações inseguras definidas nos Inteiros:

      Operador MétodoDescrição
      +~ add_unsafe()overflow
      -~ sub_unsafe()overflow
      *~ mul_unsafe()overflow
      /~ div_unsafe()divisão por zero e overflow
      %~ rem_unsafe()divisão por zero e overflow
      %%~ mod_unsafe()divisão por zero e overflow
      -~ neg_unsafe()overflow
      >>~ shr_unsafe()Se bits diferentes de zero são deslocados para fora
      <<~ shl_unsafe()Se os bits que diferem do bit de sinal final são deslocados para fora

      Conversão insegura

        A conversão entre os tipos inteiros no Pony precisa acontecer explicitamente. Cada tipo numérico pode ser convertido explicitamente em qualquer outro tipo.

      // convertendo um I32 para um ponto flutuante de 32 bits
      I32(12).f32()

        Para cada operação de conversão existe uma contrapartida não segura, que é muito mais rápida na conversão de e para números de ponto flutuante. Todas estas conversões inseguras entre tipos numéricos são indefinidas se o tipo alvo for menor que o tipo fonte, por exemplo, se convertermos de I64 para F32.

      // convertendo um I32 para um ponto flutuante de 32 bits, caminho inseguro
      I32(12).f32_unsafe()
      
      // um exemplo para conversão insegura não definida
      I64.max_value().f32_unsafe()
      
      // um exemplo para conversão insegura não definida, que atualmente é segura
      I64(1).u8_unsafe()

        Aqui está uma lista completa de todas as conversões disponíveis para os tipos numéricos:

      Conversão seguraConversão insegura
      u8()u8_unsafe()
      u16()u16_unsafe()
      u32()u32_unsafe()
      u64()u64_unsafe()
      u128()u128_unsafe()
      ulong()ulong_unsafe()
      usize()usize_unsafe()
      i8()i8_unsafe()
      i16()i16_unsafe()
      i32()i32_unsafe()
      i64()i64_unsafe()
      i128()i128_unsafe()
      ilong()ilong_unsafe()
      isize()isize_unsafe()
      f32()f32_unsafe()
      f64()f64_unsafe()

      Aritmética Parcial e Verificada

        Se o transbordo ou divisão por zero são casos que precisam ser evitados e o desempenho não é prioridade crítica, a aritmética parcial ou verificada oferece grande segurança durante o tempo de execução. Erro de operadores aritméticos parciais em overflow/underflow e divisão por zero. Os métodos aritméticos verificados devolvem um trecho do resultado da operação e um Booleano indicando transbordamento ou outro comportamento excepcional.

      // aritimética parcial
      let result =
        try
          USize.max_value() +? env.args.size()
        else
          env.out.print("overflow detectedo!")
        end
      
      // checagem de aritmetica
      let result =
        match USize.max_value().addc(env.args.size())
        | (let result: USize, false) =>
          // use result
          ...
        | (_, true) =>
          env.out.print("overflow detectado!")
        end

        A aritmética parcial, bem como a verificada, vem com o fardo de tratar exceções em cada caso e incorre em alguma sobrecarga de desempenho, seja avisado.

      Operador ParcialMétodoDescrição
      +? add_partial()erros em over-/underflow
      -? sub_partial()erros em over-/underflow
      *? mul_partial()erros em over-/underflow
      /? div_partial()erros em over-/underflow e divisão por zero
      %? rem_partial()erros em over-/underflow e divisão por zero
      %%? mod_partial()erros em over-/underflow e divisão por zero

        Todas as funções aritméticas verificadas retornam o resultado da operação e uma bandeira booleana indicando transbordo/ subfluxo ou divisão por zero em um tuple.


      Método ChecadoDescrição
      addc()Adição marcada, o segundo elemento da tupla é verdadeiro em overflow / underflow
      subc()Subtração verificada, o segundo elemento da tupla é verdadeiro em overflow / underflow
      mulc()Multiplicação verificada, o segundo elemento da tupla é verdadeiro no overflow
      divc()Divisão marcada, o segundo elemento da tupla é verdadeiro em caso de overflow ou divisão por zero
      remc()Restante verificado, o segundo elemento da tupla é verdadeiro em caso de overflow ou divisão por zero
      modc()Módulo verificado, o segundo elemento da tupla é verdadeiro em caso de overflow ou divisão por zero
      fldc()Divisão de piso marcada, o segundo elemento tipo é verdadeiro em overflow ou divisão por zero

      Ponto Flutuante

        A aritmética padrão do pônei sobre números de ponto flutuante (F32, F64) comporta-se como definido na norma de ponto flutuante IEEE 754. Isso significa, por exemplo, que a divisão por +0 retorna Inf e por -0 retorna -Inf.


      Aritmética insegura

        As operações de Ponto Flutuante Inseguro não estão necessariamente de acordo com IEEE 754 para cada entrada ou cada resultado. Se qualquer argumento para uma operação insegura ou seu resultado for +/-Inf ou NaN, o resultado é na verdade indefinido.
        Isto permite otimizações mais agressivas e para uma execução mais rápida, mas só produz resultados válidos para valores diferentes dos valores excepcionais +/-Inf e NaN. Sugerimos que só se utilize aritmética insegura em flutuadores se você puder excluir esses casos.

      OperadorMétodo
      +~add_unsafe()
      -~sub_unsafe()
      *~mul_unsafe()
      /~div_unsafe()
      %~rem_unsafe()
      %%~mod_unsafe()
      -~neg_unsafe()
      <~lt_unsafe()
      >~gt_unsafe()
      <=~le_unsafe()
      >=~ge_unsafe()
      =~eq_unsafe()
      !=~ne_unsafe()

        Além disso, sqrt_unsafe() é indefinido para valores negativos.