1

A problem with C defines:

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

This gives 16.0 for COUNTER1_LIMIT.

Debugging the code in Eclipse shows all is OK. However, when I build release version from a makefile, it produces different result. I have narrowed the problem down to this line:

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

where counter1 is uint16_t.

What am I doing wrong with those defines?

This solves the problem:

if( counter1 == 16 )

but that's not the way to go.

Danijel
  • 8,198
  • 18
  • 69
  • 133

4 Answers4

2

The reason for the bug is that floating point numbers are accurate, see the millions of others posts on SO about this, for example Why Are Floating Point Numbers Inaccurate?

But in your specific case the problem is that you use floating point where it isn't needed or useful. You just need a compile-time constant. Fix the expression like this:

#define RANGE1_ms 64u
#define COUNTER1_LIMIT (16000u / 64u * RANGE1_ms / 1000u )
Community
  • 1
  • 1
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • This "works" when `16000u / 64u` is exact - like here. It can introduce unneeded computational error of `COUNTER1_LIMIT` with `FS / 64u` and `FS` is not a multiple of 64. – chux - Reinstate Monica May 11 '17 at 16:37
  • @chux None of that is mentioned or made clear by the question. This part is the only thing making this question worth preserving though. If the question is just yet another "why are my float numbers inaccurate" question, then it should be closed. – Lundin May 12 '17 at 06:44
1

Floating point numbers are susceptible to loss of precision. The 16.0 can get converted to 15.9999 when loaded as a temporary. Casting it to uint16_t makes it 15 that doesn't compare against 16.

You need a function to round the floating point values. That you can call on COUNTER1_LIMIT.

Other option would be to promote counter1 to float and check if the absolute difference between this and COUNTER1_LIMIT is less than a small value like 0.001.

It can be done as

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

This should work for you.

Ajay Brahmakshatriya
  • 8,993
  • 3
  • 26
  • 49
  • It's a hack, but will consider it. – Danijel May 11 '17 at 08:34
  • Use this technique is float values are an absolute requirement. Like say `COUNTER_LIMIT1` can be `16.5`. If it is known to be an integer value, use what @Lundin has suggested. – Ajay Brahmakshatriya May 11 '17 at 08:43
  • @Danijel It's not a hack, it is how you compare float numbers for equality, you introduce a "delta" which is the desired value +/- a certain interval. This does however require a run-time check, which shouldn't be needed here. – Lundin May 11 '17 at 11:07
1

RANGE1_ms/1000.0f is 64/1000.0f . The problem is, computers cannot represent 0.064 exactly. Ultimately, numbers are represented as a*2b, where a and b are integers, which is impossible for 0.064 . So, RANGE1_ms/1000.0f is approximately 0.064. When you multiply it by 16000f and divide by 64, you receive approximately 16f. It may be 15.999999, or 16.000003, or something. When you cast it to uint16_t, it becomes either 15 or 16.

One way is to cast it as (uint16_t)(COUNTER1_LIMIT+0.5). It will round it to the closest integer. Alternatively, if your COUNTER1_LIMIT is always integer (that is you know that the expression should produce integer number), you may do

#define FS_BY_1000 16
#define COUNTER1_LIMIT (RANGE1_ms * FS_BY_1000/BS)
user31264
  • 6,557
  • 3
  • 26
  • 40
1

Avoid FP math with the pre-processor.

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

Alternative 1: use integer math that rounds quotients to nearest. Adding half the divisor works when dividend/divisor are positive.

#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:

When constants like FS truly need to be FP like 16123.4f, use a rounding function rather than truncation with an integer cast like (uint16_t)

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

Alternative 3:

When constants like FS truly need to be FP like 16123.4f, add 0.5 then truncate with an integer cast like (uint16_t). The add 0.5 trick works when the value is positive. It fails with a number of values when adding 0.5 is not exact. Yet has the advantage: it can be computed, as in OP's case, at compile time.

if (counter1 == (uint16_t)(COUNTER1_LIMIT + 0.5))
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256