fixed division, undefined behaviors and some improvements (#33)

* testing using ctest

* emove old testing script

* added github workflow CI

* updated CI

* added unit test for macros

* F16() and F16C() are both rounding allways, so fix16_from_dbl should as well

* added tests for strign operations, but these functions are in my opinion unreliable and tests are failing

* removed old unittests

* removed old unittests from cmake

* problem with division using gcc

* improved benchmark

* clarification of problem with division

* attempt to fix

* fixed some undefined behaviors, fixed division
This commit is contained in:
Martin Bařinka 2021-05-03 12:43:54 +02:00 committed by GitHub
parent 4ea8f3ff12
commit 0c88e55fa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 445 additions and 995 deletions

View File

@ -15,7 +15,6 @@ include(tests/tests.cmake)
file(GLOB fixsingen-srcs fixsingen/*.c)
file(GLOB fixtest-srcs fixtest/*.c fixtest/*.h)
file(GLOB unittests-srcs unittests/*.c unittests/*.h)
add_executable(fixtest ${fixtest-srcs})
target_link_libraries(fixtest PRIVATE libfixmath m)
@ -25,8 +24,4 @@ add_executable(fixsingen ${fixsingen-srcs})
target_link_libraries(fixsingen PRIVATE libfixmath m)
target_include_directories(fixsingen PRIVATE ${CMAKE_SOURCE_DIR})
add_executable(unittests ${unittests-srcs})
target_link_libraries(unittests PRIVATE libfixmath m)
target_include_directories(unittests PRIVATE ${CMAKE_SOURCE_DIR})

View File

@ -2,9 +2,9 @@
# (currently ARM Cortex M3 and AVR). They are a bit tricky to run, as they
# depend on specific simulator versions.
FILES = benchmark.c ../libfixmath/fix16.c ../libfixmath/fix16_sqrt.c ../libfixmath/fix16_exp.c
FILES = benchmark.c interface.c ../libfixmath/fix16.c ../libfixmath/fix16_sqrt.c ../libfixmath/fix16_exp.c
CFLAGS = -DFIXMATH_NO_OVERFLOW -DFIXMATH_NO_ROUNDING -ffast-math -I../libfixmath
CFLAGS = -std=gnu11 -I../libfixmath
.PHONY clean:
rm -f *.elf

View File

@ -9,15 +9,6 @@
/* Autogenerated testcases */
#include "testcases.c"
/* Tools for profiling */
typedef struct {
uint32_t min;
uint32_t max;
uint32_t sum;
uint32_t count;
} cyclecount_t;
// Initializer for cyclecount_t structure.
// Max is initialized to 0 and min is 2^32-1 so that first call to cyclecount_update will set them.
#define CYCLECOUNT_INIT {0xFFFFFFFF, 0, 0, 0}
@ -40,12 +31,6 @@ static void cyclecount_update(cyclecount_t *data, uint32_t cycles)
cyclecount_update(&variable, end_timing()); \
}
#define PRINT(variable, label) { \
print_value(label " min", variable.min); \
print_value(label " max", variable.max); \
print_value(label " avg", variable.sum / variable.count); \
}
static cyclecount_t exp_cycles = CYCLECOUNT_INIT;
static cyclecount_t sqrt_cycles = CYCLECOUNT_INIT;
static cyclecount_t add_cycles = CYCLECOUNT_INIT;
@ -53,13 +38,12 @@ static cyclecount_t sub_cycles = CYCLECOUNT_INIT;
static cyclecount_t div_cycles = CYCLECOUNT_INIT;
static cyclecount_t mul_cycles = CYCLECOUNT_INIT;
#ifndef NO_FLOAT
static cyclecount_t float_sqrtf_cycles = CYCLECOUNT_INIT;
static cyclecount_t float_expf_cycles = CYCLECOUNT_INIT;
static cyclecount_t float_add_cycles = CYCLECOUNT_INIT;
static cyclecount_t float_sub_cycles = CYCLECOUNT_INIT;
static cyclecount_t float_div_cycles = CYCLECOUNT_INIT;
static cyclecount_t float_mul_cycles = CYCLECOUNT_INIT;
#endif
static fix16_t delta(fix16_t result, fix16_t expected)
{
@ -119,8 +103,6 @@ int main()
print_value("Failed EXP, expected", expected);
}
}
PRINT(sqrt_cycles, "fix16_sqrt");
PRINT(exp_cycles, "fix16_exp");
for (i = 0; i < TESTCASES2_COUNT; i++)
{
@ -175,10 +157,6 @@ int main()
}
}
}
PRINT(add_cycles, "fix16_add");
PRINT(sub_cycles, "fix16_sub");
PRINT(mul_cycles, "fix16_mul");
PRINT(div_cycles, "fix16_div");
/* Compare with floating point performance */
#ifndef NO_FLOAT
@ -187,8 +165,8 @@ int main()
float input = fix16_to_float(testcases1[i].a);
volatile float result;
MEASURE(float_sqrtf_cycles, result = sqrtf(input));
MEASURE(float_expf_cycles, result = expf(input));
}
PRINT(float_sqrtf_cycles, "float sqrtf");
for (i = 0; i < TESTCASES2_COUNT; i++)
{
@ -204,11 +182,20 @@ int main()
MEASURE(float_div_cycles, result = a / b);
}
}
PRINT(float_add_cycles, "float add");
PRINT(float_sub_cycles, "float sub");
PRINT(float_mul_cycles, "float mul");
PRINT(float_div_cycles, "float div");
#endif
#endif
print("fix16_sqrt", &sqrt_cycles);
print("float sqrtf", &float_sqrtf_cycles);
print("fix16_exp", &exp_cycles);
print("float expf", &float_expf_cycles);
print("fix16_add", &add_cycles);
print("float add", &float_add_cycles);
print("fix16_sub", &sub_cycles);
print("float sub", &float_sub_cycles);
print("fix16_mul", &mul_cycles);
print("float mul", &float_mul_cycles);
print("fix16_div", &div_cycles);
print("float div", &float_div_cycles);
return 0;
}

View File

@ -25,8 +25,4 @@ uint16_t end_timing()
return 0x00FFFFFF - STCURRENT - 4;
}
void print_value(const char *label, int32_t value)
{
printf("%-20s %ld\n", label, value);
}

View File

@ -32,8 +32,3 @@ uint16_t end_timing()
{
return TCNT1 - 9;
}
void print_value(const char *label, int32_t value)
{
printf("%-20s %ld\n", label, value);
}

12
benchmarks/interface.c Normal file
View File

@ -0,0 +1,12 @@
#include <stdio.h>
#include "interface.h"
void print_value(const char *label, int32_t value)
{
printf("%-20s %6ld\n", label, value);
}
void print(const char *label, cyclecount_t *count)
{
printf("%-20s %6ld %6ld %6ld\n",label,count->min, count->sum / count->count, count->max);
}

View File

@ -3,6 +3,15 @@
#include <stdint.h>
/* Tools for profiling */
typedef struct {
uint32_t min;
uint32_t max;
uint32_t sum;
uint32_t count;
} cyclecount_t;
// Initialize
void interface_init();
@ -14,3 +23,5 @@ uint16_t end_timing();
// Print a value to console, along with a descriptive label
void print_value(const char *label, int32_t value);
void print(const char *label, cyclecount_t *count);

