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étodo | Descriçã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étodo | Descriçã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 segura | Conversã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 Parcial | Método | Descriçã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 Checado | Descriçã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.
Operador | Mé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.