libnum: unit tests, perf tests for num16, double limits
* Add a unit testing framework in libnum/test/. Assertions are checked against sparse sets of input values (a couple thousands for each type), distributed fractally. * Add performance tests for num16. * Fix an overly ambitious substitution of /256 by >>8 in num16::mul, which would give some incorrect results for negative results. * Also fix an incorrect sign extension in the num16->num32 conversion. * Express comparison-with-int operators in terms of the integer even though some versions are faster when expressed in terms of the fixed- point value. This is because the integer is frequently known at compile-time. * Add minDouble and maxDouble static members to each num type to programmatically supply the bounds of the type.
This commit is contained in:
parent
967eb034f4
commit
708ba1b017
|
@ -8,9 +8,9 @@
|
|||
include(CTest)
|
||||
|
||||
add_library(num STATIC
|
||||
src/static_checks.cpp)
|
||||
src/str.cpp)
|
||||
|
||||
target_include_directories(num PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_include_directories(num PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
|
||||
# Library file: libnum.a
|
||||
install(TARGETS num DESTINATION ${LIBDIR})
|
||||
|
@ -18,13 +18,29 @@ install(TARGETS num DESTINATION ${LIBDIR})
|
|||
install(DIRECTORY include/ DESTINATION ${INCDIR})
|
||||
|
||||
#---
|
||||
# Testing
|
||||
# Unit tests
|
||||
#---
|
||||
|
||||
set(TESTS
|
||||
test/isel_num8.cpp)
|
||||
set(UNIT_TESTS
|
||||
test/unit_scalar.cpp
|
||||
test/unit_static.cpp)
|
||||
|
||||
foreach(testfile IN LISTS TESTS)
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
add_executable(numtest ${UNIT_TESTS})
|
||||
target_link_libraries(numtest PUBLIC num)
|
||||
|
||||
add_test(NAME "UnitTests" COMMAND numtest)
|
||||
endif()
|
||||
|
||||
#---
|
||||
# Performance tests
|
||||
#---
|
||||
|
||||
set(PERF_TESTS
|
||||
test/isel_num8.cpp
|
||||
test/isel_num16.cpp)
|
||||
|
||||
foreach(testfile IN LISTS PERF_TESTS)
|
||||
add_test(NAME "${testfile}"
|
||||
COMMAND python "${CMAKE_CURRENT_SOURCE_DIR}/test/isel.py"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${testfile}"
|
||||
|
|
|
@ -19,10 +19,6 @@
|
|||
General idea for an fp -> num conversion:
|
||||
1. Literally just shift mantissa by exponent - num_fixed_position */
|
||||
|
||||
/* TODO: Template specializations for std::integral_constant<int, VALUE> that
|
||||
inlines at compile time to either (1) true/false if out of bounds, or (2)
|
||||
coerce the int to the fixed point type */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
@ -103,15 +99,19 @@ struct num8
|
|||
inline constexpr bool operator<(int const &i) {
|
||||
return i >= 1;
|
||||
}
|
||||
inline constexpr bool operator>=(int const &i) {
|
||||
return i <= 0;
|
||||
}
|
||||
inline constexpr bool operator<=(int const &i) {
|
||||
return i + !v > 0;
|
||||
}
|
||||
inline constexpr bool operator>(int const &i) {
|
||||
return i + !v <= 0;
|
||||
}
|
||||
inline constexpr bool operator>=(int const &i) {
|
||||
return i <= 0;
|
||||
}
|
||||
|
||||
/* Limits as double */
|
||||
static constexpr double minDouble = 0.0;
|
||||
static constexpr double maxDouble = double(0xff) / 256;
|
||||
};
|
||||
|
||||
/* num16: Signed 8:8 fixed-point type
|
||||
|
@ -148,7 +148,7 @@ struct num16
|
|||
inline constexpr explicit operator double() { return (double)v / 256; }
|
||||
|
||||
/* num16 x num16 -> num32 multiplication
|
||||
This is efficiently implemented with a muls.l instruction. */
|
||||
This is efficiently implemented with a muls.w instruction. */
|
||||
static constexpr num32 dmul(num16 const &x, num16 const &y);
|
||||
|
||||
/* Basic arithmetic */
|
||||
|
@ -162,7 +162,7 @@ struct num16
|
|||
return *this;
|
||||
}
|
||||
inline constexpr num16 &operator*=(num16 const &other) {
|
||||
v = (v * other.v) >> 8;
|
||||
v = (v * other.v) / 256;
|
||||
return *this;
|
||||
}
|
||||
inline constexpr num16 &operator/=(num16 const &other) {
|
||||
|
@ -177,20 +177,27 @@ struct num16
|
|||
/* Comparisons with int */
|
||||
|
||||
inline constexpr bool operator==(int const &i) {
|
||||
return ((v & 0xff) == 0) && (v >> 8) == i;
|
||||
return (int16_t)i == i && (i << 8) == v;
|
||||
}
|
||||
inline constexpr bool operator<(int const &i) {
|
||||
return (v >> 8) < i;
|
||||
}
|
||||
inline constexpr bool operator>=(int const &i) {
|
||||
return (v >> 8) >= i;
|
||||
}
|
||||
/* Unfortunately the branchless version for this test is expressed in terms
|
||||
of `v`, not `i`, so it does not simplify well when `i` is known. In that
|
||||
case, writing eg. `x > num16(0)` is faster than `x > 0`. */
|
||||
inline constexpr bool operator<=(int const &i) {
|
||||
return (v >> 8) + ((v & 0xff) != 0) <= i;
|
||||
}
|
||||
inline constexpr bool operator>(int const &i) {
|
||||
return (v >> 8) + ((v & 0xff) != 0) > i;
|
||||
}
|
||||
inline constexpr bool operator>=(int const &i) {
|
||||
return (v >> 8) >= i;
|
||||
}
|
||||
|
||||
/* Limits as double */
|
||||
static constexpr double minDouble = -128.0;
|
||||
static constexpr double maxDouble = double(0x7fff) / 256;
|
||||
};
|
||||
|
||||
/* num32: Signed 16:16 fixed-point type
|
||||
|
@ -257,6 +264,28 @@ struct num32
|
|||
v %= other.v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Comparisons with int */
|
||||
|
||||
inline constexpr bool operator==(int const &i) {
|
||||
return (int16_t)i == i && (i << 16) == v;
|
||||
}
|
||||
inline constexpr bool operator<(int const &i) {
|
||||
return (v >> 16) < i;
|
||||
}
|
||||
inline constexpr bool operator>=(int const &i) {
|
||||
return (v >> 16) >= i;
|
||||
}
|
||||
inline constexpr bool operator<=(int const &i) {
|
||||
return (v >> 16) + ((v & 0xffff) != 0) <= i;
|
||||
}
|
||||
inline constexpr bool operator>(int const &i) {
|
||||
return (v >> 16) + ((v & 0xffff) != 0) > i;
|
||||
}
|
||||
|
||||
/* Limits as double */
|
||||
static constexpr double minDouble = -32768.0;
|
||||
static constexpr double maxDouble = double(0x7fffffff) / 65536;
|
||||
};
|
||||
|
||||
/* Arithmetic with integers */
|
||||
|
@ -331,6 +360,11 @@ struct num64
|
|||
v %= other.v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Limits as double; note that the double doesn't have enough precision to
|
||||
represent the entirety of the maximum value. */
|
||||
static constexpr double minDouble = -2147483648.0;
|
||||
static constexpr double maxDouble = 2147483648.0 - double(1) / 2147483648;
|
||||
};
|
||||
|
||||
/* The following concept identifies the four num types */
|
||||
|
@ -355,7 +389,7 @@ inline constexpr num16::num16(num32 n): v((uint32_t)n.v >> 8) {}
|
|||
inline constexpr num16::num16(num64 n): v(n.v >> 24) {}
|
||||
|
||||
inline constexpr num32::num32(num8 n): v(n.v * 256) {}
|
||||
inline constexpr num32::num32(num16 n): v(n.v * 256) {}
|
||||
inline constexpr num32::num32(num16 n): v((int32_t)n.v * 256) {}
|
||||
inline constexpr num32::num32(num64 n): v(n.v >> 16) {}
|
||||
|
||||
inline constexpr num64::num64(num8 n): v((uint64_t)n.v * 16777216) {}
|
||||
|
@ -369,7 +403,6 @@ template<typename T> requires(is_num<T>)
|
|||
inline constexpr bool operator==(T const &left, T const &right) {
|
||||
return left.v == right.v;
|
||||
}
|
||||
|
||||
template<typename T> requires(is_num<T>)
|
||||
inline constexpr bool operator!=(T const &left, T const &right) {
|
||||
return left.v != right.v;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#include <num/num.h>
|
||||
using namespace num;
|
||||
|
||||
/* 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)
|
||||
{
|
||||
// x = mod_64(x, num64_const(1));
|
||||
// x = mod_64(x, num64_const(1));
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
# is a shortcut for the number of "non-trivial" instructions, and currently
|
||||
# expands to `[!mov && !rts]`.
|
||||
#
|
||||
# A test is a normal C++ source built using the library, which exposes
|
||||
# A test is a normal C++ source file built using the library, which exposes
|
||||
# functions with C linkage (ie. no name mangling) and has specifications in
|
||||
# comments of the form:
|
||||
#
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
#include <num/num.h>
|
||||
using namespace num;
|
||||
|
||||
extern "C" {
|
||||
|
||||
// num16_of_num8: %=1 && [extu.b]
|
||||
num16 num16_of_num8(num8 x)
|
||||
{
|
||||
return num16(x);
|
||||
}
|
||||
|
||||
// num16_of_num32: %=1 && [shlr8]
|
||||
num16 num16_of_num32(num32 x)
|
||||
{
|
||||
return num16(x);
|
||||
}
|
||||
|
||||
// num16_of_num64: %=[sh* || or]
|
||||
num16 num16_of_num64(num64 x)
|
||||
{
|
||||
return num16(x);
|
||||
}
|
||||
|
||||
// num16_mul: [shad] && ![jsr]
|
||||
num16 num16_mul(num16 x, num16 y)
|
||||
{
|
||||
return x * y;
|
||||
}
|
||||
|
||||
// num16_dmul: [muls.w]
|
||||
num32 num16_dmul(num16 x, num16 y)
|
||||
{
|
||||
return num16::dmul(x, y);
|
||||
}
|
||||
|
||||
// num16_eq: [bt* || bf*] && [shll8]
|
||||
bool num16_eq(num16 x, int i)
|
||||
{
|
||||
return x == i;
|
||||
}
|
||||
|
||||
// num16_le: ![bt* || bf*]
|
||||
bool num16_le(num16 x, int i)
|
||||
{
|
||||
return x <= i;
|
||||
}
|
||||
|
||||
// num16_gt: ![bt* || bf*]
|
||||
bool num16_gt(num16 x, int i)
|
||||
{
|
||||
return x > i;
|
||||
}
|
||||
|
||||
// num16_le_0: %<=3
|
||||
bool num16_le_0(num16 x)
|
||||
{
|
||||
return x <= num16(0);
|
||||
}
|
||||
|
||||
// num16_ge_0: %<=3 || (%=4 && [mov.l])
|
||||
bool num16_ge_0(num16 x)
|
||||
{
|
||||
return x >= num16(0);
|
||||
}
|
||||
|
||||
// num16_gt_0: %<=3
|
||||
bool num16_gt_0(num16 x)
|
||||
{
|
||||
return x > num16(0);
|
||||
}
|
||||
|
||||
// num16_lt_0: %<=3 || (%=4 && [mov.l])
|
||||
bool num16_lt_0(num16 x)
|
||||
{
|
||||
return x < num16(0);
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
|
@ -0,0 +1,102 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
// ," /\ ", 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_check.h: Utilities for asserting and printing
|
||||
//
|
||||
// This header defines a ToString<T> typeclass and the utility functions
|
||||
// runWithChecker() which extend the provided function with a Checker argument.
|
||||
// This object accumulates the results of tests, and stores the names and
|
||||
// values of the test subjects so they can be printed if any assertion fails.
|
||||
//---
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include <stdio.h>
|
||||
#include <num/num.h>
|
||||
|
||||
template<typename T>
|
||||
struct ToString {};
|
||||
|
||||
template<typename T>
|
||||
concept to_string = requires { ToString<T>::str; };
|
||||
|
||||
template<typename T> requires(is_num<T>)
|
||||
struct ToString<T>
|
||||
{
|
||||
static std::string str(T x) {
|
||||
char s[64];
|
||||
uint64_t v = (uint64_t)x.v;
|
||||
if constexpr (sizeof x < 8)
|
||||
v &= ((1ull << 8 * sizeof x) - 1);
|
||||
sprintf(s, "%0*lx (%lf)", 2 * (int)sizeof(x), v, (double)x);
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ToString<int>
|
||||
{
|
||||
static std::string str(int i) {
|
||||
return std::to_string(i);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> requires(to_string<Ts> && ...)
|
||||
class Checker
|
||||
{
|
||||
public:
|
||||
Checker(): m_success(true) {}
|
||||
|
||||
void vars(std::initializer_list<char const *> names) {
|
||||
m_names = std::vector(names);
|
||||
}
|
||||
void values(Ts... values) {
|
||||
m_values = std::make_tuple(values...);
|
||||
}
|
||||
|
||||
bool check(bool b, char const *expr) {
|
||||
if(!b) {
|
||||
m_success = false;
|
||||
fprintf(stderr, "FAILED: %s\n", expr);
|
||||
printValues(std::index_sequence_for<Ts...> {});
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
bool successful() const {
|
||||
return m_success;
|
||||
}
|
||||
|
||||
private:
|
||||
/* Iterate on `m_names` and `m_values` following the index sequence Is; for
|
||||
each index, prints the variable name and value. */
|
||||
template<std::size_t... Is>
|
||||
void printValues(std::index_sequence<Is...>) {
|
||||
(fprintf(stderr, " %s: %s\n", m_names[Is],
|
||||
ToString<typename std::tuple_element<Is, decltype(m_values)>::type>
|
||||
::str(std::get<Is>(m_values)).c_str()), ...);
|
||||
}
|
||||
|
||||
std::vector<char const *> m_names;
|
||||
std::tuple<Ts...> m_values;
|
||||
bool m_success;
|
||||
};
|
||||
|
||||
template<typename T> requires(to_string<T>)
|
||||
bool runWithChecker(std::function<void(T, Checker<T> &)> f)
|
||||
{
|
||||
Checker<T> c;
|
||||
runOnSampleInputs<T>([f, &c](T x) { f(x, c); });
|
||||
return c.successful();
|
||||
}
|
||||
|
||||
template<typename T, typename U> requires(to_string<T> && to_string<U>)
|
||||
bool runWithChecker(std::function<void(T, U, Checker<T, U> &)> f)
|
||||
{
|
||||
Checker<T, U> c;
|
||||
runOnSampleInputs<T, U>([f, &c](T x, U y) { f(x, y, c); });
|
||||
return c.successful();
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
// ," /\ ", 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_sample.h: Utility for generating sample values for testing
|
||||
//
|
||||
// This file mainly provides the runOnSampleInputs() function which runs a
|
||||
// boolean function f() on a series of generated inputs. It achieves this by
|
||||
// reading a storage of pre-computed inputs for each of the arguments of f.
|
||||
// This requires a storage to exist for each argument's type, which is provided
|
||||
// by SampleBase and subclassed by Sample.
|
||||
//
|
||||
// The meat of the generation is the generateIntSample() function, which
|
||||
// generates sparse sets of integers of varied size that omit needlessly
|
||||
// redundant input while densely covering special cases.
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <cassert>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <num/num.h>
|
||||
using namespace num;
|
||||
|
||||
//---
|
||||
// Integer sampling
|
||||
//---
|
||||
|
||||
template<typename T, typename Uint>
|
||||
void generateIntSample(std::vector<T> &v, std::function<T(Uint)> ctor,
|
||||
Uint start, Uint length, int count)
|
||||
{
|
||||
/* When we get to a set of size 16, do all values. */
|
||||
if(count <= 16) {
|
||||
/* If we have an even-only range, force in some odd numbers too. */
|
||||
bool switch_odd = (length >= (Uint)(2*count));
|
||||
Uint step = length / count;
|
||||
while(count-- > 0) {
|
||||
v.push_back(ctor(start));
|
||||
start += switch_odd ? step ^ (count & 1) : step;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise, fractally divide into 16 segments and assign a portion of the
|
||||
available points to each section. We divide the count of points in 16ths
|
||||
so that there is at least one point in each segment. */
|
||||
Uint sublength = length / 16;
|
||||
int subcount = count / 16;
|
||||
/* This array must add up to 16. */
|
||||
int props[16] = { 4, 1, 1, 0, 0, 2, 0, 0, 1, 0, 2, 0, 0, 0, 1, 4 };
|
||||
for(int i = 0; i < 16; i++) {
|
||||
if(props[i])
|
||||
generateIntSample(v, ctor, (Uint)(start + i * sublength),
|
||||
sublength, subcount * props[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// Input sampling
|
||||
//---
|
||||
|
||||
template<typename T>
|
||||
struct SampleBase {
|
||||
static std::vector<T> v;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> SampleBase<T>::v;
|
||||
|
||||
template<typename T>
|
||||
struct Sample {};
|
||||
|
||||
template<typename T>
|
||||
concept has_sample = requires { Sample<T>::get; };
|
||||
|
||||
template<>
|
||||
struct Sample<num8>: SampleBase<num8>
|
||||
{
|
||||
static std::vector<num8> const &get() {
|
||||
if(v.size() > 0)
|
||||
return v;
|
||||
for(int i = 0; i <= 0xff; i++) {
|
||||
num8 x;
|
||||
x.v = i;
|
||||
v.push_back(x);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Sample<num16>: SampleBase<num16>
|
||||
{
|
||||
static std::vector<num16> const &get() {
|
||||
if(v.size() > 0)
|
||||
return v;
|
||||
auto f = [](uint16_t i) { num16 x; x.v = i; return x; };
|
||||
generateIntSample<num16, uint16_t>(v, f, 0, 1 << 15, 512);
|
||||
generateIntSample<num16, uint16_t>(v, f, -(1 << 15), 1 << 15, 512);
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Sample<num32>: SampleBase<num32>
|
||||
{
|
||||
static std::vector<num32> const &get() {
|
||||
if(v.size() > 0)
|
||||
return v;
|
||||
auto f = [](uint32_t i) { num32 x; x.v = i; return x; };
|
||||
generateIntSample<num32, uint32_t>(v, f, 0, 1 << 15, 512);
|
||||
generateIntSample<num32, uint32_t>(v, f, -(1 << 15), 1 << 15, 512);
|
||||
generateIntSample<num32, uint32_t>(v, f, 0, 1ul << 31, 512);
|
||||
generateIntSample<num32, uint32_t>(v, f, 1ul << 31, 1ul << 31, 512);
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Sample<int>: SampleBase<int>
|
||||
{
|
||||
static std::vector<int> const &get() {
|
||||
if(v.size() > 0)
|
||||
return v;
|
||||
auto f = [](uint32_t i) { return i; };
|
||||
generateIntSample<int, uint32_t>(v, f, 0, 1 << 15, 512);
|
||||
generateIntSample<int, uint32_t>(v, f, -(1 << 15), 1 << 15, 512);
|
||||
generateIntSample<int, uint32_t>(v, f, 0, 1ul << 31, 512);
|
||||
generateIntSample<int, uint32_t>(v, f, 1ul << 31, 1ul << 31, 512);
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Sample<num64>: SampleBase<num64>
|
||||
{
|
||||
static std::vector<num64> const &get() {
|
||||
if(v.size() > 0)
|
||||
return v;
|
||||
auto f = [](uint64_t i) { num64 x; x.v = i; return x; };
|
||||
generateIntSample<num64, uint64_t>(v, f, 0, 1 << 15, 512);
|
||||
generateIntSample<num64, uint64_t>(v, f, -(1 << 15), 1 << 15, 512);
|
||||
generateIntSample<num64, uint64_t>(v, f, 0, 1ul << 31, 512);
|
||||
generateIntSample<num64, uint64_t>(v, f, -(1ul << 31), 1ul << 31, 512);
|
||||
generateIntSample<num64, uint64_t>(v, f, 0, 1ull << 63, 512);
|
||||
generateIntSample<num64, uint64_t>(v, f, 1ull << 63, 1ull << 63, 512);
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
//---
|
||||
// Automatic test functions
|
||||
//---
|
||||
|
||||
template<typename T> requires(has_sample<T>)
|
||||
void runOnSampleInputs(std::function<void(T)> f)
|
||||
{
|
||||
for(auto t: Sample<T>::get())
|
||||
f(t);
|
||||
}
|
||||
|
||||
template<typename T, typename U> requires(has_sample<T> && has_sample<U>)
|
||||
void runOnSampleInputs(std::function<void(T, U)> f)
|
||||
{
|
||||
for(auto t: Sample<T>::get())
|
||||
for(auto u: Sample<U>::get())
|
||||
f(t, u);
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
// ," /\ ", 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_scalar.cpp: Unit tests for scalar arithmetic
|
||||
|
||||
#include <stdio.h>
|
||||
#include "unit_sample.h"
|
||||
#include "unit_check.h"
|
||||
|
||||
/* Automatically stringify expressions in arguments to Checker.check() */
|
||||
#define CHECK(EXPR) check(EXPR, #EXPR)
|
||||
|
||||
/* Test equality of num values up to slight variations */
|
||||
template<typename T> requires(is_num<T>)
|
||||
bool isEqUpTo(T x, T y, int i)
|
||||
{
|
||||
return abs(x.v - y.v) <= i;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool runInternalArithmeticTest(int error)
|
||||
{
|
||||
return runWithChecker<T, T>([error](T x, T y, Checker<T, T> &c) {
|
||||
c.vars({ "x", "y" });
|
||||
c.values(x, y);
|
||||
|
||||
/* Surprisinly the cast back to integer in T() does saturation
|
||||
arithmetic, so we can't require equality in case of overflow. */
|
||||
|
||||
double sum = double(x) + double(y);
|
||||
if(sum >= T::minDouble && sum <= T::maxDouble)
|
||||
c.CHECK(x + y == T(sum));
|
||||
|
||||
double diff = double(x) - double(y);
|
||||
if(diff >= T::minDouble && diff <= T::maxDouble)
|
||||
c.CHECK(x - y == T(diff));
|
||||
|
||||
double prod = double(x) * double(y);
|
||||
if(prod >= T::minDouble && prod <= T::maxDouble)
|
||||
c.CHECK(isEqUpTo(x * y, T(prod), error));
|
||||
|
||||
if(y != 0) {
|
||||
double quot = double(x) / double(y);
|
||||
if(quot >= T::minDouble && quot <= T::maxDouble)
|
||||
c.CHECK(x / y == T(quot));
|
||||
}
|
||||
|
||||
c.CHECK(y <= 0 || x < 0 || x % y < y);
|
||||
c.CHECK((x < y) + (x == y) + (x > y) == 1);
|
||||
c.CHECK((x <= y) - (x == y) + (x >= y) == 1);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool runIntegerComparisonTest()
|
||||
{
|
||||
return runWithChecker<T, int>([](T x, int i, Checker<T, int> &c) {
|
||||
c.vars({ "x", "i" });
|
||||
c.values(x, i);
|
||||
c.CHECK((x < i) + (x == i) + (x > i) == 1);
|
||||
c.CHECK((x <= i) - (x == i) + (x >= i) == 1);
|
||||
});
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
printf("Testing unary laws on num8...\n");
|
||||
success &= runWithChecker<num8>([](num8 x, Checker<num8> &c) {
|
||||
c.vars({ "x" });
|
||||
c.values(x);
|
||||
c.CHECK(num8(num16(x)) == x);
|
||||
c.CHECK(num8(num32(x)) == x);
|
||||
c.CHECK(num8(num64(x)) == x);
|
||||
c.CHECK(-x + x == 0);
|
||||
});
|
||||
|
||||
printf("Testing binary laws on num8...\n");
|
||||
success &= runInternalArithmeticTest<num8>(0);
|
||||
|
||||
printf("Testing comparisons of num8 with integers...\n");
|
||||
success &= runIntegerComparisonTest<num8>();
|
||||
|
||||
printf("Testing unary laws on num16...\n");
|
||||
success &= runWithChecker<num16>([](num16 x, Checker<num16> &c) {
|
||||
c.vars({ "x" });
|
||||
c.values(x);
|
||||
c.CHECK(num16(num8(x)).v == (x.v & 0xff));
|
||||
c.CHECK(x < 0 || num16(num8(x)) == x % num16(1));
|
||||
c.CHECK(num16(num32(x)) == x);
|
||||
c.CHECK(num16(num64(x)) == x);
|
||||
c.CHECK(-x + x == 0);
|
||||
});
|
||||
|
||||
printf("Testing binary laws on num16...\n");
|
||||
success &= runInternalArithmeticTest<num16>(1);
|
||||
|
||||
printf("Testing comparisons of num16 with integers...\n");
|
||||
success &= runIntegerComparisonTest<num16>();
|
||||
|
||||
printf("Testing unary laws on num32...\n");
|
||||
success &= runWithChecker<num32>([](num32 x, Checker<num32> &c) {
|
||||
c.vars({ "x" });
|
||||
c.values(x);
|
||||
c.CHECK(num32(num8(x)).v == (x.v & 0x0000ff00));
|
||||
c.CHECK(num32(num16(x)).v >> 8 == (int16_t)(x.v >> 8));
|
||||
c.CHECK(num32(num64(x)) == x);
|
||||
c.CHECK(-x + x == 0);
|
||||
});
|
||||
|
||||
printf("Testing binary laws on num32...\n");
|
||||
success &= runInternalArithmeticTest<num32>(1);
|
||||
|
||||
printf("Testing comparisons of num32 with integers...\n");
|
||||
success &= runIntegerComparisonTest<num32>();
|
||||
|
||||
printf("Testing unary laws on num64...\n");
|
||||
success &= runWithChecker<num64>([](num64 x, Checker<num64> &c) {
|
||||
c.vars({ "x" });
|
||||
c.values(x);
|
||||
c.CHECK(num64(num8(x)).v == (x.v & 0x00000000ff000000ll));
|
||||
c.CHECK(num64(num16(x)).v >> 24 == (int16_t)(x.v >> 24));
|
||||
c.CHECK(num64(num32(x)).v >> 16 == (int32_t)(x.v >> 16));
|
||||
c.CHECK(-x + x == num64(0));
|
||||
});
|
||||
|
||||
return (success ? 0 : 1);
|
||||
}
|
|
@ -1,5 +1,11 @@
|
|||
#include <num/num.h>
|
||||
//---------------------------------------------------------------------------//
|
||||
// ," /\ ", 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_static.cpp: Compile-time evaluation tests
|
||||
|
||||
#include <num/num.h>
|
||||
using namespace num;
|
||||
|
||||
static_assert(sizeof(num8) == 1);
|
||||
|
@ -23,31 +29,16 @@ static_assert(num32(num16(-15)) == num32(-15));
|
|||
static_assert(num64(num16(1)) == num64(1));
|
||||
static_assert(num64(num16(-1)) == num64(-1));
|
||||
|
||||
/* Comparisons between num8 and int */
|
||||
|
||||
static_assert(num8(0) == 0);
|
||||
static_assert(num8(0) != 1);
|
||||
static_assert(num8(0.5) != 0);
|
||||
static_assert(num8(0.5) != 1);
|
||||
static_assert(num8(1) == 0); // overflow
|
||||
static_assert(num8(1) != 1); // overflow
|
||||
|
||||
static_assert(!(num8(0) < 0));
|
||||
static_assert(num8(0) < 1);
|
||||
static_assert(!(num8(0.5) < 0));
|
||||
static_assert(num8(0.5) < 1);
|
||||
|
||||
static_assert(num8(0) <= 0);
|
||||
static_assert(num8(0) <= 1);
|
||||
static_assert(!(num8(0.5) <= 0));
|
||||
static_assert(num8(0.5) <= 1);
|
||||
|
||||
static_assert(!(num8(0) > 0));
|
||||
static_assert(!(num8(0) > 1));
|
||||
static_assert(num8(0.5) > 0);
|
||||
static_assert(!(num8(0.5) > 1));
|
||||
|
||||
static_assert(num8(0) >= 0);
|
||||
static_assert(!(num8(0) >= 1));
|
||||
static_assert(num8(0.5) >= 0);
|
||||
static_assert(!(num8(0.5) >= 1));
|
Loading…
Reference in New Issue