BIN
benchmarks/results-avr.ods Normal file

Binary file not shown.

View File

@ -10,7 +10,8 @@ fix16_t fix16_add(fix16_t a, fix16_t b)
{
// Use unsigned integers because overflow with signed integers is
// an undefined operation (http://www.airs.com/blog/archives/120).
uint32_t _a = a, _b = b;
uint32_t _a = a;
uint32_t _b = b;
uint32_t sum = _a + _b;
// Overflow can only happen if sign of a == sign of b, and then
@ -23,7 +24,8 @@ fix16_t fix16_add(fix16_t a, fix16_t b)
fix16_t fix16_sub(fix16_t a, fix16_t b)
{
uint32_t _a = a, _b = b;
uint32_t _a = a;
uint32_t _b = b;
uint32_t diff = _a - _b;
// Overflow can only happen if sign of a != sign of b, and then
@ -173,8 +175,8 @@ fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1)
#if defined(FIXMATH_OPTIMIZE_8BIT)
fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1)
{
uint32_t _a = (inArg0 >= 0) ? inArg0 : (-inArg0);
uint32_t _b = (inArg1 >= 0) ? inArg1 : (-inArg1);
uint32_t _a = fix_abs(inArg0);
uint32_t _b = fix_abs(inArg1);
uint8_t va[4] = {_a, (_a >> 8), (_a >> 16), (_a >> 24)};
uint8_t vb[4] = {_b, (_b >> 8), (_b >> 16), (_b >> 24)};
@ -218,7 +220,7 @@ fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1)
// i = 2
if (va[0] && vb[2]) mid += (uint16_t)va[0] * vb[2];
if (va[1] && vb[1]) mid += (uint16_t)va[1] * vb[1];
if (va[2] && vb[0]) mid += (uint16_t)va[2] * vb[0];
if (va[2] && vb[0]) mid += (uint16_t)va[2] * vb[0];
// i = 1
if (va[0] && vb[1]) low += (uint16_t)va[0] * vb[1];
@ -295,20 +297,21 @@ fix16_t fix16_div(fix16_t a, fix16_t b)
if (b == 0)
return fix16_minimum;
uint32_t remainder = (a >= 0) ? a : (-a);
uint32_t divider = (b >= 0) ? b : (-b);
uint32_t quotient = 0;
int bit_pos = 17;
uint32_t remainder = fix_abs(a);
uint32_t divider = fix_abs(b);
uint64_t quotient = 0;
int bit_pos = 17;
// Kick-start the division a bit.
// This improves speed in the worst-case scenarios where N and D are large
// It gets a lower estimate for the result by N/(D >> 17 + 1).
if (divider & 0xFFF00000)
{
uint32_t shifted_div = ((divider >> 17) + 1);
quotient = remainder / shifted_div;
remainder -= ((uint64_t)quotient * divider) >> 17;
}
quotient = remainder / shifted_div;
uint64_t tmp = ((uint64_t)quotient * (uint64_t)divider) >> 17;
remainder -= (uint32_t)(tmp);
}
// If the divider is divisible by 2^n, take advantage of it.
while (!(divider & 0xF) && bit_pos >= 4)
@ -326,8 +329,8 @@ fix16_t fix16_div(fix16_t a, fix16_t b)
bit_pos -= shift;
uint32_t div = remainder / divider;
remainder = remainder % divider;
quotient += div << bit_pos;
remainder = remainder % divider;
quotient += (uint64_t)div << bit_pos;
#ifndef FIXMATH_NO_OVERFLOW
if (div & ~(0xFFFFFFFF >> bit_pos))
@ -375,8 +378,8 @@ fix16_t fix16_div(fix16_t a, fix16_t b)
if (b == 0)
return fix16_minimum;
uint32_t remainder = (a >= 0) ? a : (-a);
uint32_t divider = (b >= 0) ? b : (-b);
uint32_t remainder = fix_abs(a);
uint32_t divider = fix_abs(b);
uint32_t quotient = 0;
uint32_t bit = 0x10000;

View File

@ -70,9 +70,10 @@ static inline fix16_t fix16_from_float(float a)
static inline fix16_t fix16_from_dbl(double a)
{
double temp = a * fix16_one;
#ifndef FIXMATH_NO_ROUNDING
/* F16() and F16C() are both rounding allways, so this should as well */
//#ifndef FIXMATH_NO_ROUNDING
temp += (double)((temp >= 0) ? 0.5f : -0.5f);
#endif
//#endif
return (fix16_t)temp;
}
@ -227,6 +228,21 @@ extern void fix16_to_str(fix16_t value, char *buf, int decimals);
*/
extern fix16_t fix16_from_str(const char *buf);
static inline uint32_t fix_abs(fix16_t in)
{
if(in == fix16_minimum)
{
// minimum negative number has same representation as
// its absolute value in unsigned
return 0x80000000;
}
else
{
return ((in >= 0)?(in):(-in));
}
}
/** Helper macro for F16C. Replace token with its number of characters/digits. */
#define FIXMATH_TOKLEN(token) ( sizeof( #token ) - 1 )

View File

@ -11,74 +11,75 @@
*/
fix16_t fix16_sqrt(fix16_t inValue)
{
uint8_t neg = (inValue < 0);
uint32_t num = (neg ? -inValue : inValue);
uint32_t result = 0;
uint32_t bit;
uint8_t n;
// Many numbers will be less than 15, so
// this gives a good balance between time spent
// in if vs. time spent in the while loop
// when searching for the starting value.
if (num & 0xFFF00000)
bit = (uint32_t)1 << 30;
else
bit = (uint32_t)1 << 18;
while (bit > num) bit >>= 2;
// The main part is executed twice, in order to avoid
// using 64 bit values in computations.
for (n = 0; n < 2; n++)
{
// First we get the top 24 bits of the answer.
while (bit)
{
if (num >= result + bit)
{
num -= result + bit;
result = (result >> 1) + bit;
}
else
{
result = (result >> 1);
}
bit >>= 2;
}
if (n == 0)
{
// Then process it again to get the lowest 8 bits.
if (num > 65535)
{
// The remainder 'num' is too large to be shifted left
// by 16, so we have to add 1 to result manually and
// adjust 'num' accordingly.
// num = a - (result + 0.5)^2
// = num + result^2 - (result + 0.5)^2
// = num - result - 0.5
num -= result;
num = (num << 16) - 0x8000;
result = (result << 16) + 0x8000;
}
else
{
num <<= 16;
result <<= 16;
}
bit = 1 << 14;
}
}
uint8_t neg = (inValue < 0);
uint32_t num = fix_abs(inValue);
uint32_t result = 0;
uint32_t bit;
uint8_t n;
// Many numbers will be less than 15, so
// this gives a good balance between time spent
// in if vs. time spent in the while loop
// when searching for the starting value.
if (num & 0xFFF00000)
bit = (uint32_t)1 << 30;
else
bit = (uint32_t)1 << 18;
while (bit > num)
bit >>= 2;
// The main part is executed twice, in order to avoid
// using 64 bit values in computations.
for (n = 0; n < 2; n++)
{
// First we get the top 24 bits of the answer.
while (bit)
{
if (num >= result + bit)
{
num -= result + bit;
result = (result >> 1) + bit;
}
else
{
result = (result >> 1);
}
bit >>= 2;
}
if (n == 0)
{
// Then process it again to get the lowest 8 bits.
if (num > 65535)
{
// The remainder 'num' is too large to be shifted left
// by 16, so we have to add 1 to result manually and
// adjust 'num' accordingly.
// num = a - (result + 0.5)^2
// = num + result^2 - (result + 0.5)^2
// = num - result - 0.5
num -= result;
num = (num << 16) - 0x8000;
result = (result << 16) + 0x8000;
}
else
{
num <<= 16;
result <<= 16;
}
bit = 1 << 14;
}
}
#ifndef FIXMATH_NO_ROUNDING
// Finally, if next bit would have been 1, round the result upwards.
if (num > result)
{
result++;
}
// Finally, if next bit would have been 1, round the result upwards.
if (num > result)
{
result++;
}
#endif
return (neg ? -(fix16_t)result : (fix16_t)result);
return (neg ? -(fix16_t)result : (fix16_t)result);
}

