aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPetteri.Aimonen <Petteri.Aimonen@gmail.com>2012-08-29 14:38:36 +0000
committerPetteri.Aimonen <Petteri.Aimonen@gmail.com>2012-08-29 14:38:36 +0000
commit543e39f42d00fbaa3dfd7a2ed19e3ea84e4f19ee (patch)
treefe1dab9907b4679feefb25da84da5b69722d80c5
parentcb1744b88691fbb6db938d702c87dd40929efb42 (diff)
Added to/from string conversion functions, and testcases for them.
-rw-r--r--libfixmath/fix16.h11
-rw-r--r--libfixmath/fix16_str.c114
-rw-r--r--unittests/Makefile12
-rw-r--r--unittests/fix16_str_unittests.c117
4 files changed, 252 insertions, 2 deletions
diff --git a/libfixmath/fix16.h b/libfixmath/fix16.h
index bab7254..3b41d2c 100644
--- a/libfixmath/fix16.h
+++ b/libfixmath/fix16.h
@@ -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"
diff --git a/libfixmath/fix16_str.c b/libfixmath/fix16_str.c
new file mode 100644
index 0000000..eff906f
--- /dev/null
+++ b/libfixmath/fix16_str.c
@@ -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;
+}
+
diff --git a/unittests/Makefile b/unittests/Makefile
index cb5dbb4..14dac10 100644
--- a/unittests/Makefile
+++ b/unittests/Makefile
@@ -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
+
diff --git a/unittests/fix16_str_unittests.c b/unittests/fix16_str_unittests.c
new file mode 100644
index 0000000..5a5bf94
--- /dev/null
+++ b/unittests/fix16_str_unittests.c
@@ -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;
+}