Julia é uma linguagem de propósito geral, que pode ser usada na maioria das tarefas de programação. Na prática, porém, os principais casos de uso tendem a estar na engenharia e na ciência. Cálculos numéricos rápidos, versáteis e sofisticados estão no centro do seu projeto.
Um número inteiro é um número "redondo", sem ponto decimal.
No conceito Básico, vimos que um valor inteiro pode ser atribuído a uma variável sem especificar um tipo.
Para facilitar a leitura, sublinhados podem ser usados como separador de dígitos. Eles são ignorados pelo compilador.
julia> x = 3
3
julia> typeof(x)
Int64
julia> large_number = 1_234_567_890
1234567890
Internamente, o compilador vai usar o tipo inteiro com sinal mais adequado para a sua CPU.
Em PCs modernos, isso normalmente será Int64, que é perfeitamente adequado para a maioria das tarefas.
Tipos serão discutidos com mais profundidade em um conceito futuro. Por enquanto, os tipos numéricos devem ser intuitivos o bastante para que este documento faça sentido.
Como veremos em um conceito mais adiante, Julia dá suporte nativo a arrays muito grandes e multidimensionais. Eles podem sobrecarregar tanto a quantidade de memória disponível quanto a largura de banda para a transferência de dados.
Para dar mais controle ao desenvolvedor, é possível e bastante comum especificar o tipo inteiro com sinal ou sem sinal desejado. Muitos estudantes de Julia que já usaram Python podem querer comparar isso com o NumPy, em vez do Python básico.
julia> y = Int8(42)
42
julia> typeof(y)
Int8
julia> z = UInt32(1024)
0x00000400
Repare que Julia, por padrão, exibe inteiros sem sinal no formato hexadecimal, porque os tipos sem sinal são muito usados em operações de bits de baixo nível.
Também é possível escrever inteiros em binário, octal ou hexadecimal, com os prefixos 0b, 0o e 0x, respectivamente.
julia> a = 0x10
0x10
julia> Int(a)
16
julia> b = 0b1010 # displays as hexadecimal
0x0a
BigInt
Cada tipo inteiro tem um valor máximo e um mínimo que consegue armazenar:
julia> typemax(Int8)
127
julia> typemin(Int8)
-128
Sair desse intervalo válido causa um estouro de inteiro, com resultados que parecem muito estranhos.
julia> c = Int8(126)
126
julia> c * c
4
Provavelmente podemos concordar que 126 * 126 não deveria ser igual a 4!
Uma explicação detalhada exigiria saber como os registradores da CPU armazenam inteiros, o que está fora do escopo deste conceito.
O problema pode ser minimizado usando um tipo "mais largo", até Int128 ou UInt128 (o significado de "mais largo" é discutido abaixo). No entanto, todos têm limites rígidos antes de esbarrar em problemas de estouro.
Uma solução é usar o tipo BigInt, que é limitado apenas pela memória do seu computador.
É a implementação de Julia da aritmética de precisão arbitrária.
julia> 2 ^ 70
0
julia> big = BigInt(2) ^ 70
1180591620717411303424
julia> UInt128(big)
0x00000000000000400000000000000000
Como 2 elevado a 70 não cabe em um Int64, o primeiro cálculo do exemplo acima falha.
Repare, porém, que não há mensagem de erro.
Atenção, programador!
Uma conversão de tipo de 2 para BigInt dá a resposta correta, e exibi-lo em hexadecimal deixa mais claro que é uma potência de 2.
Não vai ser surpresa que números de ponto flutuante tenham, opcionalmente, um ponto decimal e uma parte fracionária depois do ponto.
julia> f = 3.45
3.45
julia> typeof(f)
Float64
É claro que a notação científica é suportada.
julia> avogadro = 6.02e23
6.02e23
Como acontece com os inteiros, o tipo padrão serve para a maioria dos casos, mas há outros tipos com sinal disponíveis. Não existem tipos de ponto flutuante sem sinal.
Como atalho, valores Float32 podem ser criados usando o sufixo f0.
julia> f32 = 4.56f0
4.56f0
julia> typeof(f32)
Float32
Outros tamanhos de ponto flutuante precisam de uma conversão de tipo explícita, como no caso dos inteiros.
Os valores máximo e mínimo podem surpreender:
julia> typemax(Float64)
Inf
julia> typemin(Float64)
-Inf
O infinito é um valor válido!
No entanto, o intervalo útil dos números de ponto flutuante é limitado: qualquer valor muito grande simplesmente recebe o valor Inf, e qualquer valor muito pequeno é arredondado para 0.0.
Podemos usar outro par de funções para ver esses limites: aproximadamente ± 10 ^ 308.
julia> floatmax(Float64)
1.7976931348623157e308
julia> floatmin(Float64)
2.2250738585072014e-308
Além disso, a precisão é limitada, com um número grande, mas finito, de algarismos significativos representados (aproximadamente 15 para Float64).
Como discutido no conceito Básico, os operadores aritméticos funcionam quase da mesma forma que a aritmética padrão ensinada às crianças.
Repare que a exponenciação usa ^, e não ** (as duas formas são comuns em outras linguagens).
2 + 3 # 5 (addition)
2 - 3 # -1 (subtraction)
2 * 3 # 6 (multiplication)
8 / 2 # 4.0 (division)
8 % 3 # 2 (remainder)
2 ^ 3 # 8 (exponentiation)
No entanto, alguns detalhes específicos de Julia vale a pena discutir.
julia> x = 4.2
4.2
julia> 2 * x
8.4
julia> 2x
8.4
julia> 2.4x
10.08
Isso pode surpreender.
É sempre possível usar * como operador infixo, como na maioria das outras linguagens de programação.
No entanto, Julia foi projetada por pessoas que acreditam que o código deve se parecer o máximo possível com equações matemáticas.
Como os nomes de variáveis precisam começar com uma letra, colocar um número (inteiro ou de ponto flutuante) antes do nome é tratado como multiplicação implícita.
Por exemplo, se quisermos a área da superfície de uma esfera, em vez de 4 * pi * r * r poderíamos fazer isto :
julia> surface(r) = 4π * r^2
surface (generic function with 1 method)
julia> surface(3)
113.09733552923255
Embora π seja uma constante embutida, ela também é uma letra (grega).
Por isso, o parser ainda precisa de um * explícito para separar π de r.
Usar / como operador infixo sempre dá um resultado de ponto flutuante, mesmo com entradas inteiras.
Para a divisão inteira, há mais opções:
julia> 10 / 3
3.3333333333333335
julia> div(10, 3)
3
julia> 10 ÷ 3
3
julia> 10 // 3
10//3
A função div() serve para a divisão inteira, com o resultado truncado em direção ao zero: para baixo, nos números positivos, e para cima, nos negativos.
Como sinônimo, podemos usar o operador infixo ÷, de novo com o objetivo de deixar tudo mais matemático.
Se você estiver usando um editor que reconhece Julia, digite \div e depois aperte a tecla <Tab>.
O operador // vai precisar de um conceito próprio, mais adiante no currículo.
Por enquanto, basta dizer que o resultado de // é um número "racional", o nome formal daquilo que a maioria das pessoas chama de fração.
Fatores comuns serão removidos do numerador e do denominador, para dar uma razão de dois inteiros na chamada "forma irredutível".
julia> rationalnum = 22 // 6
11//3
julia> typeof(rationalnum)
Rational{Int64}
Temos os números racionais. E os números "irracionais"?
julia> π
π = 3.1415926535897...
julia> typeof(π)
Irrational{:π}
Um número irracional é aquele que não pode ser reduzido a uma razão de inteiros.
Exemplos comuns incluem π, e (número de Euler) e muitas raízes, como √2 (a raiz quadrada de 2).
Julia procura fazer matemática do jeito certo.
Isso muitas vezes acontece automaticamente:
julia> x = 2 + 3.5
5.5
julia> typeof(x)
Float64
Somamos um Int64 a um Float64 e obtivemos um resultado Float64.
Na verdade, o inteiro foi convertido silenciosamente para Float64 antes de a soma ser feita.
Julia tem um conceito de "largura" dos tipos numéricos.
Int64 é mais largo que Int16.Se uma mistura de tipos for usada em uma expressão, cada um é "promovido" conforme necessário para o tipo mais largo em uso.
Para forçar a conversão, podemos converter um inteiro para um tipo específico, como em Float64(5).
Como alternativa, basta usar float(5) e deixar o compilador escolher um tipo adequado.
As conversões de ponto flutuante para inteiro são inevitavelmente mais complicadas. O que você quer fazer com o que vem depois do ponto decimal?
round() converte para o número inteiro mais próximo, e, em empates como 4.5, arredonda para o número inteiro par mais próximo.floor() arredonda para baixo, ceil() arredonda para cima e trunc() arredonda em direção ao zero.Int32(), vai falhar com um InexactError.No entanto, por padrão, essas funções não retornam o tipo inteiro que você talvez quisesse. É possível especificar o tipo de saída desejado.
julia> round(4.5)
4.0
julia> round(Int64, 4.5)
4
julia> round(Int, 4.5) # => default integer type
4
julia> ceil(Int16, 4.3)
5
Também é possível arredondar para um número específico de dígitos depois do ponto decimal com a palavra-chave digits.
julia> round(π, digits=10)
3.1415926536
Consulte o manual para mais detalhes.
Com certeza isso só gera um erro, não é? Na verdade, a situação não é tão simples assim.
A divisão inteira com ÷ ou // resulta em um erro, como você provavelmente espera.
A divisão de ponto flutuante com / adota o que poderia ser considerado uma abordagem de engenharia, em vez da abordagem padrão da ciência da computação:
julia> 2 / 0
Inf
julia> 0 / 0
NaN
Como discutido em uma seção anterior, o infinito é um número de ponto flutuante válido em Julia, representado por Inf.
Quando o numerador também é zero, o resultado é matematicamente indefinido.
Julia então o trata como "não é um número", representado por NaN.
Se isso parecer estranho, pense no contexto de trabalhar com arrays grandes de dados do mundo real (e, portanto, muitas vezes bastante bagunçados). Para seguir em frente, o melhor é apenas sinalizar os valores problemáticos e continuar.
Verificar manualmente os valores sem parar seria tedioso de programar e certamente prejudicaria o desempenho em tempo de execução.
Parar com uma mensagem de erro a cada pequeno problema deixaria o seu programa muito impopular entre os usuários!
Como descrito no conceito Conditionals, o teste de igualdade geralmente é feito com o operador ==.
Isso funciona bem para inteiros, caracteres, strings etc.
No entanto, os valores de ponto flutuante têm precisão limitada, e maneiras diferentes de calcular o mesmo resultado podem levar a valores ligeiramente diferentes.
Para Float64, isso normalmente ocorre por volta do 15º algarismo significativo: uma diferença pequena, mas que não é "igualdade".
Tradicionalmente, o conselho aos programadores é nunca usar == com valores de ponto flutuante: os resultados são imprevisíveis.
Uma alternativa amplamente usada é testar o valor absoluto da diferença contra alguma tolerância permitida (muitas vezes chamada de epsilon ou ϵ).
Então, em vez de a == b, use abs(a - b) < epsilon.
Julia oferece uma alternativa mais limpa com a função isapprox().
Para uma tolerância absoluta, a sintaxe é isapprox(a, b, atol=epsilon).
Nesse caso, a palavra-chave atol é obrigatória.
A tolerância relativa costuma ser mais útil.
Ela é o padrão, então isapprox(a, b) vai tentar escolher um valor razoável para rtol (as regras são bastante complicadas).
Também é possível especificar uma tolerância relativa, como uma fração dos valores comparados.
Assim, isapprox(a, b, rtol=0.01 testa se os valores estão dentro de 1% um do outro.
Ao estilo habitual de Julia, existe um operador matemático sinônimo do caso padrão: a ≈ b (com o operador digitado como \approx e depois <tab>).
Além dos números racionais, partes posteriores do currículo vão discutir:
2.3 + 4.5im.