Compile time initialization of fix16_t values without floating-point support

66 views
Skip to first unread message

Jonas Zeiger

unread,
Dec 30, 2014, 4:44:49 AM12/30/14
to libfi...@googlegroups.com
On lower-end 32-bit microcontrollers without a FPU (like ARM Cortex-M0) it can be helpfull to disable compiler floating point (soft-float) support entirely. That makes sure that no code in the project uses types like "float" and "double" for variables or parameter passing and thus eases porting to libfixmath based fixed-point math. I haven't used that feature with gcc, but for armcc the option is "--fpu=none".

Compiling with that option means that constant fix16_t values cannot be initialized using the F16() macro anymore, because it relies on floating point calculation at compile time.

So I added a helper macro which allows for compile time initialization of fix16_t values to "fix16.h" without using floating-point types internally. The macro was split up for improved readability.

Many invalid uses of the macro are caught by the compiler at compile time (warnings about range and changes of sign), but not all.

Please review, suggest changes and consider integration into libfixmath to save the time of people in need of the same functionality.

Code (fix16.h):


/** Replace token with its number of characters/digits. */
#define FIXMATH_TOKLEN(token) ( sizeof( #token ) - 1 )

/** Handles pow(10, n) for n from 0 to 8. */
#define FIXMATH_CONSTANT_POW10(times) ( \
 
(times == 0) ? 1ULL \
       
: (times == 1) ? 10ULL \
           
: (times == 2) ? 100ULL \
               
: (times == 3) ? 1000ULL \
                   
: (times == 4) ? 10000ULL \
                       
: (times == 5) ? 100000ULL \
                           
: (times == 6) ? 1000000ULL \
                               
: (times == 7) ? 10000000ULL \
                                   
: 100000000ULL \
)


/** Helper macro for F16C, the type uint64_t is only used at compile time and
 *  shouldn't be visible in the generated code.
 *
 * @note We do not use fix16_one instead of 65536ULL, because the
 *       "use of a const variable in a constant expression is nonstandard in C".
 */

#define FIXMATH_CONVERT_MANTISSA(m) \
( (unsigned) \
   
( \
       
( \
           
( \
               
(uint64_t)( ( ( 1 ## m ## ULL ) - FIXMATH_CONSTANT_POW10(FIXMATH_TOKLEN(m)) ) * FIXMATH_CONSTANT_POW10(5 - FIXMATH_TOKLEN(m)) ) \
               
* 100000ULL * 65536ULL \
           
) \
           
+ 5000000000ULL /* rounding: + 0.5 */ \
       
) \
       
/ \
       
10000000000LL \
   
) \
)


#define FIXMATH_COMBINE_I_M(i, m) \
( \
   
( \
       
(    i ) \
       
<< 16 \
   
) \
   
| \
   
( \
        FIXMATH_CONVERT_MANTISSA
(m) \
       
& 0xFFFF \
   
) \
)


/** Create int16_t (Q16.16) constant from separate integer and mantissa part.
 *
 * Only tested on 32-bit ARM Cortex-M0 / x86 Intel.
 *
 * This macro is needed when compiling with options like "--fpu=none",
 * which forbid all and every use of float and related types and
 * would thus make it impossible to have fix16_t constants.
 *
 * Just replace uses of F16() with F16C() like this:
 *   F16(123.1234) becomes F16C(123,1234)
 *
 * @warning Specification of any value outside the mentioned intervals
 *          WILL result in undefined behavior!
 *
 * @note Regardless of the specified minimum and maximum values for i and m below,
 *       the total value of the number represented by i and m MUST be in the interval
 *       ]-32768.00000:32767.99999[ else usage with this macro will yield undefined behavior.
 *
 * @param i Signed integer constant with a value in the interval ]-32768:32767[.
 * @param m Positive integer constant in the interval ]0:99999[ (fractional part/mantissa).
 */

#define F16C(i, m) \
( (fix16_t) \
   
( \
     
(( i ) < 0) \
       
? -FIXMATH_COMBINE_I_M((unsigned)( ( (i) * -1) ), m) \
       
: FIXMATH_COMBINE_I_M(i, m) \
   
) \
)


Jonas Zeiger

unread,
Dec 30, 2014, 5:28:27 AM12/30/14
to libfi...@googlegroups.com
I used the following function (C99) to test some input values:

/* Number of elements of a static array.
 * The extra fuss is about catching invalid uses at compile time,
 * the compiler will optimize this away in production builds.
 */

#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))

static void test_f16c(void)
{
   
const fix16_t test[] =
   
{
        F16C
(0,0),
        F16C
(000000,00000),
        F16C
(0,00001),
        F16C
(0,00010),
        F16C
(0,1),
        F16C
(0,10001),
        F16C
(0,11000),
        F16C
(25,133),
        F16C
(32767,00000),
        F16C
(32767,00001),
        F16C
(32767,99999),
        F16C
(0,25),
        F16C
(0,99555),
        F16C
(0,99998),
        F16C
(0,99999),
        F16C
(-1,1),
        F16C
(-25,133),
        F16C
(-32767,00001),
        F16C
(-32768,00000)
   
};

    puts
("Test F16C:\n");
   
for (int i = 0; i < COUNT_OF(test); ++i)
   
{
       
char buf[20] = { 0 };
        fix16_to_str
(test[i], buf, 7);
        puts
(buf);
        putc
('\n', stdout);
   
}
}


Petteri Aimonen

unread,
Dec 30, 2014, 1:42:37 PM12/30/14
to libfi...@googlegroups.com
Hi,

Found one small problem: values like F16C(-0,1) became positive instead, as the leading sign of 0 is lost. 
Was easy enough to fix by using ( #i[0] ) == '-') as the test in F16C() macro.

Committed, thanks!

--
Petteri
Reply all
Reply to author
Forward
0 new messages