Introdução
Bem-vindos Gophers a este novo capítulo. Nesta seção, aprenderemos sobre tipos numéricos. O conteúdo inclui tipos inteiros comumente usados, tipos de ponto flutuante, tipos booleanos, bem como números complexos e a sintaxe de valor literal introduzida na versão 1.13.
Pontos de Conhecimento:
- Tipos inteiros
- Tipos de ponto flutuante (floating-point types)
- Tipos booleanos
- Números complexos
- Sintaxe de valor literal (literal value syntax)
Inteiros
Os inteiros podem ser amplamente divididos em duas categorias: inteiros sem sinal (unsigned integers) e inteiros com sinal (signed integers). Os inteiros com sinal são os mais amplamente utilizados.
Sem sinal (unsigned) significa que pode representar apenas números não negativos (0 e números positivos), enquanto os números com sinal (signed) podem representar números negativos e não negativos.
Os inteiros sem sinal podem ser divididos em quatro tamanhos: 8 bits, 16 bits, 32 bits e 64 bits, representados por uint8, uint16, uint32 e uint64, respectivamente. Os inteiros com sinal correspondentes são int8, int16, int32 e int64. A tabela a seguir mostra as diferentes faixas representadas por cada tipo:
| Tipo | Descrição | Faixa |
|---|---|---|
| uint8 | Inteiro sem sinal de 8 bits | 0 a 255 |
| int8 | Inteiro com sinal de 8 bits | -128 a 127 |
| uint16 | Inteiro sem sinal de 16 bits | 0 a 65535 |
| int16 | Inteiro com sinal de 16 bits | -32768 a 32767 |
| uint32 | Inteiro sem sinal de 32 bits | 0 a 4294967295 |
| int32 | Inteiro com sinal de 32 bits | -2147483648 a 2147483647 |
| uint64 | Inteiro sem sinal de 64 bits | 0 a 18446744073709551615 |
| int64 | Inteiro com sinal de 64 bits | -9223372036854775808 a 9223372036854775807 |
Vamos tomar uint8 e int8 como exemplos. Ambos são inteiros de 8 bits e podem representar 256 valores. No tipo de inteiro sem sinal uint8, a faixa que ele pode representar é de 0 a 255, enquanto no tipo de inteiro com sinal int8, a faixa que ele pode representar é de -128 a 127.
Além dos 8 tipos acima, existem três outros tipos de inteiros especiais, uint, int e uintptr, onde uint e int podem representar diferentes faixas em diferentes plataformas, e uintptr é usado para armazenar endereços de ponteiros.
| Tipo | Faixa |
|---|---|
uint |
uint32 em sistemas de 32 bits, uint64 em sistemas de 64 bits |
int |
int32 em sistemas de 32 bits, int64 em sistemas de 64 bits |
uintptr |
Tipo de inteiro sem sinal usado para armazenar endereços de ponteiros, usado principalmente em programação de baixo nível, como operações inseguras |
Agora, vamos criar um arquivo chamado integer.go para demonstrar o uso de inteiros:
cd ~/project
touch integer.go
package main
import (
"fmt"
"unsafe"
)
func main() {
// View the type of int in the current environment
// Declare a as the type int
var a int
// Use unsafe.Sizeof() to output the memory size occupied by the type
fmt.Printf("The type int in the current environment is %d bits\n", unsafe.Sizeof(a)*8)
var b int8 = 125
// Use the %d placeholder in fmt.Printf to output the value of the integer
// Use the %T placeholder in fmt.Printf to output the type of the variable
fmt.Printf("The value of b is %d, and the type is %T\n", b, b)
// Integer operations
// Declare integers c and d, and calculate their sum
c, d := 2, 3
fmt.Printf("c + d = %d\n", c+d)
// 10 - 5
fmt.Printf("10 - 5 = %d\n", 10-5)
// 8 * 10
fmt.Printf("8 * 10 = %d\n", 8*10)
// Example of uintptr usage - storing an address
var ptr uintptr
x := 42
// Convert the pointer to x to uintptr
ptr = uintptr(unsafe.Pointer(&x))
fmt.Printf("The address of x stored in ptr is: %v\n", ptr)
}
Após executar o programa, obtemos a seguinte saída:
go run integer.go
The type int in the current environment is 64 bits
The value of b is 125, and the type is int8
c + d = 5
10 - 5 = 5
8 * 10 = 80
The address of x stored in ptr is: 824634818784
Explicação da saída:
The type int in the current environment is 64 bits: Esta linha mostra que o tipointno sistema onde o código está sendo executado é de 64 bits (ou 8 bytes), indicando que é umint64.unsafe.Sizeof(a)retorna o tamanho da variávelaem bytes, e multiplicar por 8 converte para bits. Isso significa que o tipointpode armazenar valores inteiros maiores.The value of b is 125, and the type is int8: Aqui, declaramos uma variávelbdo tipoint8e atribuímos o valor 125. A saída confirma isso mostrando tanto o valor quanto o tipo de dados.c + d = 5,10 - 5 = 5,8 * 10 = 80: Estas linhas mostram operações aritméticas básicas com inteiros: adição, subtração e multiplicação. A saída confirma os resultados corretos dessas operações.The address of x stored in ptr is: 824634818784: Isso demonstra comouintptrpode ser usado para armazenar um endereço de memória. Estamos convertendo um ponteiro para a variável inteiraxpara um tipouintptr. O valor real do endereço variará cada vez que o programa for executado. Este é um caso de uso avançado, normalmente encontrado em operações inseguras e programação de sistemas.
Neste arquivo, a função unsafe.Sizeof() pode ser usada para obter o número de bytes ocupados pelo tipo de variável atual. 1 byte (byte) é igual a 8 bits, então unsafe.Sizeof()*8 pode obter o número de bits ocupados pelo tipo. Pela saída, podemos ver que o ambiente online é de 64 bits. O tipo real de int no ambiente online é int64.
Podemos usar o seguinte comando no terminal para determinar a arquitetura do sistema atual:
dpkg --print-architecture
amd64
Números de Ponto Flutuante
Os números de ponto flutuante representam números reais com partes fracionárias em Go usando dois tipos: float32 e float64. O tipo de ponto flutuante padrão é float64.
float32 e float64 representam diferentes precisões. A precisão padrão de float64 é maior do que a de float32.
O padrão IEEE 754 é o padrão de cálculo de ponto flutuante mais amplamente usado em computadores, e as CPUs modernas suportam esse padrão. Como outras linguagens de programação, Go também usa IEEE 754 para armazenar números de ponto flutuante.
Sabemos que um byte em um computador pode armazenar 8 bits. float32 é um número de ponto flutuante de precisão simples e ocupa 4 bytes, que são 32 bits. float64 é de precisão dupla e ocupa 8 bytes, que são 64 bits.
Em float32, o bit de sinal ocupa 1 bit, o expoente ocupa 8 bits e os 23 bits restantes são usados para representar a mantissa.
Em float64, o bit de sinal também ocupa 1 bit, o expoente ocupa 11 bits e os 52 bits restantes são usados para representar a mantissa.
O valor máximo que float32 pode representar é aproximadamente 3.4e+38 em notação científica, e o valor mínimo é 1.4e-45. O valor máximo que float64 pode representar é aproximadamente 1.8e+308, e o valor mínimo é 4.9e-324. Podemos ver que a faixa de valores de ponto flutuante pode ser de muito pequeno a muito grande.
Podemos usar a constante math.MaxFloat32 para representar o valor máximo em float32. Use a constante math.MaxFloat64 para representar o valor máximo em float64.
Representação de Números de Ponto Flutuante
Ao exibir números de ponto flutuante, podemos usar o marcador %f da função Printf do pacote fmt. Aqui está um exemplo:
cd ~/project
touch float.go
package main
import (
"fmt"
"math"
)
func main() {
// Output without exponential form
fmt.Printf("2.333 without exponential form: %f\n", 2.333)
fmt.Printf("Pi without exponential form: %f\n", math.Pi)
// Use %.2f to keep two decimal places for Pi
fmt.Printf("Pi with two decimal places: %.2f\n", math.Pi)
fmt.Printf("The maximum value of float32: %f\n", math.MaxFloat32)
// Exponential form
fmt.Printf("2.333 in exponential form: %e", 2.333)
}
Execute o comando e você verá a seguinte saída.
go run float.go
2.333 without exponential form: 2.333000
Pi without exponential form: 3.141593
Pi with two decimal places: 3.14
The maximum value of float32: 340282346638528859811704183484516925440.000000
2.333 in exponential form: 2.333000e+00
Explicação da saída:
2.333 without exponential form: 2.333000: Isso mostra o número2.333impresso usando o marcador%f. Por padrão, o marcador%fmostra 6 dígitos após a vírgula decimal, então2.333se torna2.333000.Pi without exponential form: 3.141593: Esta linha imprime o valor da constantemath.Pi, que é uma aproximação da constante matemática π. O marcador%fo exibe com precisão total.Pi with two decimal places: 3.14: Usando%.2f, dizemos afmt.Printfpara formatar o número com duas casas decimais, o que resulta em3.14. Isso é muito útil quando você só precisa de uma certa precisão para a saída.The maximum value of float32: 340282346638528859811704183484516925440.000000: Isso mostra o valor máximo que umfloat32pode representar. Observe que é um número muito grande e, quando impresso com%f, será representado com muitos dígitos.2.333 in exponential form: 2.333000e+00: Esta linha mostra como representar um número de ponto flutuante em notação exponencial (científica) usando o marcador%e. Oe+00no final indica que multiplicamos2.333000por 10 elevado à potência de 0, que é simplesmente2.333. Se o expoente fossee+02, então o número seria multiplicado por10^2(100), resultando em233.3.
Tipos Booleanos
O tipo bool tem apenas dois valores possíveis: true ou false, sendo false o padrão. Ele tem as seguintes características:
- Não pode ser convertido para outros tipos, como converter um inteiro em um booleano ou converter um booleano em um inteiro.
- Não pode participar de operações aritméticas.
Tipos booleanos são geralmente usados em conjunto com operadores relacionais, como =, >, e <. Vamos criar um arquivo chamado bool.go para ver uma demonstração:
cd ~/project
touch bool.go
package main
import (
"fmt"
)
func main() {
// Use the %t placeholder in fmt.Printf to represent a boolean value
fmt.Printf("Is 3 equal to 2? %t\n", 3 == 2)
fmt.Printf("Is 2 equal to 2? %t\n", 2 == 2)
// Determine whether a and b are equal
a, b := 1, 2
fmt.Printf("a is %d, b is %d\n", a, b)
if a > b {
fmt.Println("a is greater than b")
} else if a == b {
fmt.Println("a is equal to b")
} else {
fmt.Println("b is greater than a")
}
}
Após executar o programa, obtemos a seguinte saída:
go run bool.go
Is 3 equal to 2? false
Is 2 equal to 2? true
a is 1, b is 2
b is greater than a
Explicação da saída:
Is 3 equal to 2? false: A expressão3 == 2avalia parafalse, que é então impresso usando o marcador%t.Is 2 equal to 2? true: A expressão2 == 2avalia paratrue, que é então impresso usando o marcador%t.a is 1, b is 2: Esta linha imprime os valores atribuídos às variáveis inteirasaeb.b is greater than a: A instrução condicionalif-else if-elseavalia paraa < b, que é1 < 2(verdadeiro), portanto, a instruçãoelseé executada, resultando no programa imprimindo "b is greater than a".
Neste programa, primeiro usamos %t em fmt.Printf para representar um valor booleano e, em seguida, usamos a instrução if para determinar a relação entre dois valores. Em fmt.Printf, podemos usar o marcador %d para representar um inteiro, o marcador %f para representar um número de ponto flutuante e o marcador %t para representar um valor booleano.
Números Complexos
Go também possui tipos de números complexos embutidos, que podem ser divididos em tipos complex64 e complex128. Em complex64, tanto a parte real quanto a imaginária são de 32 bits, enquanto em complex128, ambas as partes real e imaginária são de 64 bits. Podemos facilmente realizar operações com números complexos em Go.
Crie um arquivo chamado complex.go e insira o seguinte código:
cd ~/project
touch complex.go
package main
import (
"fmt"
)
func main() {
// Initialize complex numbers in different ways
c1 := complex(3, 1)
c2 := 4 + 5i
// Complex number operations
c3 := c1 + c2
c4 := c1 * c2
// Use the real() function to obtain the real part of a complex number, and the imag() function to obtain the imaginary part of a complex number
fmt.Printf("The real part of c1 is %v, the imaginary part is %v\n", real(c1), imag(c1))
// %v in fmt.Printf can be used to represent complex numbers
fmt.Printf("c1 + c2 is %v\n", c3)
fmt.Printf("c1 * c2 is %v\n", c4)
}
Após executar o programa, obtemos a seguinte saída:
go run complex.go
The real part of c1 is 3, the imaginary part is 1
c1 + c2 is (7+6i)
c1 * c2 is (7+19i)
Explicação da saída:
The real part of c1 is 3, the imaginary part is 1: A funçãoreal(c1)extrai a parte real do número complexoc1, que é3, eimag(c1)extrai a parte imaginária dec1, que é1. Esses valores são então impressos usando o marcador%v.c1 + c2 is (7+6i): Esta linha mostra o resultado da adição dos números complexosc1ec2.c1é definido como3 + 1iec2como4 + 5i. Adicionar as partes real e imaginária separadamente dá(3 + 4) + (1 + 5)i, ou7 + 6i.c1 * c2 is (7+19i): Esta linha mostra o resultado da multiplicação dos números complexosc1ec2.(3 + 1i) * (4 + 5i)é calculado como(3*4 - 1*5) + (3*5 + 1*4)i, que simplifica para(12-5) + (15+4)i, e finalmente para7+19i.
Neste programa, demonstramos como inicializar e realizar operações em números complexos usando a função complex e os operadores + e *. real() e imag() são usados para extrair as partes real e imaginária de um número complexo, respectivamente. O verbo %v em fmt.Printf é usado como um marcador geral para exibir números complexos.
Sintaxe de Valores Literais
Na versão 1.13, Go introduziu a Numeric Literal Syntax (Sintaxe Literal Numérica). Ela define a representação de números em diferentes sistemas numéricos. Vamos dar uma olhada em suas regras.
- Binário: Adicione
0bantes do inteiro. Por exemplo,0b101é equivalente a5em decimal. - Octal: Adicione
0oou0Oantes do inteiro. Por exemplo,0o11é equivalente a9em decimal. - Hexadecimal: Adicione
0xou0Xantes do inteiro. Por exemplo,0x1bé equivalente a27em decimal. - Use
_para separar dígitos no inteiro, por exemplo,0b1000_0100_0010_0001é equivalente a0b1000010000100001. Isso pode melhorar a legibilidade.
Vamos demonstrá-lo em detalhes:
cd ~/project
touch literals.go
package main
import "fmt"
func main() {
// Binary, add 0b at the beginning
var a int = 0b101
fmt.Printf("Binary a is %b, decimal is %d\n", a, a)
// Octal, add 0o or 0O at the beginning
var b int = 0o11
fmt.Printf("Octal b is %o, decimal is %d\n", b, b)
// Hexadecimal, add 0x or 0X at the beginning
var c int = 0x1b
fmt.Printf("Hexadecimal c is %x, decimal is %d\n", c, c)
// Use separators
d := 0b1000_0100_0010_0001
e := 0b1000010000100001
if d == e {
fmt.Println("d is equal to e")
}
}
Após executar o programa, obtemos a seguinte saída:
go run literals.go
Binary a is 101, decimal is 5
Octal b is 11, decimal is 9
Hexadecimal c is 1b, decimal is 27
d is equal to e
Explicação da saída:
Binary a is 101, decimal is 5: Esta linha mostra a representação binária de0b101, que é101, e seu equivalente decimal é5. O marcador%bexibe o valor binário dea, enquanto%dexibe a representação decimal.Octal b is 11, decimal is 9: O número octal0o11é equivalente a1*8^1 + 1*8^0 = 8 + 1 = 9em decimal. O marcador%oexibe a representação octal deb, e%dexibe seu valor decimal.Hexadecimal c is 1b, decimal is 27: O número hexadecimal0x1bé equivalente a1*16^1 + 11*16^0 = 16 + 11 = 27em decimal. O marcador%xexibe a representação hexadecimal dec, e%dexibe seu valor decimal.d is equal to e: Isso mostra que o literal binário0b1000_0100_0010_0001é igual a0b1000010000100001em valor. Os sublinhados são puramente para legibilidade e não alteram o valor do número. A condiçãoifavalia corretamente paratrue.
Neste programa, demonstramos a declaração e a saída de diferentes sistemas numéricos, bem como o uso de separadores. Em fmt.Printf, usamos diferentes marcadores para representar diferentes sistemas numéricos. Por exemplo, %b representa binário e %x representa hexadecimal. Dominá-los melhorará a eficiência da programação.
Resumo
Vamos rever o que aprendemos nesta seção:
- Inteiros podem ser divididos em inteiros com sinal e inteiros sem sinal
- Os tipos de inteiro padrão
inteuinttêm intervalos que dependem da plataforma - Representação de números de ponto flutuante
- Como usar números complexos
- Introdução da sintaxe de valor literal
- Como usar diferentes marcadores
Nesta seção, explicamos e demonstramos tipos de inteiros comuns, tipos de ponto flutuante e tipos booleanos. Discutimos seus tamanhos, intervalos e uso. Também introduzimos números complexos e constantes literais. Tipos numéricos são a pedra angular dos programas Go, e é importante que os alunos os estudem bem.



