2017-05-11 1 views
1

Un problème avec C définit:C définit le bon usage

#define RANGE1_ms 64 
#define FS 16000.0f 
#define BS 64 
#define COUNTER1_LIMIT ((RANGE1_ms/1000.0f)* FS/BS) 

Cela donne 16.0 pour COUNTER1_LIMIT.

Le débogage du code dans Eclipse montre que tout est OK. Cependant, lorsque je compile une version à partir d'un makefile, elle produit un résultat différent. J'ai réduit le problème à cette ligne:

if(counter1 == (uint16_t)COUNTER1_LIMIT) 
{ 
    ... 
} 

counter1 est uint16_t. Qu'est-ce que je fais de mal avec ces définitions?

Cela résout le problème:

if(counter1 == 16) 

mais ce n'est pas le chemin à parcourir.

+0

Le 16.0 pourrait être converti en 15.999999 et en convertissant en uint16_t serait converti en 15. –

+0

Je suggérerais aussi de promouvoir counter1 à 'float' et de vérifier si la différence absolue entre eux est inférieure à dire' 0.001'. –

+0

Essayez #define COUNTER1_LIMIT ((uint16_t) ((RANGE1_ms/1000.0f) * 16000.0f/64)) –

Répondre

1

mathématiques Évitez de FP avec le pré-processeur.

// Avoid the following 
#define FS 16000.0f 
#define COUNTER1_LIMIT ((RANGE1_ms/1000.0f)* FS/BS) 

Alternative 1: utiliser des maths entiers arrondissant les quotients au plus proche. L'ajout de la moitié du diviseur fonctionne lorsque le dividende/diviseur est positif.

#define RANGE1_ms 64 
#define FS 16000 
#define BS 64 
#define ms_PER_s 1000 
#define COUNTER1_LIMIT_N (1ul * RANGE1_ms * FS) 
#define COUNTER1_LIMIT_D (1ul * ms_PER_s * BS) 
#define COUNTER1_LIMIT_I ((COUNTER1_LIMIT_N + COUNTER1_LIMIT_D/2)/COUNTER1_LIMIT_D) 
#define COUNTER1_LIMIT_F (1.0*COUNTER1_LIMIT_N/COUNTER1_LIMIT_D) 

if (counter1 == COUNTER1_LIMIT_I) 

Alternative 2:

Lorsque des constantes comme FS doivent vraiment être FP comme 16123.4f, utiliser une fonction d'arrondi plutôt que troncature avec un entier coulé comme (uint16_t)

#include <math.h> 
if (counter1 == lround(COUNTER1_LIMIT)) 

Alternative 3:

Lorsque des constantes comme FS doivent vraiment être FP comme 16123.4f, ajoutez 0,5 puis tronquez avec un nombre entier comme (uint16_t). Le truc add 0.5 fonctionne quand la valeur est positive. Il échoue avec un certain nombre de valeurs lorsque l'ajout de 0.5 n'est pas exact. Pourtant, a l'avantage: il peut être calculé, comme dans le cas d'OP, au moment de la compilation. La conversion de `float` en entiers de n'importe quel type peut entraîner une perte de précision.

if (counter1 == (uint16_t)(COUNTER1_LIMIT + 0.5)) 
1

Les nombres à virgule flottante sont susceptibles de perdre leur précision. Le 16.0 peut être converti en 15.9999 lorsqu'il est chargé temporairement. La coulée à uint16_t le fait 15 qui ne se compare pas à 16.

Vous avez besoin d'une fonction pour arrondir les valeurs à virgule flottante. Que vous pouvez appeler COUNTER1_LIMIT.

Autre option serait de promouvoir counter1 à float et vérifier si la différence absolue entre ce dernier et COUNTER1_LIMIT est inférieure à une petite valeur comme 0.001.

Il peut se faire comme

float diff = counter1 - COUNTER1_LIMIT; 
if(diff > -0.001 && diff < 0.001) { 
    ... 
} 

Cela devrait fonctionner pour vous.

+0

C'est un hack, mais le considérera. – Danijel

+0

Utilisez cette technique est des valeurs flottantes sont une exigence absolue. Comme dire 'COUNTER_LIMIT1' peut être' 16.5'. Si l'on sait qu'il s'agit d'une valeur entière, utilisez ce que @Lundin a suggéré. –

+0

@Danijel Ce n'est pas un hack, c'est la façon dont vous comparez les nombres flottants pour l'égalité, vous introduisez un "delta" qui est la valeur désirée +/- un certain intervalle. Cela nécessite toutefois une vérification de l'exécution, qui ne devrait pas être nécessaire ici. – Lundin

2

La raison du bug est que les nombres à virgule flottante sont exacts, voir les millions d'autres messages sur SO à ce sujet, par exemple Why Are Floating Point Numbers Inaccurate?

Mais dans votre cas le problème est que vous utilisez à virgule flottante où il n'est pas nécessaire ou utile. Vous avez juste besoin d'une constante de compilation. Fixer l'expression comme ceci:

#define RANGE1_ms 64u 
#define COUNTER1_LIMIT (16000u/64u * RANGE1_ms/1000u) 
+0

Ce "fonctionne" quand '16000u/64u' est exact - comme ici. Il peut introduire une erreur de calcul non nécessaire de 'COUNTER1_LIMIT' avec' FS/64u' et 'FS' n'est pas un multiple de 64. – chux

+0

@chux Rien de tout cela n'est mentionné ou précisé par la question. Cette partie est la seule chose qui rend cette question digne d'être conservée. Si la question est juste une autre question "pourquoi mon nombre de flotteurs est inexact", alors il devrait être fermé. – Lundin

1

RANGE1_ms/1000.0f est 64/1000.0f. Le problème est que les ordinateurs ne peuvent pas représenter exactement 0,064. En fin de compte, les nombres sont représentés par un * 2 b, où a et b sont des entiers, ce qui est impossible pour 0,064. Ainsi, RANGE1_ms/1000.0f est environ 0,064. Lorsque vous le multipliez par 16000f et divisez par 64, vous recevez environ 16f. Il peut être 15,999999, ou 16,000003, ou quelque chose. Lorsque vous le transtypez en uint16_t, il devient 15 ou 16.

L'une des méthodes consiste à le transformer en (uint16_t)(COUNTER1_LIMIT+0.5). Il l'arrondira à l'entier le plus proche.Par ailleurs, si votre COUNTER1_LIMIT est toujours entier (qui vous savez que l'expression doit produire nombre entier), vous pouvez le faire

#define FS_BY_1000 16 
#define COUNTER1_LIMIT (RANGE1_ms * FS_BY_1000/BS) 
+0

La solution '+ 0.5' semble bien. – Danijel