View File

@ -1,7 +1,9 @@
#include "tests.h"
#include "tests_basic.h"
#include "tests_lerp.h"
#include "tests_macros.h"
#include "tests_sqrt.h"
#include "tests_str.h"
#include <stdio.h>
const fix16_t testcases[] = {
@ -32,14 +34,41 @@ const fix16_t testcases[] = {
// Tiny random numbers
-171, -359, 491, 844, 158, -413, -422, -737, -575, -330, -376, 435, -311,
116, 715, -1024, -487, 59, 724, 993
};
116, 715, -1024, -487, 59, 724, 993};
unsigned stack_depth = 0;
int main()
{
printf("\033[1;34m\nVARIANT: \033[39m"STR2(PREFIX)"\033[0m\n");
printf("\033[1;34m\nVARIANT: \033[39m" STR2(PREFIX) "\033[0m\n");
#if 0
fix16_t a = 65536;
fix16_t b = -2147483648;
fix16_t result = fix16_div(a, b);
double fa = fix16_to_dbl(a);
double fb = fix16_to_dbl(b);
double fresult = fa / fb;
double max = fix16_to_dbl(fix16_maximum);
double min = fix16_to_dbl(fix16_minimum);
printf("result %i, %.20f\n", result, fix16_to_dbl(result));
printf("fresult %i, %.20f\n", fix16_from_dbl(fresult), fresult);
if ((fa / fb) > max || (fa / fb) < min)
{
#ifndef FIXMATH_NO_OVERFLOW
ASSERT_EQ_INT(result, fix16_overflow);
#endif
}
else
{
ASSERT_NEAR_DOUBLE(fresult, fix16_to_dbl(result),
fix16_to_dbl(fix16_eps), "%i / %i \n", a, b);
}
#else
TEST(test_abs());
TEST(test_add());
TEST(test_mul());
@ -47,5 +76,8 @@ int main()
TEST(test_sub());
TEST(test_sqrt());
TEST(test_lerp());
TEST(test_macros());
//TEST(test_str());
#endif
return 0;
}

View File

@ -15,15 +15,22 @@ set(nn08 PREFIX=nn08 FIXMATH_NO_OVERFLOW FIXMATH_NO_ROUNDING FIXMATH_OPTIMIZE_8B
enable_testing()
#-fno-sanitize-recover
set(sanitizer_opts -fsanitize=undefined)
add_custom_target(make_tests)
function(create_variant name defs)
add_library(libfixmath_${name} STATIC ${libfixmath-srcs})
target_compile_definitions(libfixmath_${name} PRIVATE ${defs})
target_compile_options(libfixmath_${name} PRIVATE ${sanitizer_opts})
target_link_options(libfixmath_${name} PRIVATE ${sanitizer_opts})
add_executable(tests_${name} ${tests-srcs})
target_link_libraries(tests_${name} PRIVATE libfixmath_${name} m)
target_include_directories(tests_${name} PRIVATE ${CMAKE_SOURCE_DIR})
target_compile_definitions(tests_${name} PRIVATE ${defs})
target_compile_options(tests_${name} PRIVATE ${sanitizer_opts})
target_link_options(tests_${name} PRIVATE ${sanitizer_opts})
add_dependencies(make_tests tests_${name})
add_test(NAME tests_${name} COMMAND tests_${name})
endfunction()

View File

@ -5,14 +5,15 @@
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern unsigned stack_depth;
#define delta(a, b) (((a) >= (b)) ? (a) - (b) : (b) - (a))
#define COMMENT(x) printf("\n----" x "----\n");
#define STR(x) #x
#define STR2(x) STR(x)
#define STR(x) #x
#define STR2(x) STR(x)
#define TEST(x) \
do \
{ \
@ -71,6 +72,34 @@ extern unsigned stack_depth;
} \
} while (0)
#define ASSERT_EQ_STR(a, b) \
do \
{ \
size_t la = strlen(a); \
size_t lb = strlen(b); \
if (la != lb) \
{ \
fflush(stdout); \
fflush(stderr); \
fprintf(stderr, \
"\033[31;1m FAILED:\033[22;39m%*sASSERT_EQ a: %s, b: " \
"%s\033[0m at: %s(), " __FILE__ ":" STR2(__LINE__) "\n", \
stack_depth, "", (a), (b), __func__); \
return 1; \
} \
int res = strncmp((a), (b), la); \
if (res != 0) \
{ \
fflush(stdout); \
fflush(stderr); \
fprintf(stderr, \
"\033[31;1m FAILED:\033[22;39m%*sASSERT_EQ a: %s, b: " \
"%s\033[0m at: %s(), " __FILE__ ":" STR2(__LINE__) "\n", \
stack_depth, "", (a), (b), __func__); \
return 1; \
} \
} while (0)
extern const fix16_t testcases[102];
#define TESTCASES_COUNT (sizeof(testcases) / sizeof(testcases[0]))

View File

