Boa tarde pessoal.

Hoje eu estava escrevendo um sketch para ajudar um amigo aqui do LdG e me deparei

com um problema causado quando faço uma operação de potencia dentro de um for().

Se faço o calculo fora do for(), funciona corretamente, mas se faço dentro do for(),

os valores  estão errados.

Estou usando um arduino Mini e minha IDE é versão 1.8.9.

O  sketch é bem simples, mas não sei onde estou " comendo bola".

Agradeço a quem se dispuser a me auxiliar.

RV

Sketch :  Erro_potencia.ino

Resultado no serial monitor ao rodar o sketch.

10 elevado a 2 = 100
10 elevado a 9 = 1000000000

10 elevado a 0 = 1
10 elevado a 1 = 10
10 elevado a 2 = 99
10 elevado a 3 = 999
10 elevado a 4 = 9999
10 elevado a 5 = 99999
10 elevado a 6 = 999999
10 elevado a 7 = 9999984
10 elevado a 8 = 99999968
10 elevado a 9 = 999999616

Exibições: 607

Responder esta

Respostas a este tópico

olá RV.

      Analisei seu código, e descobri o motivo do "problema" que vc apresentou. A questão é muito sutil, e acabei percebendo o motivo porque recentemente eu estava elaborando uma LIB para o Arduino  e tive que tratar algumas coisas relacionadas a isso.

      Mas vou deixar vc um pouco encafifado  (e também pra ficar mais evidente o motivo do "problema"), e peço que teste este código e veja o resultado:

      Este aqui :   "Erro_potencia_01.zip"

      No Terminal, vc irá ver dois resultados para o "10 elevado a 9" fora do "for".  Essencialmente o que eu fiz pra confirmar o motivo da minha suspeita, foi "enganar" (ou talvez melhor seria dizer, "distrair")  o  Compilador C++.

      Dê uma olhada, logo mais explico o porque ocorre isso (é algo realmente importante pra se saber, pra evitar o problema de cálculo "errado").

      Uma dica importante:  depois do Teste com o código acima, teste este mesmo código no ESP32,  e verá que o erro não acontece.

      Abrçs,

      Elcids

