Ao criar um aplicativo que lida com muitos cálculos matemáticos, encontrei o problema de que certos números causam erros de arredondamento.
Embora eu entenda que o ponto flutuante não é exato , o problema é como lido com os números exatos para garantir que, quando os cálculos sejam feitos neles, o arredondamento do ponto flutuante não cause problemas?
distanceTraveled(startVel, duration, acceleration)
seria testado.Respostas:
Existem três abordagens fundamentais para criar tipos numéricos alternativos sem arredondamento de ponto flutuante. O tema comum é que eles usam matemática inteira de várias maneiras.
Racionais
Represente o número como uma parte inteira e o número racional com um numerador e um denominador. O número
15.589
seria representado comow: 15; n: 589; d:1000
.Quando adicionado a 0,25 (que é
w: 0; n: 1; d: 4
), isso envolve o cálculo do LCM e a adição dos dois números. Isso funciona bem para muitas situações, embora possa resultar em números muito grandes quando você está trabalhando com muitos números racionais que são relativamente primos entre si.Ponto fixo
Você tem a parte inteira e a parte decimal. Todos os números são arredondados (existe essa palavra - mas você sabe onde está) com essa precisão. Por exemplo, você pode ter um ponto fixo com três casas decimais.
15.589
+0.250
se torna adicionando589 + 250 % 1000
para a parte decimal (e depois qualquer carrega para a parte inteira). Isso funciona muito bem com bancos de dados existentes. Como mencionado, existe um arredondamento, mas você sabe onde ele está e pode especificá-lo de forma que seja mais preciso do que o necessário (você está medindo apenas 3 casas decimais, portanto, fixe-o 4).Ponto fixo flutuante
Armazene um valor e a precisão.
15.589
é armazenado como15589
para o valor e3
para a precisão, enquanto0.25
é armazenado como25
e2
. Isso pode lidar com precisão arbitrária. Eu acredito que isso é o que os internos de usos BigDecimal do Java (não olhei recentemente) usos. Em algum momento, convém recuperá-lo deste formato e exibi-lo - e isso pode envolver arredondamentos (novamente, você controla onde está).Depois de determinar a escolha da representação, você pode encontrar bibliotecas de terceiros existentes que usam isso ou criar suas próprias. Ao escrever o seu, certifique-se de fazer o teste unitário e verifique se está fazendo as contas corretamente.
fonte
Se os valores de ponto flutuante tiverem problemas de arredondamento e você não precisar enfrentar problemas de arredondamento, logicamente segue-se que o único curso de ação é não usar valores de ponto flutuante.
Agora a pergunta passa a ser: "como faço matemática envolvendo valores não inteiros sem variáveis de ponto flutuante?" A resposta é com tipos de dados de precisão arbitrária . Os cálculos são mais lentos porque precisam ser implementados no software e não no hardware, mas são precisos. Você não disse qual idioma está usando, por isso não posso recomendar um pacote, mas existem bibliotecas de precisão arbitrárias disponíveis para as linguagens de programação mais populares.
fonte
lot of mathematical calculations
não é útil nem as respostas dadas. Na grande maioria dos casos (se você não está lidando com moeda), o float deve ser suficiente.A aritmética de ponto flutuante é geralmente bastante precisa (15 dígitos decimais para a
double
) e bastante flexível. Os problemas surgem quando você está fazendo contas que reduz significativamente a quantidade de dígitos de precisão. aqui estão alguns exemplos:Cancelamento na subtração:,
1234567890.12345 - 1234567890.12300
o resultado0.0045
possui apenas dois dígitos decimais de precisão. Isso ocorre sempre que você subtrai dois números de magnitude semelhante.Ingestão de precisão:
1234567890.12345 + 0.123456789012345
avalia como1234567890.24691
, os últimos dez dígitos do segundo operando são perdidos.Multiplicações: se você multiplicar dois números de 15 dígitos, o resultado terá 30 dígitos que precisam ser armazenados. Como você não pode armazená-los, os últimos 15 bits são perdidos. Isso é especialmente irritante quando combinado com um
sqrt()
(como emsqrt(x*x + y*y)
: O resultado terá apenas 7,5 dígitos de precisão.Estas são as principais armadilhas que você precisa estar ciente. E uma vez que você os conhece, pode tentar formular sua matemática de uma maneira que os evite. Por exemplo, se você precisar incrementar um valor repetidamente em um loop, evite fazer isso:
Após algumas iterações, o maior
f
engolirá parte da precisão dedf
. Pior, os erros se acumularão, levando à situação contra-intuitiva de que um menordf
pode levar a piores resultados gerais. Melhor escrever isso:Como você está combinando os incrementos em uma única multiplicação, o resultado
f
será preciso com 15 dígitos decimais.Este é apenas um exemplo, existem outras maneiras de evitar a perda de precisão devido a outros motivos. Mas já ajuda bastante pensar na magnitude dos valores envolvidos e imaginar o que aconteceria se você fizesse suas contas com caneta e papel, arredondando para um número fixo de dígitos após cada etapa.
fonte
Como garantir que você não tenha problemas: aprenda sobre problemas aritméticos de ponto flutuante ou contrate alguém que o faça ou use algum bom senso.
O primeiro problema é a precisão. Em muitos idiomas, você tem "float" e "double" (posição dupla para "double precision") e, em muitos casos, "float" fornece cerca de 7 dígitos de precisão, enquanto o dobro fornece 15. O senso comum é que, se você tiver um Numa situação em que a precisão pode ser um problema, 15 dígitos são muito melhores que 7 dígitos. Em muitas situações levemente problemáticas, usar "duplo" significa que você se safa e "flutuar" significa que não. Digamos que o valor de mercado de uma empresa é de 700 bilhões de dólares. Represente isso no float e o bit mais baixo é $ 65536. Representá-lo usando o dobro, e o bit mais baixo é de cerca de 0,012 centavos. Portanto, a menos que você saiba realmente o que está fazendo, use o dobro, não o flutuador.
O segundo problema é mais uma questão de princípio. Se você fizer dois cálculos diferentes que devem dar o mesmo resultado, eles geralmente não o fazem devido a erros de arredondamento. Dois resultados que devem ser iguais serão "quase iguais". Se dois resultados estiverem próximos, os valores reais podem ser iguais. Ou eles podem não ser. Você deve ter isso em mente e deve escrever e usar funções que digam "x é definitivamente maior que y" ou "x é definitivamente menor que y" ou "x e y podem ser iguais".
Esse problema fica muito pior se você usar o arredondamento, por exemplo "arredondar x para baixo para o número inteiro mais próximo". Se você multiplicar 120 * 0,05, o resultado deve ser 6, mas o que você obtém é "algum número muito próximo de 6". Se você "arredondar para o número inteiro mais próximo", esse "número muito próximo de 6" poderá ser "um pouco menor que 6" e arredondado para 5. E observe que não importa quanta precisão você tenha. Não importa quão próximo de 6 seja o seu resultado, desde que seja menor que 6.
E terceiro, alguns problemas são difíceis . Isso significa que não há regra rápida e fácil. Se o seu compilador suportar "long double" com mais precisão, você poderá usar "long double" e verificar se isso faz diferença. Se isso não faz diferença, você está bem ou tem um problema realmente complicado. Se faz o tipo de diferença que você esperaria (como uma mudança no décimo nono decimal), provavelmente está bem. Se realmente alterar seus resultados, você terá um problema. Peça por ajuda.
fonte
Muitas pessoas cometem o erro quando vêem o dobro e gritam BigDecimal, quando na verdade acabam de mudar o problema para outro lugar. O dobro fornece bit de sinal: 1 bit, largura do expoente: 11 bits. Precisão significativa: 53 bits (52 armazenados explicitamente). Devido à natureza do duplo, quanto maior o interger, você perde a precisão relativa. Para calcular a precisão relativa que usamos aqui, abaixo.
Precisão relativa do dobro no cálculo, usamos o seguinte foluma 2 ^ E <= abs (X) <2 ^ (E + 1)
epsilon = 2 ^ (E-10)% Para um flutuador de 16 bits (meia precisão)
Em outras palavras, se você deseja uma precisão de +/- 0,5 (ou 2 ^ -1), o tamanho máximo que o número pode ter é 2 ^ 52. Qualquer que seja maior que isso e a distância entre números de ponto flutuante seja maior que 0,5.
Se você deseja uma precisão de +/- 0,0005 (cerca de 2 ^ -11), o tamanho máximo que o número pode ter é 2 ^ 42. Qualquer que seja maior que isso e a distância entre números de ponto flutuante seja maior que 0,0005.
Eu realmente não posso dar uma resposta melhor do que isso. O usuário precisará descobrir qual precisão deseja ao executar o cálculo necessário e seu valor unitário (metros, pés, polegadas, mm, cm). Para a grande maioria dos casos, o float é suficiente para simulações simples, dependendo da escala do mundo que você deseja simular.
Embora seja algo a ser dito, se você estiver apenas com o objetivo de simular um mundo de 100 metros por 100 metros, terá um lugar na ordem de precisão próximo de 2 ^ -45. Isso nem mesmo mostra como a FPU moderna dentro das CPUs fará cálculos fora do tamanho do tipo nativo e somente após a conclusão do cálculo eles serão arredondados (dependendo do modo de arredondamento da FPU) para o tamanho do tipo nativo.
fonte