@ -5,11 +5,11 @@ int test_abs_short(void)
{
for (unsigned i = 0; i < TESTCASES_COUNT; ++i)
{
fix16_t a = testcases[i];
double fa = fix16_to_dbl(a);
fix16_t result = fix16_abs(a);
double fresult = fabs(fa);
double min = fix16_to_dbl(fix16_minimum);
fix16_t a = testcases[i];
double fa = fix16_to_dbl(a);
fix16_t result = fix16_abs(a);
double fresult = fabs(fa);
double min = fix16_to_dbl(fix16_minimum);
if (fa <= min)
{
#ifndef FIXMATH_NO_OVERFLOW

100
tests/tests_macros.c Normal file
View File

@ -0,0 +1,100 @@
#include "tests_macros.h"
#include "tests.h"
#define DO_TEST(i, m) \
do \
{ \
ASSERT_EQ_INT(F16(i##.##m), F16C(i, m)); \
ASSERT_EQ_INT(F16(i##.##m), fix16_from_dbl(i##.##m)); \
} while (0)
int test_macros()
{
DO_TEST(1, 234);
DO_TEST(0, 0);
DO_TEST(1, 0);
DO_TEST(-1, 0);
DO_TEST(1, 5);
DO_TEST(-1, 5);
DO_TEST(000000, 00000);
DO_TEST(0, 00001);
DO_TEST(0, 00010);
DO_TEST(0, 1);
DO_TEST(0, 10001);
DO_TEST(0, 11000);
DO_TEST(25, 133);
DO_TEST(32767, 00000);
DO_TEST(32767, 00001);
DO_TEST(32767, 99999);
DO_TEST(0, 25);
DO_TEST(0, 99555);
DO_TEST(0, 99998);
DO_TEST(0, 99999);
DO_TEST(-1, 1);
DO_TEST(-25, 133);
DO_TEST(-32767, 00001);
DO_TEST(-32768, 00000);
/* Random values */
DO_TEST(0, 02267);
DO_TEST(1, 49887);
DO_TEST(0, 27589);
DO_TEST(0, 38393);
DO_TEST(0, 08934);
DO_TEST(0, 95820);
DO_TEST(0, 95596);
DO_TEST(72, 10642);
DO_TEST(0, 48939);
DO_TEST(3, 37797);
DO_TEST(1, 09194);
DO_TEST(0, 08605);
DO_TEST(3, 04349);
DO_TEST(3, 95401);
DO_TEST(15, 36292);
DO_TEST(56, 09242);
DO_TEST(0, 54071);
DO_TEST(27, 08953);
DO_TEST(0, 03913);
DO_TEST(1, 32707);
DO_TEST(4, 50117);
DO_TEST(0, 24990);
DO_TEST(44, 77319);
DO_TEST(2, 59139);
DO_TEST(0, 16279);
DO_TEST(17, 14712);
DO_TEST(11, 54281);
DO_TEST(0, 02768);
DO_TEST(0, 39278);
DO_TEST(0, 19369);
DO_TEST(-0, 04534);
DO_TEST(-0, 00349);
DO_TEST(-2, 30380);
DO_TEST(-0, 03061);
DO_TEST(-7, 50065);
DO_TEST(-3, 97050);
DO_TEST(-0, 43898);
DO_TEST(-3, 49876);
DO_TEST(-1, 35942);
DO_TEST(-10, 81154);
DO_TEST(-0, 26676);
DO_TEST(-9, 52134);
DO_TEST(-0, 42592);
DO_TEST(-0, 05424);
DO_TEST(-0, 62461);
DO_TEST(-0, 21562);
DO_TEST(-0, 22366);
DO_TEST(-0, 09074);
DO_TEST(-1, 29527);
DO_TEST(-4, 98427);
DO_TEST(-0, 10721);
DO_TEST(-11, 39446);
DO_TEST(-451, 53916);
DO_TEST(-0, 04279);
DO_TEST(-3, 36543);
DO_TEST(-0, 01003);
DO_TEST(-12, 08326);
DO_TEST(-1, 07143);
DO_TEST(-1, 07737);
DO_TEST(-0, 22957);
return 0;
}

6
tests/tests_macros.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef TESTS_MACROS_H
#define TESTS_MACROS_H
int test_macros();
#endif // TESTS_MACROS_H

108
tests/tests_str.c Normal file
View File

@ -0,0 +1,108 @@
#include "tests_str.h"
#include "tests.h"
int test_str_to()
{
char buf[13];
fix16_to_str(fix16_from_dbl(1234.5678), buf, 4);
ASSERT_EQ_STR(buf, "1234.5678");
fix16_to_str(fix16_from_dbl(-1234.5678), buf, 4);
ASSERT_EQ_STR(buf, "-1234.5678");
fix16_to_str(0, buf, 0);
ASSERT_EQ_STR(buf, "0");
fix16_to_str(fix16_from_dbl(0.9), buf, 0);
ASSERT_EQ_STR(buf, "1");
fix16_to_str(1, buf, 5);
ASSERT_EQ_STR(buf, "0.00002");
fix16_to_str(-1, buf, 5);
ASSERT_EQ_STR(buf, "-0.00002");
fix16_to_str(65535, buf, 5);
ASSERT_EQ_STR(buf, "0.99998");
fix16_to_str(65535, buf, 4);
ASSERT_EQ_STR(buf, "1.0000");
fix16_to_str(fix16_maximum, buf, 5);
ASSERT_EQ_STR(buf, "32767.99998");
fix16_to_str(fix16_minimum, buf, 5);
ASSERT_EQ_STR(buf, "-32768.00000");
return 0;
}
int test_str_from()
{
ASSERT_EQ_INT(fix16_from_str("1234.5678"), fix16_from_dbl(1234.5678));
ASSERT_EQ_INT(fix16_from_str("-1234.5678"), fix16_from_dbl(-1234.5678));
ASSERT_EQ_INT(fix16_from_str(" +1234,56780 "),
fix16_from_dbl(1234.5678));
ASSERT_EQ_INT(fix16_from_str("0"), 0);
ASSERT_EQ_INT(fix16_from_str("1"), fix16_one);
ASSERT_EQ_INT(fix16_from_str("1.0"), fix16_one);
ASSERT_EQ_INT(fix16_from_str("1.0000000000"), fix16_one);
ASSERT_EQ_INT(fix16_from_str("0.00002"), 1);
ASSERT_EQ_INT(fix16_from_str("0.99998"), 65535);
ASSERT_EQ_INT(fix16_from_str("32767.99998"), fix16_maximum);
ASSERT_EQ_INT(fix16_from_str("-32768.00000"), fix16_minimum);
return 0;
}
int test_str_extended()
{
fix16_t value = fix16_minimum;
char testbuf[13];
char goodbuf[13];
while (value < fix16_maximum)
{
double fvalue = fix16_to_dbl(value);
/* Turns out we have to jump through some hoops to round
doubles perfectly for printing:
http://stackoverflow.com/questions/994764/rounding-doubles-5-sprintf
*/
fvalue = round(fvalue * 100000.) / 100000.;
snprintf(goodbuf, 13, "%0.5f", fvalue);
fix16_to_str(value, testbuf, 5);
if (strcmp(goodbuf, testbuf) != 0)
{
printf("Value (fix16_t)%d gave %s, should be %s\n", value, testbuf,
goodbuf);
return 1;
}
fix16_t roundtrip = fix16_from_str(testbuf);
if (roundtrip != value)
{
printf("Roundtrip failed: (fix16_t)%d -> %s -> (fix16_t)%d\n",
value, testbuf, roundtrip);
return 1;
}
value += 0x10001;
}
return 0;
}
int test_str()
{
TEST(test_str_to());
TEST(test_str_from());
TEST(test_str_extended());
return 0;
}

6
tests/tests_str.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef TESTS_STR_H
#define TESTS_STR_H
int test_str();
#endif // TESTS_STR_H

View File

@ -1,78 +0,0 @@
# Makefile for running the unittests of libfixmath.
CC = gcc
# Basic CFLAGS for debugging
CFLAGS = -g -O0 -I../libfixmath -I.. -Wall -Wextra -Werror
# The files required for tests
FIX16_SRC = ../libfixmath/fix16.c ../libfixmath/fix16_sqrt.c ../libfixmath/fix16_str.c \
../libfixmath/fix16_exp.c ../libfixmath/fix16.h
all: run_fix16_unittests run_fix16_exp_unittests run_fix16_str_unittests run_fix16_macros_unittests
clean:
rm -f fix16_unittests_????
rm -f fix16_str_unittests_default
rm -f fix16_str_unittests_no_ctype
rm -f fix16_exp_unittests
rm -f fix16_macros_unittests
# The library is tested automatically under different compilations
# options.
#
# Test naming:
# r = rounding, n = no rounding
# o = overflow detection, n = no overflow detection
# 64 = int64_t math, 32 = int32_t math
run_fix16_unittests: \
fix16_unittests_ro64 fix16_unittests_no64 \
fix16_unittests_rn64 fix16_unittests_nn64 \
fix16_unittests_ro32 fix16_unittests_no32 \
fix16_unittests_rn32 fix16_unittests_nn32 \
fix16_unittests_ro08 fix16_unittests_no08 \
fix16_unittests_rn08 fix16_unittests_nn08
$(foreach test, $^, \
echo $(test) && \
./$(test) > /dev/null && \
) true
fix16_unittests_no64: DEFINES=-DFIXMATH_NO_ROUNDING
fix16_unittests_rn64: DEFINES=-DFIXMATH_NO_OVERFLOW
fix16_unittests_nn64: DEFINES=-DFIXMATH_NO_ROUNDING -DFIXMATH_NO_OVERFLOW
fix16_unittests_ro32: DEFINES=-DFIXMATH_NO_64BIT
fix16_unittests_no32: DEFINES=-DFIXMATH_NO_ROUNDING -DFIXMATH_NO_64BIT
fix16_unittests_rn32: DEFINES=-DFIXMATH_NO_OVERFLOW -DFIXMATH_NO_64BIT
fix16_unittests_nn32: DEFINES=-DFIXMATH_NO_OVERFLOW -DFIXMATH_NO_ROUNDING -DFIXMATH_NO_64BIT
fix16_unittests_ro08: DEFINES=-DFIXMATH_OPTIMIZE_8BIT
fix16_unittests_no08: DEFINES=-DFIXMATH_NO_ROUNDING -DFIXMATH_OPTIMIZE_8BIT
fix16_unittests_rn08: DEFINES=-DFIXMATH_NO_OVERFLOW -DFIXMATH_OPTIMIZE_8BIT
fix16_unittests_nn08: DEFINES=-DFIXMATH_NO_OVERFLOW -DFIXMATH_NO_ROUNDING -DFIXMATH_OPTIMIZE_8BIT
fix16_str_unittests_no_ctype: DEFINES=-DFIXMATH_NO_CTYPE
fix16_unittests_% : fix16_unittests.c $(FIX16_SRC)
$(CC) $(CFLAGS) $(DEFINES) -o $@ $^ -lm
# Tests for the exponential function, run only in default config
run_fix16_exp_unittests: fix16_exp_unittests
./fix16_exp_unittests > /dev/null
fix16_exp_unittests: fix16_exp_unittests.c $(FIX16_SRC)
$(CC) $(CFLAGS) $(DEFINES) -o $@ $^ -lm
# Tests for string conversion, run only in default config and no ctype
run_fix16_str_unittests: fix16_str_unittests_default fix16_str_unittests_no_ctype
./fix16_str_unittests_default > /dev/null
./fix16_str_unittests_no_ctype > /dev/null
fix16_str_unittests_%: fix16_str_unittests.c $(FIX16_SRC)
$(CC) $(CFLAGS) $(DEFINES) -o $@ $^ -lm
# Tests for literal macros, run only in default config
run_fix16_macros_unittests: fix16_macros_unittests
./fix16_macros_unittests > /dev/null
fix16_macros_unittests: fix16_macros_unittests.c $(FIX16_SRC)
$(CC) $(CFLAGS) $(DEFINES) -o $@ $^ -lm

View File

@ -1,124 +0,0 @@
#include <libfixmath/fix16.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include "unittests.h"
#define delta(a,b) (((a)>=(b)) ? (a)-(b) : (b)-(a))
int main()
{
int status = 0;
{
COMMENT("Testing fix16_exp() corner cases");
TEST(fix16_exp(0) == fix16_one);
TEST(fix16_exp(fix16_minimum) == 0);
TEST(fix16_exp(fix16_maximum) == fix16_maximum);
}
{
COMMENT("Testing fix16_exp() accuracy over -11..4");
fix16_t max_delta = -1;
fix16_t worst = 0;
fix16_t sum = 0;
int count = 0;
fix16_t a;
for (a = fix16_from_dbl(-11.0); a < fix16_from_dbl(4.0); a += 31)
{
fix16_t result = fix16_exp(a);
fix16_t resultf = fix16_from_dbl(exp(fix16_to_dbl(a)));
fix16_t d = delta(result, resultf);
if (d > max_delta)
{
max_delta = d;
worst = a;
}
sum += d;
count++;
}
printf("Worst delta %d with input %d\n", max_delta, worst);
printf("Average delta %0.2f\n", (float)sum / count);
TEST(max_delta < 200);
}
{
COMMENT("Testing fix16_exp() accuracy over full range");
float max_delta = -1;
fix16_t worst = 0;
float sum = 0;
int count = 0;
fix16_t a;
// Test the whole range of results 0..32768 with a bit less samples
for (a = -772243; a < 681391; a += 113)
{
fix16_t result = fix16_exp(a);
fix16_t resultf = fix16_from_dbl(exp(fix16_to_dbl(a)));
fix16_t d1 = delta(result, resultf);
if (d1 > 0) d1--; // Forgive +-1 for the fix16_t inaccuracy
float d = (float)d1 / resultf * 100;
if (resultf < 1000) continue; // Percentages can explode when result is almost 0.
if (d > max_delta)
{
max_delta = d;
worst = a;
}
sum += d;
count++;
}
printf("Worst delta %0.4f%% with input %d\n", max_delta, worst);
printf("Average delta %0.4f%%\n", sum / count);
TEST(max_delta < 1);
}
{
COMMENT("Testing fix16_log() accuracy over full range");
fix16_t max_delta = -1;
fix16_t worst = 0;
fix16_t sum = 0;
int count = 0;
fix16_t a;
for (a = 100; a > 0 && a < fix16_maximum - 7561; a += 7561)
{
fix16_t result = fix16_log(a);
fix16_t resultf = fix16_from_dbl(log(fix16_to_dbl(a)));
fix16_t d = delta(result, resultf);
if (d > max_delta)
{
max_delta = d;
worst = a;
}
sum += d;
count++;
}
printf("Worst delta %d with input %d\n", max_delta, worst);
printf("Average delta %0.2f\n", (float)sum / count);
TEST(max_delta < 20);
}
if (status != 0)
fprintf(stdout, "\n\nSome tests FAILED!\n");
return status;
}

View File

@ -1,110 +0,0 @@
/* This test checks that the F16() and F16C() macros work correctly. */
#include <libfixmath/fix16.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include "unittests.h"
#define DO_TEST(i,m) \
TEST(F16(i ## . ## m) == F16C(i,m)) \
TEST(F16(i ## . ## m) == fix16_from_dbl(i ## . ## m))
int main()
{
int status = 0;
/* Corner cases */
DO_TEST(1,234)
DO_TEST(0,0)
DO_TEST(1,0)
DO_TEST(-1,0)
DO_TEST(1,5)
DO_TEST(-1,5)
DO_TEST(000000,00000)
DO_TEST(0,00001)
DO_TEST(0,00010)
DO_TEST(0,1)
DO_TEST(0,10001)
DO_TEST(0,11000)
DO_TEST(25,133)
DO_TEST(32767,00000)
DO_TEST(32767,00001)
DO_TEST(32767,99999)
DO_TEST(0,25)
DO_TEST(0,99555)
DO_TEST(0,99998)
DO_TEST(0,99999)
DO_TEST(-1,1)
DO_TEST(-25,133)
DO_TEST(-32767,00001)
DO_TEST(-32768,00000)
/* Random values */
DO_TEST( 0,02267)
DO_TEST( 1,49887)
DO_TEST( 0,27589)
DO_TEST( 0,38393)
DO_TEST( 0,08934)
DO_TEST( 0,95820)
DO_TEST( 0,95596)
DO_TEST( 72,10642)
DO_TEST( 0,48939)
DO_TEST( 3,37797)
DO_TEST( 1,09194)
DO_TEST( 0,08605)
DO_TEST( 3,04349)
DO_TEST( 3,95401)
DO_TEST( 15,36292)
DO_TEST( 56,09242)
DO_TEST( 0,54071)
DO_TEST( 27,08953)
DO_TEST( 0,03913)
DO_TEST( 1,32707)
DO_TEST( 4,50117)
DO_TEST( 0,24990)
DO_TEST( 44,77319)
DO_TEST( 2,59139)
DO_TEST( 0,16279)
DO_TEST( 17,14712)
DO_TEST( 11,54281)
DO_TEST( 0,02768)
DO_TEST( 0,39278)
DO_TEST( 0,19369)
DO_TEST( -0,04534)
DO_TEST( -0,00349)
DO_TEST( -2,30380)
DO_TEST( -0,03061)
DO_TEST( -7,50065)
DO_TEST( -3,97050)
DO_TEST( -0,43898)
DO_TEST( -3,49876)
DO_TEST( -1,35942)
DO_TEST( -10,81154)
DO_TEST( -0,26676)
DO_TEST( -9,52134)
DO_TEST( -0,42592)
DO_TEST( -0,05424)
DO_TEST( -0,62461)
DO_TEST( -0,21562)
DO_TEST( -0,22366)
DO_TEST( -0,09074)
DO_TEST( -1,29527)
DO_TEST( -4,98427)
DO_TEST( -0,10721)
DO_TEST( -11,39446)
DO_TEST(-451,53916)
DO_TEST( -0,04279)
DO_TEST( -3,36543)
DO_TEST( -0,01003)
DO_TEST( -12,08326)
DO_TEST( -1,07143)
DO_TEST( -1,07737)
DO_TEST( -0,22957)
if (status != 0)
fprintf(stdout, "\n\nSome tests FAILED!\n");
return status;
}

View File

@ -1,117 +0,0 @@
#include <libfixmath/fix16.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdbool.h>
#include "unittests.h"
int main()
{
int status = 0;
{
COMMENT("Testing fix16_to_str corner cases");
char buf[13];
fix16_to_str(fix16_from_dbl(1234.5678), buf, 4);
printf("1234.5678 = %s\n", buf);
TEST(strcmp(buf, "1234.5678") == 0);
fix16_to_str(fix16_from_dbl(-1234.5678), buf, 4);
printf("-1234.5678 = %s\n", buf);
TEST(strcmp(buf, "-1234.5678") == 0);
fix16_to_str(0, buf, 0);
TEST(strcmp(buf, "0") == 0);
fix16_to_str(fix16_from_dbl(0.9), buf, 0);
TEST(strcmp(buf, "1") == 0);
fix16_to_str(1, buf, 5);
printf("(fix16_t)1 = %s\n", buf);
TEST(strcmp(buf, "0.00002") == 0);
fix16_to_str(-1, buf, 5);
printf("(fix16_t)-1 = %s\n", buf);
TEST(strcmp(buf, "-0.00002") == 0);
fix16_to_str(65535, buf, 5);
printf("(fix16_t)65535 = %s\n", buf);
TEST(strcmp(buf, "0.99998") == 0);
fix16_to_str(65535, buf, 4);
printf("(fix16_t)65535 = %s\n", buf);
TEST(strcmp(buf, "1.0000") == 0);
fix16_to_str(fix16_maximum, buf, 5);
printf("fix16_maximum = %s\n", buf);
TEST(strcmp(buf, "32767.99998") == 0);
fix16_to_str(fix16_minimum, buf, 5);
printf("fix16_minimum = %s\n", buf);
TEST(strcmp(buf, "-32768.00000") == 0);
}
{
COMMENT("Testing fix16_from_str corner cases");
TEST(fix16_from_str("1234.5678") == fix16_from_dbl(1234.5678));
TEST(fix16_from_str("-1234.5678") == fix16_from_dbl(-1234.5678));
TEST(fix16_from_str(" +1234,56780 ") == fix16_from_dbl(1234.5678));
TEST(fix16_from_str("0") == 0);
TEST(fix16_from_str("1") == fix16_one);
TEST(fix16_from_str("1.0") == fix16_one);
TEST(fix16_from_str("1.0000000000") == fix16_one);
TEST(fix16_from_str("0.00002") == 1);
TEST(fix16_from_str("0.99998") == 65535);
TEST(fix16_from_str("32767.99998") == fix16_maximum);
TEST(fix16_from_str("-32768.00000") == fix16_minimum);
}
{
COMMENT("Extended testing for whole range");
fix16_t value = fix16_minimum;
char testbuf[13];
char goodbuf[13];
bool ok = true;
while (value < fix16_maximum)
{
double fvalue = fix16_to_dbl(value);
/* Turns out we have to jump through some hoops to round
doubles perfectly for printing:
http://stackoverflow.com/questions/994764/rounding-doubles-5-sprintf
*/
fvalue = round(fvalue * 100000.)/100000.;
snprintf(goodbuf, 13, "%0.5f", fvalue);
fix16_to_str(value, testbuf, 5);
if (strcmp(goodbuf, testbuf) != 0)
{
printf("Value (fix16_t)%d gave %s, should be %s\n", value, testbuf, goodbuf);
ok = false;
}
fix16_t roundtrip = fix16_from_str(testbuf);
if (roundtrip != value)
{
printf("Roundtrip failed: (fix16_t)%d -> %s -> (fix16_t)%d\n", value, testbuf, roundtrip);
ok = false;
}
value += 0x10001;
}
TEST(ok);
}
if (status != 0)
fprintf(stdout, "\n\nSome tests FAILED!\n");
return status;
}

View File

@ -1,391 +0,0 @@
#include <libfixmath/fix16.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include "libfixmath/int64.h"
#include "unittests.h"
const fix16_t testcases[] = {
// Small numbers
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
-1, -2, -3, -4, -5, -6, -7, -8, -9, -10,
// Integer numbers
0x10000, -0x10000, 0x20000, -0x20000, 0x30000, -0x30000,
0x40000, -0x40000, 0x50000, -0x50000, 0x60000, -0x60000,
// Fractions (1/2, 1/4, 1/8)
0x8000, -0x8000, 0x4000, -0x4000, 0x2000, -0x2000,
// Problematic carry
0xFFFF, -0xFFFF, 0x1FFFF, -0x1FFFF, 0x3FFFF, -0x3FFFF,
// Smallest and largest values
0x7FFFFFFF, 0x80000000,
// Large random numbers
831858892, 574794913, 2147272293, -469161054, -961611615,
1841960234, 1992698389, 520485404, 560523116, -2094993050,
-876897543, -67813629, 2146227091, 509861939, -1073573657,
// Small random numbers
-14985, 30520, -83587, 41129, 42137, 58537, -2259, 84142,
-28283, 90914, 19865, 33191, 81844, -66273, -63215, -44459,
-11326, 84295, 47515, -39324,
// Tiny random numbers
-171, -359, 491, 844, 158, -413, -422, -737, -575, -330,
-376, 435, -311, 116, 715, -1024, -487, 59, 724, 993
};
#define TESTCASES_COUNT (sizeof(testcases)/sizeof(testcases[0]))
#define delta(a,b) (((a)>=(b)) ? (a)-(b) : (b)-(a))
#ifdef FIXMATH_NO_ROUNDING
const fix16_t max_delta = 1;
#else
const fix16_t max_delta = 0;
#endif
int main()
{
int status = 0;
{
COMMENT("Testing basic multiplication");
TEST(fix16_mul(fix16_from_int(5), fix16_from_int(5)) == fix16_from_int(25));
TEST(fix16_mul(fix16_from_int(-5), fix16_from_int(5)) == fix16_from_int(-25));
TEST(fix16_mul(fix16_from_int(-5), fix16_from_int(-5)) == fix16_from_int(25));
TEST(fix16_mul(fix16_from_int(5), fix16_from_int(-5)) == fix16_from_int(-25));
}
#ifndef FIXMATH_NO_ROUNDING
{
COMMENT("Testing multiplication rounding corner cases");
TEST(fix16_mul(0, 10) == 0);
TEST(fix16_mul(2, 0x8000) == 1);
TEST(fix16_mul(-2, 0x8000) == -1);
TEST(fix16_mul(3, 0x8000) == 2);
TEST(fix16_mul(-3, 0x8000) == -2);
TEST(fix16_mul(2, 0x7FFF) == 1);
TEST(fix16_mul(-2, 0x7FFF) == -1);
TEST(fix16_mul(2, 0x8001) == 1);
TEST(fix16_mul(-2, 0x8001) == -1);
}
#endif
{
unsigned int i, j;
int failures = 0;
COMMENT("Running testcases for multiplication");
for (i = 0; i < TESTCASES_COUNT; i++)
{
for (j = 0; j < TESTCASES_COUNT; j++)
{
fix16_t a = testcases[i];
fix16_t b = testcases[j];
fix16_t result = fix16_mul(a, b);
double fa = fix16_to_dbl(a);
double fb = fix16_to_dbl(b);
fix16_t fresult = fix16_from_dbl(fa * fb);
double max = fix16_to_dbl(fix16_maximum);
double min = fix16_to_dbl(fix16_minimum);
if (delta(fresult, result) > max_delta)
{
if (fa * fb > max || fa * fb < min)
{
#ifndef FIXMATH_NO_OVERFLOW
if (result != fix16_overflow)
{
printf("\n%d * %d overflow not detected!\n", a, b);
failures++;
}
#endif
// Legitimate overflow
continue;
}
printf("\n%d * %d = %d\n", a, b, result);
printf("%f * %f = %d\n", fa, fb, fresult);
failures++;
}
}
}
TEST(failures == 0);
}
{
COMMENT("Testing basic division");
TEST(fix16_div(fix16_from_int(15), fix16_from_int(5)) == fix16_from_int(3));
TEST(fix16_div(fix16_from_int(-15), fix16_from_int(5)) == fix16_from_int(-3));
TEST(fix16_div(fix16_from_int(-15), fix16_from_int(-5)) == fix16_from_int(3));
TEST(fix16_div(fix16_from_int(15), fix16_from_int(-5)) == fix16_from_int(-3));
}
#ifndef FIXMATH_NO_ROUNDING
{
COMMENT("Testing division rounding corner cases");
TEST(fix16_div(0, 10) == 0);
TEST(fix16_div(1, fix16_from_int(2)) == 1);
TEST(fix16_div(-1, fix16_from_int(2)) == -1);
TEST(fix16_div(1, fix16_from_int(-2)) == -1);
TEST(fix16_div(-1, fix16_from_int(-2)) == 1);
TEST(fix16_div(3, fix16_from_int(2)) == 2);
TEST(fix16_div(-3, fix16_from_int(2)) == -2);
TEST(fix16_div(3, fix16_from_int(-2)) == -2);
TEST(fix16_div(-3, fix16_from_int(-2)) == 2);
TEST(fix16_div(2, 0x7FFF) == 4);
TEST(fix16_div(-2, 0x7FFF) == -4);
TEST(fix16_div(2, 0x8001) == 4);
TEST(fix16_div(-2, 0x8001) == -4);
}
#endif
{
unsigned int i, j;
int failures = 0;
COMMENT("Running testcases for division");
for (i = 0; i < TESTCASES_COUNT; i++)
{
for (j = 0; j < TESTCASES_COUNT; j++)
{
fix16_t a = testcases[i];
fix16_t b = testcases[j];
// We don't require a solution for /0 :)
if (b == 0) continue;
fix16_t result = fix16_div(a, b);
double fa = fix16_to_dbl(a);
double fb = fix16_to_dbl(b);
fix16_t fresult = fix16_from_dbl(fa / fb);
double max = fix16_to_dbl(fix16_maximum);
double min = fix16_to_dbl(fix16_minimum);
if (delta(fresult, result) > max_delta)
{
if (((fa / fb) > max) || ((fa / fb) < min))
{
#ifndef FIXMATH_NO_OVERFLOW
if (result != fix16_overflow)
{
printf("\n%d / %d overflow not detected!\n", a, b);
failures++;
}
#endif
// Legitimate overflow
continue;
}
printf("\n%f / %f = %f\n", fix16_to_dbl(a), fix16_to_dbl(b), fix16_to_dbl(fresult));
printf("%f / %f = %f\n", fa, fb, (fa / fb));
failures++;
}
}
}
TEST(failures == 0);
}
{
unsigned int i, j;
int failures = 0;
COMMENT("Running testcases for addition");
for (i = 0; i < TESTCASES_COUNT; i++)
{
for (j = 0; j < TESTCASES_COUNT; j++)
{
fix16_t a = testcases[i];
fix16_t b = testcases[j];
fix16_t result = fix16_add(a, b);
double fa = fix16_to_dbl(a);
double fb = fix16_to_dbl(b);
fix16_t fresult = fix16_from_dbl(fa + fb);
double max = fix16_to_dbl(fix16_maximum);
double min = fix16_to_dbl(fix16_minimum);
if (delta(fresult, result) > max_delta)
{
if (fa + fb > max || fa + fb < min)
{
#ifndef FIXMATH_NO_OVERFLOW
if (result != fix16_overflow)
{
printf("\n%d + %d overflow not detected!\n", a, b);
failures++;
}
#endif
// Legitimate overflow
continue;
}
printf("\n%d + %d = %d\n", a, b, result);
printf("%f + %f = %d\n", fa, fb, fresult);
failures++;
}
}
}
TEST(failures == 0);
}
{
unsigned int i, j;
int failures = 0;
COMMENT("Running testcases for subtraction");
for (i = 0; i < TESTCASES_COUNT; i++)
{
for (j = 0; j < TESTCASES_COUNT; j++)
{
fix16_t a = testcases[i];
fix16_t b = testcases[j];
fix16_t result = fix16_sub(a, b);
double fa = fix16_to_dbl(a);
double fb = fix16_to_dbl(b);
fix16_t fresult = fix16_from_dbl(fa - fb);
double max = fix16_to_dbl(fix16_maximum);
double min = fix16_to_dbl(fix16_minimum);
if (delta(fresult, result) > max_delta)
{
if (fa - fb > max || fa - fb < min)
{
#ifndef FIXMATH_NO_OVERFLOW
if (result != fix16_overflow)
{
printf("\n%d - %d overflow not detected!\n", a, b);
failures++;
}
#endif
// Legitimate overflow
continue;
}
printf("\n%d - %d = %d\n", a, b, result);
printf("%f - %f = %d\n", fa, fb, fresult);
failures++;
}
}
}
TEST(failures == 0);
}
{
COMMENT("Testing basic square roots");
TEST(fix16_sqrt(fix16_from_int(16)) == fix16_from_int(4));
TEST(fix16_sqrt(fix16_from_int(100)) == fix16_from_int(10));
TEST(fix16_sqrt(fix16_from_int(1)) == fix16_from_int(1));
}
#ifndef FIXMATH_NO_ROUNDING
{
COMMENT("Testing square root rounding corner cases");
TEST(fix16_sqrt(214748302) == 3751499);
TEST(fix16_sqrt(214748303) == 3751499);
TEST(fix16_sqrt(214748359) == 3751499);
TEST(fix16_sqrt(214748360) == 3751500);
}
#endif
{
unsigned int i;
int failures = 0;
COMMENT("Running test cases for square root");
for (i = 0; i < TESTCASES_COUNT; i++)
{
fix16_t a = testcases[i];
if (a < 0) continue;
fix16_t result = fix16_sqrt(a);
double fa = fix16_to_dbl(a);
fix16_t fresult = fix16_from_dbl(sqrt(fa));
if (delta(fresult, result) > max_delta)
{
printf("\nfix16_sqrt(%d) = %d\n", a, result);
printf("sqrt(%f) = %d\n", fa, fresult);
failures++;
}
}
TEST(failures == 0);
}
{
COMMENT("Running int64 test cases");
TEST(int64_cmp_eq(int64_const(0,1), int64_from_int32(1)));
TEST(int64_cmp_eq(int64_const(0xffffffff,0xfffffffe), int64_from_int32(-2)));
TEST(int64_cmp_eq(int64_const(1,0), int64_shift(int64_from_int32(1),32)));
TEST(int64_cmp_eq(int64_const(1,0), int64_shift(int64_from_int32(2),31)));
TEST(int64_cmp_eq(int64_const(0,(1<<31)), int64_shift(int64_from_int32(1),31)));
TEST(int64_cmp_eq(int64_const(-2,0), int64_shift(int64_from_int32(-1),33)));
TEST(int64_cmp_eq(int64_const(0,1), int64_shift(int64_const(0,2),-1)));
TEST(int64_cmp_eq(int64_const(2,1), int64_shift(int64_const(4,2),-1)));
TEST(int64_cmp_eq(int64_const(0,(1<<31)), int64_shift(int64_const(1,0),-1)));
TEST(int64_cmp_eq(int64_const(0,4), int64_shift(int64_const(2,0),-31)));
TEST(int64_cmp_eq(int64_const(0,2), int64_shift(int64_const(2,0),-32)));
TEST(int64_cmp_eq(int64_const(0,1), int64_shift(int64_const(2,0),-33)));
int64_t bit31 = int64_const(0, 0x80000000);
int64_t negbit31 = int64_const(-1, 0x80000000);
TEST(int64_cmp_eq(negbit31, int64_neg(bit31)));
TEST(int64_cmp_eq(int64_const(1,0), int64_mul_i64_i32(bit31, 2)));
TEST(int64_cmp_eq(int64_const(-1,(1<<31)), int64_mul_i64_i32(bit31, -1)));
TEST(int64_cmp_eq(int64_mul_i64_i32(int64_const(0,-1), fix16_maximum),
int64_const(0x7ffffffe, 0x80000001)));
TEST(int64_cmp_eq(int64_mul_i64_i32(int64_const(0,1), fix16_minimum),
int64_const(-1, fix16_minimum)));
}
{
COMMENT("Running linear interpolation test cases");
TEST(fix16_lerp8(0, 2, 0) == 0);
TEST(fix16_lerp8(0, 2, 127) == 0);
TEST(fix16_lerp8(0, 2, 128) == 1);
TEST(fix16_lerp8(0, 2, 255) == 1);
TEST(fix16_lerp8(fix16_minimum, fix16_maximum, 0) == fix16_minimum);
TEST(fix16_lerp8(fix16_minimum, fix16_maximum, 255) == (fix16_maximum - (1<<24)));
TEST(fix16_lerp8(-fix16_maximum, fix16_maximum, 128) == 0);
TEST(fix16_lerp16(0, 2, 0) == 0);
TEST(fix16_lerp16(0, 2, 0x7fff) == 0);
TEST(fix16_lerp16(0, 2, 0x8000) == 1);
TEST(fix16_lerp16(0, 2, 0xffff) == 1);
TEST(fix16_lerp16(fix16_minimum, fix16_maximum, 0) == fix16_minimum);
TEST(fix16_lerp16(fix16_minimum, fix16_maximum, 0xffff) == (fix16_maximum - (1<<16)));
TEST(fix16_lerp16(-fix16_maximum, fix16_maximum, 0x8000) == 0);
TEST(fix16_lerp32(0, 2, 0) == 0);
TEST(fix16_lerp32(0, 2, 0x7fffffff) == 0);
TEST(fix16_lerp32(0, 2, 0x80000000) == 1);
TEST(fix16_lerp32(0, 2, 0xffffffff) == 1);
TEST(fix16_lerp32(fix16_minimum, fix16_maximum, 0) == fix16_minimum);
TEST(fix16_lerp32(fix16_minimum, fix16_maximum, 0xffffffff) == (fix16_maximum - 1));
TEST(fix16_lerp32(-fix16_maximum, fix16_maximum, 0x80000000) == 0);
}
if (status != 0)
fprintf(stdout, "\n\nSome tests FAILED!\n");
return status;
}

View File

@ -1,16 +0,0 @@
#!/bin/bash
fail=0
pass=0
for f in fix16_unittests_* fix16_exp_unittests fix16_macros_unittests fix16_str_unittests_*
do
if ! ./$f >> /dev/null; then
echo "./$f failed!"
((fail=fail+1))
else
((pass=pass+1))
fi
done
echo "$fail tests failed, $pass tests passed."

View File

@ -1,18 +0,0 @@
#include <stdio.h>
#define COMMENT(x) printf("\n----" x "----\n");
#define STR(x) #x
#define STR2(x) STR(x)
#define TEST(x) \
if (!(x)) { \
fflush(stdout); \
fflush(stderr); \
fprintf(stderr, "\033[31;1mFAILED:\033[22;39m " __FILE__ ":" STR2(__LINE__) " " #x "\n"); \
status = 1; \
} else { \
fflush(stdout); \
fflush(stderr); \
printf("\033[32;1mOK:\033[22;39m " #x "\n"); \
}