Added to/from string conversion functions, and testcases for them.
This commit is contained in:
parent
cb1744b886
commit
543e39f42d
|
@ -208,6 +208,17 @@ extern fix16_t fix16_log2(fix16_t x) FIXMATH_FUNC_ATTRS;
|
|||
*/
|
||||
extern fix16_t fix16_slog2(fix16_t x) FIXMATH_FUNC_ATTRS;
|
||||
|
||||
/*! Convert fix16_t value to a string.
|
||||
* Required buffer length for largest values is 13 bytes.
|
||||
*/
|
||||
extern void fix16_to_str(fix16_t value, char *buf, int decimals);
|
||||
|
||||
/*! Convert string to a fix16_t value
|
||||
* Ignores spaces at beginning and end. Returns fix16_overflow if
|
||||
* value is too large or there were garbage characters.
|
||||
*/
|
||||
extern fix16_t fix16_from_str(const char *buf);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#include "fix16.hpp"
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
#include "fix16.h"
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static const uint32_t scales[8] = {
|
||||
/* 5 decimals is enough for full fix16_t precision */
|
||||
1, 10, 100, 1000, 10000, 100000, 100000, 100000
|
||||
};
|
||||
|
||||
static char *itoa_loop(char *buf, uint32_t scale, uint32_t value, bool skip)
|
||||
{
|
||||
while (scale)
|
||||
{
|
||||
unsigned digit = (value / scale);
|
||||
|
||||
if (!skip || digit || scale == 1)
|
||||
{
|
||||
skip = false;
|
||||
*buf++ = '0' + digit;
|
||||
value %= scale;
|
||||
}
|
||||
|
||||
scale /= 10;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
void fix16_to_str(fix16_t value, char *buf, int decimals)
|
||||
{
|
||||
uint32_t uvalue = (value >= 0) ? value : -value;
|
||||
if (value < 0)
|
||||
*buf++ = '-';
|
||||
|
||||
/* Separate the integer and decimal parts of the value */
|
||||
unsigned intpart = uvalue >> 16;
|
||||
uint32_t fracpart = uvalue & 0xFFFF;
|
||||
uint32_t scale = scales[decimals & 7];
|
||||
fracpart = fix16_mul(fracpart, scale);
|
||||
|
||||
if (fracpart >= scale)
|
||||
{
|
||||
/* Handle carry from decimal part */
|
||||
intpart++;
|
||||
fracpart -= scale;
|
||||
}
|
||||
|
||||
/* Format integer part */
|
||||
buf = itoa_loop(buf, 10000, intpart, true);
|
||||
|
||||
/* Format decimal part (if any) */
|
||||
if (scale != 1)
|
||||
{
|
||||
*buf++ = '.';
|
||||
buf = itoa_loop(buf, scale / 10, fracpart, false);
|
||||
}
|
||||
|
||||
*buf = '\0';
|
||||
}
|
||||
|
||||
fix16_t fix16_from_str(const char *buf)
|
||||
{
|
||||
while (isspace(*buf))
|
||||
buf++;
|
||||
|
||||
/* Decode the sign */
|
||||
bool negative = (*buf == '-');
|
||||
if (*buf == '+' || *buf == '-')
|
||||
buf++;
|
||||
|
||||
/* Decode the integer part */
|
||||
uint32_t intpart = 0;
|
||||
int count = 0;
|
||||
while (isdigit(*buf))
|
||||
{
|
||||
intpart *= 10;
|
||||
intpart += *buf++ - '0';
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == 0 || count > 5
|
||||
|| intpart > 32768 || (!negative && intpart > 32767))
|
||||
return fix16_overflow;
|
||||
|
||||
fix16_t value = intpart << 16;
|
||||
|
||||
/* Decode the decimal part */
|
||||
if (*buf == '.' || *buf == ',')
|
||||
{
|
||||
buf++;
|
||||
|
||||
uint32_t fracpart = 0;
|
||||
uint32_t scale = 1;
|
||||
while (isdigit(*buf) && scale < 100000)
|
||||
{
|
||||
scale *= 10;
|
||||
fracpart *= 10;
|
||||
fracpart += *buf++ - '0';
|
||||
}
|
||||
|
||||
value += fix16_div(fracpart, scale);
|
||||
}
|
||||
|
||||
/* Verify that there is no garbage left over */
|
||||
while (*buf != '\0')
|
||||
{
|
||||
if (!isdigit(*buf) && !isspace(*buf))
|
||||
return fix16_overflow;
|
||||
|
||||
buf++;
|
||||
}
|
||||
|
||||
return negative ? -value : value;
|
||||
}
|
||||
|
|
@ -5,10 +5,10 @@ CC = gcc
|
|||
CFLAGS = -g -O0 -I../libfixmath -Wall -Wextra -Werror
|
||||
|
||||
# The files required for tests
|
||||
FIX16_SRC = ../libfixmath/fix16.c ../libfixmath/fix16_sqrt.c \
|
||||
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
|
||||
all: run_fix16_unittests run_fix16_exp_unittests run_fix16_str_unittests
|
||||
|
||||
clean:
|
||||
rm -f fix16_unittests_????
|
||||
|
@ -55,3 +55,11 @@ run_fix16_exp_unittests: fix16_exp_unittests
|
|||
|
||||
fix16_exp_unittests: fix16_exp_unittests.c $(FIX16_SRC)
|
||||
$(CC) $(CFLAGS) $(DEFINES) -o $@ $^ -lm
|
||||
|
||||
# Tests for string conversion, run only in default config
|
||||
run_fix16_str_unittests: fix16_str_unittests
|
||||
./fix16_str_unittests > /dev/null
|
||||
|
||||
fix16_str_unittests: fix16_str_unittests.c $(FIX16_SRC)
|
||||
$(CC) $(CFLAGS) $(DEFINES) -o $@ $^ -lm
|
||||
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
#include <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.5lf", 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;
|
||||
}
|
Loading…
Reference in New Issue