No JavaScript moderno, existem dois tipos de números:
Os números regulares em JavaScript são armazenados no formato IEEE-754 de 64 bits, também conhecido como “números de ponto flutuante de precisão dupla”. Esses são números que usamos na maior parte do tempo e falaremos sobre eles neste capítulo.
Os números BigInt representam inteiros de comprimento arbitrário. Às vezes, eles são necessários porque um número inteiro regular não pode exceder (2 53 -1)
ou ser menor que -(2 53 -1)
com segurança, como mencionamos anteriormente no capítulo Tipos de dados. Como bigints são usados em algumas áreas especiais, nós os dedicamos a um capítulo especial BigInt.
Então aqui falaremos sobre números regulares. Vamos expandir nosso conhecimento sobre eles.
Imagine que precisamos escrever 1 bilhão. A maneira óbvia é:
seja bilhão = 1000000000;
Também podemos usar sublinhado _
como separador:
deixe bilhão = 1_000_000_000;
Aqui o sublinhado _
desempenha o papel de “açúcar sintático”, torna o número mais legível. O mecanismo JavaScript simplesmente ignora _
entre os dígitos, então é exatamente o mesmo bilhão acima.
Porém, na vida real, tentamos evitar escrever longas sequências de zeros. Temos preguiça de fazer isso. Tentaremos escrever algo como "1bn"
por um bilhão ou "7.3bn"
por 7 bilhões e 300 milhões. O mesmo se aplica à maioria dos números grandes.
Em JavaScript, podemos encurtar um número anexando a letra "e"
a ele e especificando a contagem de zeros:
seja bilhão = 1e9; // 1 bilhão, literalmente: 1 e 9 zeros alerta( 7.3e9 ); // 7,3 bilhões (o mesmo que 7300000000 ou 7_300_000_000)
Em outras palavras, e
multiplica o número por 1
com a contagem de zeros fornecida.
1e3 === 1 * 1000; //e3 significa *1000 1,23e6 === 1,23 * 1.000.000; //e6 significa *1000000
Agora vamos escrever algo bem pequeno. Digamos, 1 microssegundo (um milionésimo de segundo):
seja mсs = 0,000001;
Assim como antes, usar "e"
pode ajudar. Se quisermos evitar escrever os zeros explicitamente, poderíamos escrever o mesmo que:
seja mcs = 1e-6; // cinco zeros à esquerda de 1
Se contarmos os zeros em 0.000001
, existem 6 deles. Então, naturalmente, é 1e-6
.
Em outras palavras, um número negativo após "e"
significa uma divisão por 1 com o número determinado de zeros:
// -3 divide por 1 com 3 zeros 1e-3 === 1/1000; //0,001 // -6 divide por 1 com 6 zeros 1,23e-6 === 1,23/1.000.000; //0,00000123 //um exemplo com um número maior 1234e-2 === 1234/100; // 12,34, ponto decimal se move 2 vezes
Números hexadecimais são amplamente usados em JavaScript para representar cores, codificar caracteres e para muitas outras coisas. Então, naturalmente, existe uma maneira mais curta de escrevê-los: 0x
e depois o número.
Por exemplo:
alerta(0xff); //255 alerta(0xFF); // 255 (o mesmo, caso não importa)
Os sistemas numéricos binários e octais raramente são usados, mas também são suportados usando os prefixos 0b
e 0o
:
seja a = 0b11111111; // forma binária de 255 seja b = 0o377; //forma octal de 255 alerta(a == b); // verdadeiro, o mesmo número 255 em ambos os lados
Existem apenas 3 sistemas numéricos com esse suporte. Para outros sistemas numéricos, devemos usar a função parseInt
(que veremos mais adiante neste capítulo).
O método num.toString(base)
retorna uma representação de string de num
no sistema numérico com a base
fornecida.
Por exemplo:
seja num = 255; alerta(num.toString(16) ); // ff alerta(num.toString(2) ); //11111111
A base
pode variar de 2
a 36
. Por padrão, é 10
.
Os casos de uso comuns para isso são:
base=16 é usado para cores hexadecimais, codificações de caracteres, etc., os dígitos podem ser 0..9
ou A..F
.
base=2 é principalmente para depuração de operações bit a bit, os dígitos podem ser 0
ou 1
.
base=36 é o máximo, os dígitos podem ser 0..9
ou A..Z
. Todo o alfabeto latino é usado para representar um número. Um caso engraçado, mas útil para 36
é quando precisamos transformar um identificador numérico longo em algo mais curto, por exemplo, para fazer uma URL curta. Pode simplesmente representá-lo no sistema numérico com base 36
:
alerta(123456..toString(36)); //2n9c
Dois pontos para chamar um método
Observe que dois pontos em 123456..toString(36)
não são um erro de digitação. Se quisermos chamar um método diretamente em um número, como toString
no exemplo acima, precisamos colocar dois pontos ..
depois dele.
Se colocássemos um único ponto: 123456.toString(36)
, haveria um erro, porque a sintaxe JavaScript implica a parte decimal após o primeiro ponto. E se colocarmos mais um ponto, então o JavaScript sabe que a parte decimal está vazia e agora segue o método.
Também poderia escrever (123456).toString(36)
.
Uma das operações mais utilizadas ao trabalhar com números é o arredondamento.
Existem várias funções integradas para arredondamento:
Math.floor
Arredonda para baixo: 3.1
torna-se 3
e -1.1
torna-se -2
.
Math.ceil
Arredondamentos: 3.1
torna-se 4
e -1.1
torna-se -1
.
Math.round
Arredonda para o número inteiro mais próximo: 3.1
torna-se 3
, 3.6
torna-se 4
. Nos casos intermediários, 3.5
rodadas até 4
e -3.5
rodadas até -3
.
Math.trunc
(não suportado pelo Internet Explorer)
Remove qualquer coisa após a vírgula sem arredondamento: 3.1
torna-se 3
, -1.1
torna-se -1
.
Aqui está a tabela para resumir as diferenças entre eles:
Math.floor | Math.ceil | Math.round | Math.trunc | |
---|---|---|---|---|
3.1 | 3 | 4 | 3 | 3 |
3.5 | 3 | 4 | 4 | 3 |
3.6 | 3 | 4 | 4 | 3 |
-1.1 | -2 | -1 | -1 | -1 |
-1.5 | -2 | -1 | -1 | -1 |
-1.6 | -2 | -1 | -2 | -1 |
Essas funções cobrem todas as formas possíveis de lidar com a parte decimal de um número. Mas e se quisermos arredondar o número para n-th
dígito após o decimal?
Por exemplo, temos 1.2345
e queremos arredondá-lo para 2 dígitos, obtendo apenas 1.23
.
Existem duas maneiras de fazer isso:
Multiplicar e dividir.
Por exemplo, para arredondar o número para o segundo dígito após o decimal, podemos multiplicar o número por 100
, chamar a função de arredondamento e depois dividir novamente.
seja num = 1,23456; alerta(Math.round(num * 100)/100); // 1,23456 -> 123,456 -> 123 -> 1,23
O método toFixed(n) arredonda o número para n
dígitos após o ponto e retorna uma representação em string do resultado.
seja num = 12,34; alerta(num.toFixed(1) ); //"12.3"
Isso arredonda para cima ou para baixo para o valor mais próximo, semelhante a Math.round
:
seja num = 12,36; alerta(num.toFixed(1) ); // "12,4"
Observe que o resultado de toFixed
é uma string. Se a parte decimal for menor que o necessário, zeros serão acrescentados ao final:
seja num = 12,34; alerta(num.toFixed(5) ); // "12.34000", adicionamos zeros para formar exatamente 5 dígitos
Podemos convertê-lo em um número usando o sinal de adição unário ou uma chamada Number()
, por exemplo, write +num.toFixed(5)
.
Internamente, um número é representado no formato IEEE-754 de 64 bits, portanto existem exatamente 64 bits para armazenar um número: 52 deles são usados para armazenar os dígitos, 11 deles armazenam a posição do ponto decimal e 1 bit é para o sinal.
Se um número for realmente grande, ele poderá estourar o armazenamento de 64 bits e se tornar um valor numérico especial Infinity
:
alerta( 1e500 ); // Infinito
O que pode ser um pouco menos óbvio, mas acontece com bastante frequência, é a perda de precisão.
Considere este (falso!) teste de igualdade:
alerta( 0,1 + 0,2 == 0,3 ); // falso
É isso mesmo, se verificarmos se a soma de 0.1
e 0.2
é 0.3
, obtemos false
.
Estranho! O que é então senão 0.3
?
alerta( 0,1 + 0,2 ); //0,30000000000000004
Ai! Imagine que você está criando um site de compras eletrônicas e o visitante coloca mercadorias $0.10
e $0.20
em seu carrinho. O total do pedido será $0.30000000000000004
. Isso surpreenderia qualquer um.
Mas por que isso acontece?
Um número é armazenado na memória em sua forma binária, uma sequência de bits – uns e zeros. Mas frações como 0.1
, 0.2
que parecem simples no sistema numérico decimal são, na verdade, frações infinitas em sua forma binária.
alerta(0.1.toString(2)); //0.0001100110011001100110011001100110011001100110011001101 alerta(0.2.toString(2)); //0,001100110011001100110011001100110011001100110011001101 alerta((0,1 + 0,2).toString(2)); //0.0100110011001100110011001100110011001100110011001101
O que é 0.1
? É um dividido por dez 1/10
, um décimo. No sistema de numeração decimal, esses números são facilmente representáveis. Compare com um terço: 1/3
. Torna-se uma fração infinita 0.33333(3)
.
Portanto, é garantido que a divisão por potências 10
funcione bem no sistema decimal, mas a divisão por 3
não. Pela mesma razão, no sistema numérico binário, a divisão por potências de 2
funciona garantidamente, mas 1/10
torna-se uma fração binária infinita.
Simplesmente não há como armazenar exatamente 0,1 ou exatamente 0,2 usando o sistema binário, assim como não há como armazenar um terço como uma fração decimal.
O formato numérico IEEE-754 resolve isso arredondando para o número mais próximo possível. Estas regras de arredondamento normalmente não nos permitem ver aquela “pequena perda de precisão”, mas ela existe.
Podemos ver isso em ação:
alerta(0.1.toFixed(20)); //0,1000000000000000555
E quando somamos dois números, as suas “perdas de precisão” somam-se.
É por isso que 0.1 + 0.2
não é exatamente 0.3
.
Não apenas JavaScript
O mesmo problema existe em muitas outras linguagens de programação.
PHP, Java, C, Perl e Ruby fornecem exatamente o mesmo resultado, porque são baseados no mesmo formato numérico.
Podemos contornar o problema? Claro, o método mais confiável é arredondar o resultado com a ajuda de um método toFixed(n):
seja soma = 0,1 + 0,2; alerta(soma.toFixed(2)); // "0,30"
Observe que toFixed
sempre retorna uma string. Ele garante que tenha 2 dígitos após a vírgula decimal. Isso é realmente conveniente se tivermos uma compra eletrônica e precisarmos mostrar $0.30
. Para outros casos, podemos usar o sinal de mais unário para transformá-lo em um número:
seja soma = 0,1 + 0,2; alerta( +soma.toFixed(2) ); //0,3
Também podemos multiplicar temporariamente os números por 100 (ou um número maior) para transformá-los em números inteiros, fazer as contas e depois dividir novamente. Então, como estamos fazendo contas com números inteiros, o erro diminui um pouco, mas ainda o obtemos na divisão:
alerta( (0,1 * 10 + 0,2 * 10)/10 ); //0,3 alerta ((0,28 * 100 + 0,14 * 100) / 100); //0,4200000000000001
Portanto, a abordagem multiplicar/dividir reduz o erro, mas não o remove totalmente.
Às vezes poderíamos tentar fugir das frações. Por exemplo, se estivéssemos lidando com uma loja, poderíamos armazenar preços em centavos em vez de dólares. Mas e se aplicarmos um desconto de 30%? Na prática, fugir totalmente das frações raramente é possível. Basta arredondá-los para cortar “caudas” quando necessário.
A coisa engraçada
Tente executar isto:
// Olá! Eu sou um número que cresce sozinho! alerta( 9999999999999999 ); //mostra 10000000000000000
Isso sofre do mesmo problema: perda de precisão. Existem 64 bits para o número, 52 deles podem ser usados para armazenar dígitos, mas isso não é suficiente. Assim, os dígitos menos significativos desaparecem.
JavaScript não aciona um erro em tais eventos. Ele faz o possível para ajustar o número no formato desejado, mas infelizmente esse formato não é grande o suficiente.
Dois zeros
Outra consequência engraçada da representação interna dos números é a existência de dois zeros: 0
e -0
.
Isso ocorre porque um sinal é representado por um único bit, portanto pode ser definido ou não para qualquer número, incluindo zero.
Na maioria dos casos, a distinção é imperceptível, porque os operadores são adequados para tratá-los como iguais.
Lembra desses dois valores numéricos especiais?
Infinity
(e -Infinity
) é um valor numérico especial maior (menor) que qualquer coisa.
NaN
representa um erro.
Eles pertencem ao tipo number
, mas não são números “normais”, portanto existem funções especiais para verificá-los:
isNaN(value)
converte seu argumento em um número e então testa se é NaN
:
alerta(éNaN(NaN)); // verdadeiro alerta(isNaN("str") ); // verdadeiro
Mas precisamos dessa função? Não podemos simplesmente usar a comparação === NaN
? Infelizmente não. O valor NaN
é único porque não é igual a nada, inclusive a si mesmo:
alerta(NaN === NaN); // falso
isFinite(value)
converte seu argumento em um número e retorna true
se for um número regular, não NaN/Infinity/-Infinity
:
alerta(isFinite("15") ); // verdadeiro alert(isFinite("str") ); // falso, porque um valor especial: NaN alert(isFinite(Infinito)); // falso, porque um valor especial: Infinity
Às vezes, isFinite
é usado para validar se um valor de string é um número regular:
deixe num = + prompt("Digite um número", ''); // será verdadeiro a menos que você insira Infinity, -Infinity ou não um número alerta( isFinite(num) );
Observe que uma string vazia ou apenas com espaço é tratada como 0
em todas as funções numéricas, incluindo isFinite
.
Number.isNaN
e Number.isFinite
Os métodos Number.isNaN e Number.isFinite são as versões mais “estritas” das funções isNaN
e isFinite
. Eles não convertem automaticamente seu argumento em um número, mas verificam se ele pertence ao tipo number
.
Number.isNaN(value)
retorna true
se o argumento pertencer ao tipo number
e for NaN
. Em qualquer outro caso, retorna false
.
alerta(Número.isNaN(NaN)); // verdadeiro alerta(Número.isNaN("str" / 2) ); // verdadeiro // Observe a diferença: alerta(Número.isNaN("str") ); // falso, porque "str" pertence ao tipo string, não ao tipo número alerta(isNaN("str") ); // verdadeiro, porque isNaN converte a string "str" em um número e obtém NaN como resultado desta conversão
Number.isFinite(value)
retorna true
se o argumento pertencer ao tipo number
e não for NaN/Infinity/-Infinity
. Em qualquer outro caso, retorna false
.
alerta(Número.isFinite(123)); // verdadeiro alerta(Número.isFinite(Infinito)); // falso alerta(Número.isFinite(2/0)); // falso // Observe a diferença: alerta(Número.isFinite("123") ); // falso, porque "123" pertence ao tipo string, não ao tipo número alerta( isFinite("123") ); // verdadeiro, porque isFinite converte a string "123" em um número 123
De certa forma, Number.isNaN
e Number.isFinite
são mais simples e diretos do que as funções isNaN
e isFinite
. Na prática, porém, isNaN
e isFinite
são mais usados, pois são mais curtos para escrever.
Comparação com Object.is
Existe um método interno especial Object.is
que compara valores como ===
, mas é mais confiável para dois casos extremos:
Funciona com NaN
: Object.is(NaN, NaN) === true
, isso é uma coisa boa.
Os valores 0
e -0
são diferentes: Object.is(0, -0) === false
, tecnicamente isso está correto porque internamente o número possui um bit de sinal que pode ser diferente mesmo que todos os outros bits sejam zeros.
Em todos os outros casos, Object.is(a, b)
é o mesmo que a === b
.
Mencionamos Object.is
aqui porque é frequentemente usado na especificação JavaScript. Quando um algoritmo interno precisa comparar dois valores por serem exatamente iguais, ele usa Object.is
(chamado internamente de SameValue).
A conversão numérica usando mais +
ou Number()
é estrita. Se um valor não for exatamente um número, ele falhará:
alerta( +"100px"); //NaN
A única exceção são os espaços no início ou no final da string, pois são ignorados.
Mas na vida real, muitas vezes temos valores em unidades, como "100px"
ou "12pt"
em CSS. Também em muitos países, o símbolo da moeda vem depois do valor, por isso temos "19€"
e gostaríamos de extrair um valor numérico disso.
É para isso que servem parseInt
e parseFloat
.
Eles “lêem” um número de uma string até não conseguirem mais. Em caso de erro, o número obtido é retornado. A função parseInt
retorna um número inteiro, enquanto parseFloat
retornará um número de ponto flutuante:
alerta(parseInt('100px') ); // 100 alerta(parseFloat('12.5em') ); //12,5 alerta(parseInt('12.3') ); // 12, apenas a parte inteira é retornada alerta(parseFloat('12.3.4') ); // 12.3, o segundo ponto interrompe a leitura
Existem situações em que parseInt/parseFloat
retornará NaN
. Acontece quando nenhum dígito pode ser lido:
alerta(parseInt('a123') ); //NaN, o primeiro símbolo interrompe o processo
O segundo argumento de parseInt(str, radix)
A função parseInt()
possui um segundo parâmetro opcional. Ele especifica a base do sistema numérico, então parseInt
também pode analisar strings de números hexadecimais, números binários e assim por diante:
alerta(parseInt('0xff', 16) ); //255 alerta(parseInt('ff', 16) ); // 255, sem 0x também funciona alerta(parseInt('2n9c', 36) ); //123456
JavaScript possui um objeto Math integrado que contém uma pequena biblioteca de funções matemáticas e constantes.
Alguns exemplos:
Math.random()
Retorna um número aleatório de 0 a 1 (não incluindo 1).
alerta(Math.random()); //0,1234567894322 alerta(Math.random()); //0,5435252343232 alerta(Math.random()); // ... (quaisquer números aleatórios)
Math.max(a, b, c...)
e Math.min(a, b, c...)
Retorna o maior e o menor número arbitrário de argumentos.
alerta(Math.max(3, 5, -10, 0, 1) ); //5 alerta(Mat.min(1, 2) ); //1
Math.pow(n, power)
Retorna n
elevado à potência fornecida.
alerta( Math.pow(2, 10) ); // 2 na potência 10 = 1024
Existem mais funções e constantes no objeto Math
, incluindo trigonometria, que você pode encontrar na documentação do objeto Math.
Para escrever números com muitos zeros:
Acrescente "e"
com a contagem de zeros ao número. Tipo: 123e6
é igual a 123
com 6 zeros 123000000
.
Um número negativo após "e"
faz com que o número seja dividido por 1 com zeros fornecidos. Por exemplo, 123e-6
significa 0.000123
( 123
milionésimos).
Para diferentes sistemas numéricos:
Pode escrever números diretamente em sistemas hexadecimais ( 0x
), octais ( 0o
) e binários ( 0b
).
parseInt(str, base)
analisa a string str
em um número inteiro no sistema numeral com determinada base
, 2 ≤ base ≤ 36
.
num.toString(base)
converte um número em uma string no sistema numérico com a base
fornecida.
Para testes numéricos regulares:
isNaN(value)
converte seu argumento em um número e então testa se é NaN
Number.isNaN(value)
verifica se seu argumento pertence ao tipo number
e, em caso afirmativo, testa se é NaN
isFinite(value)
converte seu argumento em um número e então testa se não é NaN/Infinity/-Infinity
Number.isFinite(value)
verifica se seu argumento pertence ao tipo number
e, em caso afirmativo, testa se não é NaN/Infinity/-Infinity
Para converter valores como 12pt
e 100px
em um número:
Use parseInt/parseFloat
para a conversão “suave”, que lê um número de uma string e retorna o valor que eles podiam ler antes do erro.
Para frações:
Arredonde usando Math.floor
, Math.ceil
, Math.trunc
, Math.round
ou num.toFixed(precision)
.
Lembre-se de que há perda de precisão ao trabalhar com frações.
Mais funções matemáticas:
Veja o objeto Math quando precisar deles. A biblioteca é muito pequena, mas pode cobrir necessidades básicas.
importância: 5
Crie um script que solicite ao visitante que insira dois números e depois mostre sua soma.
Execute a demonstração
PS Há uma pegadinha com os tipos.
deixe a = +prompt("O primeiro número?", ""); deixe b = +prompt("O segundo número?", ""); alerta(a + b);
Observe o unário mais +
antes de prompt
. Ele converte imediatamente o valor em um número.
Caso contrário, a
e b
seriam string, sua soma seria sua concatenação, ou seja: "1" + "2" = "12"
.
importância: 4
De acordo com a documentação Math.round
e toFixed
ambos arredondam para o número mais próximo: 0..4
lidera para baixo enquanto 5..9
lidera para cima.
Por exemplo:
alerta(1.35.toFixed(1)); //1.4
No exemplo semelhante abaixo, por que 6.35
é arredondado para 6.3
e não 6.4
?
alerta(6.35.toFixed(1)); //6.3
Como arredondar 6.35
da maneira certa?
Internamente, a fração decimal 6.35
é um binário infinito. Como sempre nesses casos, ele é armazenado com perda de precisão.
Vejamos:
alerta(6.35.toFixed(20)); //6.34999999999999964473
A perda de precisão pode causar aumento e diminuição de um número. Neste caso específico o número fica um pouquinho menor, por isso foi arredondado para baixo.
E quanto custa 1.35
?
alerta(1.35.toFixed(20)); //1.3500000000000008882
Aqui, a perda de precisão tornou o número um pouco maior, por isso foi arredondado.
Como podemos resolver o problema com 6.35
se quisermos que ele seja arredondado da maneira correta?
Devemos aproximá-lo de um número inteiro antes do arredondamento:
alerta( (6,35 * 10).toFixed(20) ); //63.5000000000000000000
Observe que 63.5
não apresenta nenhuma perda de precisão. Isso ocorre porque a parte decimal 0.5
é na verdade 1/2
. As frações divididas por potências de 2
são representadas exatamente no sistema binário, agora podemos arredondar:
alerta( Math.round(6,35 * 10)/10 ); // 6,35 -> 63,5 -> 64 (arredondado) -> 6,4
importância: 5
Crie uma função readNumber
que solicita um número até que o visitante insira um valor numérico válido.
O valor resultante deve ser retornado como um número.
O visitante também pode interromper o processo inserindo uma linha vazia ou pressionando “CANCELAR”. Nesse caso, a função deve retornar null
.
Execute a demonstração
Abra uma sandbox com testes.
função lerNúmero() { deixe num; fazer { num = prompt("Digite um número, por favor?", 0); } while ( !isFinite(num) ); if (num === nulo || num === '') retornar nulo; retornar +num; } alert(`Leitura: ${readNumber()}`);
A solução é um pouco mais complexa do que poderia ser porque precisamos lidar com linhas null
/vazias.
Então, na verdade, aceitamos a entrada até que seja um “número normal”. Tanto null
(cancelar) quanto linha vazia também se enquadram nessa condição, pois na forma numérica são 0
.
Depois de pararmos, precisamos tratar especialmente as linhas null
e vazias (retornar null
), porque convertê-las em um número retornaria 0
.
Abra a solução com testes em uma sandbox.
importância: 4
Este ciclo é infinito. Isso nunca acaba. Por que?
seja i = 0; enquanto (eu! = 10) { eu+= 0,2; }
Isso porque i
nunca seria igual 10
.
Execute-o para ver os valores reais de i
:
seja i = 0; enquanto (eu <11) { eu+= 0,2; if (i > 9,8 && i < 10,2) alerta( i ); }
Nenhum deles é exatamente 10
.
Essas coisas acontecem por causa das perdas de precisão ao adicionar frações como 0.2
.
Conclusão: evite verificações de igualdade ao trabalhar com frações decimais.
importância: 2
A função integrada Math.random()
cria um valor aleatório de 0
a 1
(sem incluir 1
).
Escreva a função random(min, max)
para gerar um número aleatório de ponto flutuante de min
a max
(sem incluir max
).
Exemplos de seu trabalho:
alerta(aleatório(1, 5) ); //1.2345623452 alerta(aleatório(1, 5) ); //3.7894332423 alerta(aleatório(1, 5) ); //4.3435234525
Precisamos “mapear” todos os valores do intervalo 0…1 em valores de min
a max
.
Isso pode ser feito em duas etapas:
Se multiplicarmos um número aleatório de 0…1 por max-min
, então o intervalo de valores possíveis aumenta de 0..1
a 0..max-min
.
Agora, se adicionarmos min
, o intervalo possível passa a ser de min
a max
.
A função:
função aleatória(min, max) { return min + Math.random() * (máx - min); } alerta(aleatório(1, 5) ); alerta(aleatório(1, 5) ); alerta(aleatório(1, 5) );
importância: 2
Crie uma função randomInteger(min, max)
que gera um número inteiro aleatório de min
a max
incluindo min
e max
como valores possíveis.
Qualquer número do intervalo min..max
deve aparecer com a mesma probabilidade.
Exemplos de seu trabalho:
alerta( randomInteger(1, 5) ); //1 alerta( randomInteger(1, 5) ); //3 alerta( randomInteger(1, 5) ); //5
Você pode usar a solução da tarefa anterior como base.
A solução mais simples, mas errada, seria gerar um valor do min
ao max
e arredondá-lo:
function randomInteger(min, max) { deixe rand = min + Math.random() * (máx - min); retornar Math.round(rand); } alerta( randomInteger(1, 3) );
A função funciona, mas está incorreta. A probabilidade de obter valores de borda min
e max
é duas vezes menor do que qualquer outra.
Se você executar o exemplo acima muitas vezes, verá facilmente que 2
aparece com mais frequência.
Isso acontece porque Math.round()
obtém números aleatórios do intervalo 1..3
e os arredonda da seguinte forma:
valores de 1 ... a 1,4999999999 tornam-se 1 valores de 1,5 ... a 2,4999999999 tornam-se 2 valores de 2,5 ... a 2,9999999999 tornam-se 3
Agora podemos ver claramente que 1
obtém duas vezes menos valores que 2
. E o mesmo com 3
.
Existem muitas soluções corretas para a tarefa. Uma delas é ajustar as bordas dos intervalos. Para garantir os mesmos intervalos, podemos gerar valores de 0.5 to 3.5
, somando assim as probabilidades necessárias às arestas:
function randomInteger(min, max) { // agora rand é de (min-0,5) a (max+0,5) seja rand = min - 0,5 + Math.random() * (máx - min + 1); retornar Math.round(rand); } alerta( randomInteger(1, 3) );
Uma maneira alternativa poderia ser usar Math.floor
para um número aleatório de min
a max+1
:
function randomInteger(min, max) { // aqui rand é de min a (max+1) deixe rand = min + Math.random() * (máx + 1 - min); retornar Math.floor(rand); } alerta( randomInteger(1, 3) );
Agora todos os intervalos são mapeados desta forma:
valores de 1 ... a 1,9999999999 tornam-se 1 valores de 2 ... a 2,9999999999 tornam-se 2 valores de 3 ... a 3,9999999999 tornam-se 3
Todos os intervalos têm o mesmo comprimento, tornando a distribuição final uniforme.