RV, vamos ao motivo do problema, e o esclarecimento disto:

      Antes de tudo, é preciso lembrar que o "pow" fornece um resultado "float",  o qual no AVR tem precisão de 32 bits.  E nesta representação de 32 bits, o "float"  tem  no máximo  6 dígitos confiáveis (isto no total, independente se estão de um lado ou outro do ponto decimal).   Assim,  se vc fizer  de fato,  qualquer cálculo com "float",  nunca  terá  mais que  6 dígitos confiáveis no resultado. Então quando o resultado do "pow" é atribuído ao "saída", que é um "unsigned long",  o valor será truncado, e como no "float" não tem mais que 6 dígitos então resulta no valor "errado" que vc viu no Terminal.  Mas é claro que isto não explica o motivo de isso não acontecer no início,  e depois acontecer dentro do "for". Na verdade, o problema não  é  o  "for", conforme demonstrei pra vc que acontece mesmo no início (quando eu "enganei" o Compilador C++,  como explico logo adiante). Veja o porque disto:

     Há uma "esperteza" no Compilador C++ do AVR.  Ele analisa a sequência de "statements" que vc está executando, especificamente em relação aos valores que estão nas variáveis, e como estes valores foram "parar ali".

     Assim, ele "sabe" que os valores atribuídos ao "saida" e ao "cont" são valores constantes, de acordo com a sequencia de "statements"  até  o  cálculo  de "saida = pow(10,cont )". Então ele "engana"  vc,  e ele mesmo  faz o cálculo no x86,  uma vez que ele "percebeu" que aquilo é uma constante. Ocorre que o cálculo no x86  tem precisão de pelo menos 80 bits (efetivamente são 64 bits na "mantissa"), e nesta quantidade de bits,  temos 18 dígitos confiáveis (novamente, esse é o total, independente se estão de um lado ou outro do ponto decimal).  E claro, neste caso, o resultado é preciso e ao ser truncado para "unsigned long", nenhuma perda ocorre para o valor decimal "1000000000".  Em suma, o Compilador  faz o cálculo do valor ele mesmo, e então o seu "print" resulta ser exatamente isso:  "Serial.println(1000000000)"  ao invés de ser "Serial.println(saida)". E sem perda de precisão na representação numérica.

      Então o que eu fiz, foi "enganar" o Compilador C++,  e usei variáveis intermediárias pra despistar a análise dele. Inclusive pra garantir que ele não desconfiasse que haviam valores constantes nas variáveis, eu incrementei o "cont", e depois decrementei pra voltar no valor "9".  Neste caso, o Compilador foi obrigado a realmente chamar a função "pow" pra fazer o cálculo efetivo quando o código estiver executando no Arduino.  Mas isso resulta em problema de representação numérica,  pois o resultado preisou de 10 dígitos efetivos (o valor decimal "1000000000"),  e o "float" do AVR só te dá 6 dígitos. E quando o valor foi novamente convertido para inteiro ("unsigned long"),  o resultado ficou truncado num valor "errado".

 

      No ESP32  o problema não ocorre, porque o "float" tem 64 bits (53 bits efetivos, pois é este o tamanho da "mantissa'), o que confere um total de 15 dígitos confiáveis, sendo mais que suficiente para o resultado decimal "1000000000".

      Ou seja,  é preciso termos em mente a questão da precisão da representação numérica de cada tipo de variável, e ainda ficarmos atentos a estas "espertezas" do Compilador. Estamos trabalhando com Máquinas, e cada uma delas tem suas próprias limitações.

      Espero ter ajudado,

      Abrçs,

      Elcids

Oi EC,

muito obrigado pela detalhamento do explicação,

Nas primeiras tentativas de entender o erro, eu saquei que a operação pow era uma operação

e fp, mas como testei com variáveis no formato float e o problema persistiu eu desconsiderei.

Realmente são as falhas perigosas deste "ucontrladorinhos" ingênuos que enganam a gente. 

Abraços RV

Abrçs,

olá RV.

      Esqueci de falar sobre os resultados "dentro do for",  quando o expoente é menor que "9".

      Veja que a partir dos expoentes acima de "6", é que ocorre o erro que mencionei sobre a precisão de "6" dígitos. Isto fica bem evidente nos expoentes 7, 8, e 9.

      No entanto, o valor "impresso" para o expoente 6,  foi de "999999",  e embora isto também seja um resultado "errado", há algo a mais a considerar neste caso além da própria precisão de 6 dígitos.

      Ocorre que para fazer o cálculo da "potência",  a respectiva função "pow" da LIB Matemática do Arduino,  usa algumas constantes "float" (estas constantes são relacionadas a logaritmos),  e por serem "float", estas constantes também tem a precisão de 6 dígitos.  Assim,  no resultado do cálculo para por exemplo "10^6" o valor não será exatamente "1000000",  e alguma coisa sobrará após a vírgula. No caso da implementação matemática para o AVR,  os valores sempre resultam ser ligeiramente abaixo do valor exato, e por isso para o resultado "1000000",  temos algo como "999999.xxx", onde "xxx" seriam casas decimais após a vírgula, porém sem precisão, uma vez que a precisão é de apenas 6 dígitos. Quando o valor "float"  é então convertido para "unsigned long",  a Linguagem C  determina que esta conversão deve ser feita truncada, o que resulta em "999999".

      Observe que o mesmo ocorre para o caso dos expoentes 2, 3, 4, e 5.  Porém neste caso,  os resultados tem menos de 6 dígitos,  o que implica que não haveria perda de precisão.  Porém,  devido àquelas constantes "float" usadas "internamente" no cálculo na função "pow" da LIB Matemática do AVR,  os valores também são aproximados, e resultam ligeiramente menor que os valores exatos. Por exemplo para o expoente "2",  o resultado seria algo como "99.9999xxx", que ao ser convertido para "unsigned long" é truncado  e resulta em apenas "99".

      Conclusão:  é preciso atentar que ao convertermos de "float" para um tipo "inteiro", se esperamos valores exatos,  devemos arredondar os valores, e não esquecer que a precisão é de apenas 6 dígitos (mas sobre esta precisão,  incide  o  problema de cálculos cumulativos, que falo a seguir).

      Além disso, conforme vc vai fazendo cálculos cumulativos com "float",  a cada novo cálculo os erros de precisão vão se acumulando, e conforme o cálculo avança, vc vai perdendo mais dígitos na precisão, o que pode fornecer um valor muito diferente do que se espera. Ou seja, resultados de "float", devem ser observados com muito cuidado em relação à precisão.

      Mas e sobre os expoentes "0" e "1"?  Porque com eles o valor foi exato?  Estes são casos "especiais". Ocorre que a função "pow" testa se o expoente é um destes dois números (0 ou 1), e estes são os únicos dois casos em que o resultado é previsível: para o expoente "0" o resultado será sempre "1" (a exceção é o 0^0, onde eu acho que retorna um "nan"),  e para o expoente "1" o resultado será sempre a própria "base" (o valor que está sendo elevado ao expoente).  Então nestes dois casos, a função "pownão "perde tempo" fazendo os cálculos,  mas sim retorna os dois valores exatos como resultado (ou "1"  ou a própria "base").

      E em Sistemas onde a precisão é maior,  como o caso do ESP32,  também temos erros de precisão cumulativos,  e nunca se deve descuidar de até onde eles podem chegar conforme vc avança nos cálculos. E para analisar isso, normalmente é preciso usar técnicas de Metodologia Científica.  Este é o motivo pelo qual na computação ordinária do dia a dia  (por exemplo quando se trabalha com "dim-dim" ou "mufufa"), se buscam implementações com uma enormidade de bits  na execução de cálculos em Ponto-Flutuante. Justamente pra diminuir ou negligenciar erros "traiçoeiros" oriundos do Sistema Binário (e que poderiam causar problemas no "mundo dos humanos").

      Outro detalhe, é que no ESP32, os resultados sempre resultam em ligeiramente acima do valor exato (em contraste com as rotinas matemáticas para o AVR, onde a precisão de 6 dígitos é muito menor),  e quando ocorre o  truncamento  para um tipo inteiro, o resultado então coincide com o valor exato (exemplo:  100.000000005,  é truncado em "100").  Então nos resultados com o ESP32  temos erros também, mas por estes erros serem muito menores, eles acabaram sendo "automaticamente" suprimidos.

      Espero ter contribuído para elucidar estas "maluquices" que podem nos deixar também doidos.

      Abrçs,

      Elcids

Bom dia EC,

novamente agradeço pela explicação altamente técnica.

Para amenizar este erro, desenvolvi, (uma gambiarra),  um arquivo .h que poderá se usado para cálculos

de potencia de numero inteiros e positivos com expoente inteiros e positivos, e para resultados

de potenciação até "unsigned long", 4 bytes, no caso do arduino = 0xFFFF FFFF).

Disponibilizo aqui o teste e o .h.

RV

Calc_potencia_01.ino

newPotencia.h

Oi Rui nao é GAMBIARRA e sim soluçao tecnica nao documentada oficialmente.KKKKKKKKKKK, Existe tambem uma tecnica no meio dos programadores chamada POG ----> Linguagem Orientada a Gambiarra, Os seguidores dessa linha de estudo se utilizaçao de tecnicas milenares desenvolvimento nunca antes vista, ate mesmo pelo proprio desenvolvedor.

Brincadeiras a parte esse post vou procurar estudar mais a frente, por enquanto nao tenho nivel pra isso.

RSS

© 2024   Criado por Marcelo Rodrigues.   Ativado por

Badges  |  Relatar um incidente  |  Termos de serviço