forked from Lephenixnoir/Azur
libnum: add experimental string representation
Currently only has hand-picked tests and very rough code, but it's a start. In the future, I want to have better tests, more options like printf's %e/%f/%g, and more versatile methods.
This commit is contained in:
parent
ec1593d4f8
commit
1d8851cbf5
|
@ -21,15 +21,23 @@ install(DIRECTORY include/ DESTINATION ${INCDIR})
|
|||
# Unit tests
|
||||
#---
|
||||
|
||||
set(UNIT_TESTS
|
||||
set(UNIT_TESTS_SCALAR
|
||||
test/unit_scalar.cpp
|
||||
test/unit_static.cpp)
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
add_executable(numtest ${UNIT_TESTS})
|
||||
target_link_libraries(numtest PUBLIC num)
|
||||
set(UNIT_TESTS_STR
|
||||
test/unit_str.cpp)
|
||||
|
||||
add_test(NAME "UnitTests" COMMAND numtest)
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
add_executable(numtest_scalar ${UNIT_TESTS_SCALAR})
|
||||
target_link_libraries(numtest_scalar PUBLIC num)
|
||||
add_test(NAME "UnitTestsScalar" COMMAND numtest_scalar)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
add_executable(numtest_str ${UNIT_TESTS_STR})
|
||||
target_link_libraries(numtest_str PUBLIC num)
|
||||
add_test(NAME "UnitTestsStr" COMMAND numtest_str)
|
||||
endif()
|
||||
|
||||
#---
|
||||
|
|
|
@ -5,10 +5,37 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
// num.num: Fixed-point numerical types
|
||||
//
|
||||
// This header provides numerical types of various fixed-point sizes. The base
|
||||
// type num is num32, and other data structures outside of this header
|
||||
// (vectors, matrices, etc.) default to it. Other types are useful for storage
|
||||
// and sometimes intermediate computation steps.
|
||||
// This header provides fixed-point numerical types with the following shapes:
|
||||
// num8: 0: 8, unsigned (only values 0.x)
|
||||
// num16: 8: 8, signed (from -128 to 127.x)
|
||||
// num32: 16:16, signed (from -32768 to 32767.x)
|
||||
// num64: 32:32, signed (from -2147483648 to 2147483647.x)
|
||||
//
|
||||
// The canonical type for computations is `num`, which is an alias for num32.
|
||||
//
|
||||
// Type safety is enforced by never allowing implicit casts whenever that would
|
||||
// narrow the range. To convert a num64 to a num32 or an int to a num16, the
|
||||
// constructor must be called explicitly. This means that overflows can only
|
||||
// happen during computation and explicit conversion, which are easier to track
|
||||
// down and check than implicit conversions.
|
||||
//
|
||||
// TODO: Currently all constructors are explicit, even eg. num8 -> num16
|
||||
//
|
||||
// The API for each fixed-point type consists of:
|
||||
// - Explicit constructors and cast operators to convert to and from integers,
|
||||
// floating-point types and other fixed-point types.
|
||||
// - Minimum and maximum values as int (minInt, maxInt) and double (minDouble,
|
||||
// maxDouble). Note that num64 values don't fit in a double so the accuracy
|
||||
// of the latter two isn't perfect.
|
||||
// - Basic arithmetic (+, -, *, /, %, +=, -=, *=, /=, %=).
|
||||
// - Comparisons with itself and with int (range-safe).
|
||||
// - Conversions to strings:
|
||||
// * strToBuffer(): appends the string representation of the value and a NUL
|
||||
// byte to a char *; returns the number of characters (excluding the NUL).
|
||||
// (TODO: experimental)
|
||||
// * TODO: Other string conversion functions with more options
|
||||
// - floor(), ceil() and frac() methods
|
||||
// - TODO: More functions to do the equivalent of <math.h> without floats
|
||||
//---
|
||||
|
||||
/* TODO: Conversion with float/double: use the binary format efficiently
|
||||
|
@ -127,6 +154,9 @@ struct num8
|
|||
/* Limits as double */
|
||||
static constexpr double minDouble = 0.0;
|
||||
static constexpr double maxDouble = double(0xff) / 256;
|
||||
|
||||
/* String representations */
|
||||
int strToBuffer(char *str);
|
||||
};
|
||||
|
||||
/* num16: Signed 8:8 fixed-point type
|
||||
|
@ -231,6 +261,9 @@ struct num16
|
|||
/* Limits as double */
|
||||
static constexpr double minDouble = -128.0;
|
||||
static constexpr double maxDouble = double(0x7fff) / 256;
|
||||
|
||||
/* String representations */
|
||||
int strToBuffer(char *str);
|
||||
};
|
||||
|
||||
/* num32: Signed 16:16 fixed-point type
|
||||
|
@ -337,6 +370,9 @@ struct num32
|
|||
/* Limits as double */
|
||||
static constexpr double minDouble = -32768.0;
|
||||
static constexpr double maxDouble = double(0x7fffffff) / 65536;
|
||||
|
||||
/* String representations */
|
||||
int strToBuffer(char *str);
|
||||
};
|
||||
|
||||
/* Arithmetic with integers */
|
||||
|
@ -434,6 +470,9 @@ struct num64
|
|||
represent the entirety of the maximum value. */
|
||||
static constexpr double minDouble = -2147483648.0;
|
||||
static constexpr double maxDouble = 2147483648.0 - double(1) / 2147483648;
|
||||
|
||||
/* String representations */
|
||||
int strToBuffer(char *str);
|
||||
};
|
||||
|
||||
/* The following concept identifies the four num types */
|
||||
|
|
|
@ -1,10 +1,88 @@
|
|||
#include <num/num.h>
|
||||
#include <stdio.h>
|
||||
using namespace libnum;
|
||||
|
||||
/* Digits of the decimal part, from most to least significant. Returns the
|
||||
number of digits (which is 0 when x=0) */
|
||||
static int decimal_digits(char *str, num64 x)
|
||||
number of digits (which is 0 when x=0). The operand should be nonnegative.
|
||||
The string should have room for 32 characters; no NUL is added. */
|
||||
template<typename T, typename I, int N> requires(is_num<T> && N >= 0)
|
||||
static int decimalDigitsBase(char *str, T x)
|
||||
{
|
||||
// x = mod_64(x, num64_const(1));
|
||||
I v = x.frac().v;
|
||||
int i = 0;
|
||||
|
||||
while(v != 0) {
|
||||
v *= 10;
|
||||
I digit = v >> N;
|
||||
str[i++] = (v >> N) + '0';
|
||||
v -= digit << N;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
template<typename T> requires(is_num<T>)
|
||||
static int decimalDigits(char *str, T x) {
|
||||
return 0;
|
||||
}
|
||||
template<> int decimalDigits<num8>(char *str, num8 x) {
|
||||
return decimalDigitsBase<num8, int, 8>(str, x);
|
||||
}
|
||||
template<> int decimalDigits<num16>(char *str, num16 x) {
|
||||
return decimalDigitsBase<num16, int, 8>(str, x);
|
||||
}
|
||||
template<> int decimalDigits<num32>(char *str, num32 x) {
|
||||
return decimalDigitsBase<num32, int, 16>(str, x);
|
||||
}
|
||||
template<> int decimalDigits<num64>(char *str, num64 x) {
|
||||
return decimalDigitsBase<num64, int64_t, 32>(str, x);
|
||||
}
|
||||
|
||||
/* TODO: Complex string representations like %f/%e/%g
|
||||
Generates the string representation of x in str; returns the number of
|
||||
characters written. A NUL terminator is added (but not counted in the return
|
||||
value). A total of 45 bytes is required for the longest 64-bit value. */
|
||||
template<typename T> requires(is_num<T>)
|
||||
static int toString(char *str, T x)
|
||||
{
|
||||
int n = 0;
|
||||
/* We need to be able to represent the opposite of INT_MIN */
|
||||
int64_t integral_part = (int)x;
|
||||
|
||||
if(x.v == 0) {
|
||||
str[0] = '0';
|
||||
str[1] = 0;
|
||||
return 1;
|
||||
}
|
||||
if(x.v < 0) {
|
||||
*str = '-';
|
||||
n++;
|
||||
/* This might overflow, which is why we separated the integral part */
|
||||
x = -x;
|
||||
integral_part = -integral_part - (x.frac().v != 0);
|
||||
}
|
||||
|
||||
n += sprintf(str + n, "%ld", integral_part);
|
||||
if(x.frac().v != 0) {
|
||||
str[n++] = '.';
|
||||
n += decimalDigits<T>(str + n, x);
|
||||
}
|
||||
str[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
int num8::strToBuffer(char *str)
|
||||
{
|
||||
return toString<num8>(str, *this);
|
||||
}
|
||||
int num16::strToBuffer(char *str)
|
||||
{
|
||||
return toString<num16>(str, *this);
|
||||
}
|
||||
int num32::strToBuffer(char *str)
|
||||
{
|
||||
return toString<num32>(str, *this);
|
||||
}
|
||||
int num64::strToBuffer(char *str)
|
||||
{
|
||||
return toString<num64>(str, *this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
// ," /\ ", Azur: A game engine for CASIO fx-CG and PC //
|
||||
// | _/__\_ | Designed by Lephe' and the Planète Casio community. //
|
||||
// "._`\/'_." License: MIT <https://opensource.org/licenses/MIT> //
|
||||
//---------------------------------------------------------------------------//
|
||||
// unit_str.cpp: Unit tests for string representations
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "unit_sample.h"
|
||||
#include "unit_check.h"
|
||||
|
||||
#define CHECK(EXPR, MSG) check(EXPR, #MSG)
|
||||
#define CHECK_STR(value, exp) \
|
||||
CHECK(({ value.strToBuffer(_str); !strcmp(_str, exp); }), value: exp)
|
||||
|
||||
bool runBasicTest(void)
|
||||
{
|
||||
char _str[128];
|
||||
Checker c;
|
||||
|
||||
c.CHECK_STR(num8(0), "0");
|
||||
c.CHECK_STR(num16(1), "1");
|
||||
c.CHECK_STR(num16(-1), "-1");
|
||||
c.CHECK_STR(num16(-0.25), "-0.25");
|
||||
c.CHECK_STR(num8(0.5), "0.5");
|
||||
c.CHECK_STR(num8(0.25), "0.25");
|
||||
c.CHECK_STR(num32(0.53125), "0.53125");
|
||||
c.CHECK_STR(num32(-73), "-73");
|
||||
c.CHECK_STR(num32(-5329), "-5329");
|
||||
c.CHECK_STR(num32(-32767), "-32767");
|
||||
c.CHECK_STR(num8(0.99609375), "0.99609375");
|
||||
c.CHECK_STR(num16(-127), "-127");
|
||||
c.CHECK_STR(num16(-30.6015625), "-30.6015625");
|
||||
c.CHECK_STR(num16(-128), "-128"); /* Overflow on -128 --> 128 after sign */
|
||||
|
||||
num64 x64;
|
||||
x64.v = 1;
|
||||
c.CHECK_STR(x64, "0.00000000023283064365386962890625");
|
||||
x64.v = -1;
|
||||
c.CHECK_STR(x64, "-0.00000000023283064365386962890625");
|
||||
|
||||
return c.successful();
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
printf("Testing a handful of pre-written inputs...\n");
|
||||
success &= runBasicTest();
|
||||
|
||||
return (success ? 0 : 1);
|
||||
}
|
Loading…
Reference in New Issue