Porting rapidcheck to meson

This commit is contained in:
Stepland 2022-04-10 13:08:41 +02:00
parent be7219ad3d
commit f5d6199f27
14 changed files with 7184 additions and 39 deletions

6816
include/doctest.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,40 @@
project(
'F.E.I.S.',
'cpp',
meson_version : '>=0.55.0',
meson_version : '>=0.62.0',
version : '1.1.0',
default_options : ['cpp_std=c++20'],
)
sources = []
deps = []
sfml_system_dep = dependency('sfml-system', version : '>=2.5.1')
deps += [sfml_system_dep]
sfml_window_dep = dependency('sfml-window', version : '>=2.5.1')
deps += [sfml_window_dep]
sfml_graphics_dep = dependency('sfml-graphics', version : '>=2.5.1')
deps += [sfml_graphics_dep]
sfml_audio_dep = dependency('sfml-audio', version : '>=2.5.1')
deps += [sfml_audio_dep]
gl_dep = dependency('gl')
deps += [gl_dep]
openal_dep = dependency('openal')
deps += [openal_dep]
mpdecpp_dep = dependency('mpdec++', fallback: ['mpdecimal', 'mpdecpp_dep'])
deps += [mpdecpp_dep]
gmp_dep = dependency('gmp')
deps += [gmp_dep]
nowide_dep = dependency('nowide')
deps += [nowide_dep]
# I chose to put the .cpp files of the libs I vendor directly in include/
# I store the files in a (lib name -> files) dict so that tests can
# select which libs they want to compile with
include_sources = {}
subdir('include') # Defines `inc` and adds stuff in `include_sources`
subdir('src') # Adds stuff in `sources`
deps = [
dependency('sfml-system', version : '>=2.5.1'),
dependency('sfml-window', version : '>=2.5.1'),
dependency('sfml-graphics', version : '>=2.5.1'),
dependency('sfml-audio', version : '>=2.5.1'),
dependency('gl'),
dependency('openal'),
dependency('mpdec++', fallback: ['mpdecimal', 'mpdecpp_dep']),
dependency('nowide'),
dependency('gmp')
]
sources = []
subdir('src') # Adds stuff in the `sources` list
subdir('tests')
@ -40,4 +48,3 @@ executable(
dependencies: deps,
include_directories: inc,
)

View File

@ -3,18 +3,19 @@
#include <boost/multiprecision/gmp.hpp>
#include <fmt/core.h>
#include <json.hpp>
#include <memory>
#include <stdexcept>
#include "special_numeric_types.hpp"
bool is_expressible_as_240th(const Fraction& beat) {
return (240 * beat.get_num()) % beat.get_den() == 0;
return (240 * beat.numerator()) % beat.denominator() == 0;
};
nlohmann::ordered_json beat_to_best_form(const Fraction& beat) {
if (is_expressible_as_240th(beat)) {
return nlohmann::ordered_json(
(240 * beat.get_num().get_ui()) / beat.get_den().get_ui()
(240 * convert_to_u64(beat.numerator()) / convert_to_u64(beat.denominator())
);
} else {
return beat_to_fraction_tuple(beat);
@ -22,12 +23,12 @@ nlohmann::ordered_json beat_to_best_form(const Fraction& beat) {
};
nlohmann::ordered_json beat_to_fraction_tuple(const Fraction& beat) {
const auto integer_part = beat.get_num().get_ui() / beat.get_den().get_ui();
const auto integral_part = static_cast<std::uint64_t>(beat);
const auto remainder = beat % 1;
return {
integer_part,
remainder.get_num().get_ui(),
remainder.get_den().get_ui(),
integral_part,
convert_to_u64(remainder.numerator()),
convert_to_u64(remainder.denominator()),
};
};

View File

@ -86,11 +86,11 @@ namespace better {
if (std::distance(begin, end) > 1) {
std::stringstream ss;
ss << "Attempted to create a Timing object with multiple ";
ss << "BPMs defined at beat " << bpm_at_beat.get_beats().get_str();
ss << "BPMs defined at beat " << bpm_at_beat.get_beats();
ss << " :";
std::for_each(begin, end, [&ss](auto b){
ss << " (bpm: " << b.get_bpm() << ", beat: ";
ss << b.get_beats().get_str() << "),";
ss << b.get_beats() << "),";
});
throw std::invalid_argument(ss.str());
}

View File

@ -30,4 +30,6 @@ sources += files(
'toolbox.cpp',
)
subdir('widgets')
subdir('widgets')
subdir('tests')

View File

@ -1,14 +1,85 @@
#include "special_numeric_types.hpp"
#include <stdexcept>
std::strong_ordering operator<=>(const Fraction& lhs, const Fraction& rhs) {
const auto res = cmp(lhs, rhs);
if (res > 0) {
return std::strong_ordering::greater;
} else if (res == 0) {
return std::strong_ordering::equal;
Fraction::Fraction(const Decimal& d) {
const auto reduced = d.reduce();
const mpq_class sign = (reduced.sign() > 0 ? 1 : -1);
const mpq_class coefficient = reduced.coeff().u64();
const auto exponent = reduced.exponent();
const mpq_class power = fast_pow(mpq_class(10), std::abs(exponent));
if (exponent >= 0) {
value = sign * coefficient * power;
} else {
return std::strong_ordering::less;
value = sign * coefficient / power;
}
}
Fraction::operator std::int64_t() const {
const auto a = convert_to_i64(value.get_num());
const auto b = convert_to_i64(value.get_den());
return a / b;
};
Fraction::operator std::uint64_t() const {
const auto a = convert_to_u64(value.get_num());
const auto b = convert_to_u64(value.get_den());
return a / b;
};
Fraction::operator double() const {
return value.get_d();
};
const mpz_class& Fraction::numerator() const {
return value.get_num();
}
const mpz_class& Fraction::denominator() const {
return value.get_den();
}
Fraction& Fraction::operator+=(const Fraction& rhs) {
value += rhs.value;
return *this;
};
Fraction& Fraction::operator-=(const Fraction& rhs) {
value -= rhs.value;
return *this;
};
Fraction& Fraction::operator*=(const Fraction& rhs) {
value *= rhs.value;
return *this;
};
Fraction& Fraction::operator/=(const Fraction& rhs) {
value /= rhs.value;
return *this;
};
bool Fraction::operator==(const Fraction& rhs) const {
return value == rhs.value;
};
Fraction operator+(Fraction lhs, const Fraction& rhs) {
lhs+= rhs;
return lhs;
};
Fraction operator-(Fraction lhs, const Fraction& rhs) {
lhs -= rhs;
return lhs;
};
Fraction operator*(Fraction lhs, const Fraction& rhs) {
lhs *= rhs;
return lhs;
};
Fraction operator/(Fraction lhs, const Fraction& rhs) {
lhs /= rhs;
return lhs;
};
// Thanks python !
@ -16,13 +87,79 @@ std::strong_ordering operator<=>(const Fraction& lhs, const Fraction& rhs) {
//
// passing lhs by value helps optimize chained a+b+c, says cppreference
Fraction operator%(Fraction lhs, const Fraction& rhs) {
const auto da = lhs.get_den();
const auto db = rhs.get_den();
const auto na = lhs.get_num();
const auto nb = rhs.get_num();
const auto da = lhs.value.get_den();
const auto db = rhs.value.get_den();
const auto na = lhs.value.get_num();
const auto nb = rhs.value.get_num();
return Fraction{(na * db) % (nb * da), da * db};
};
std::strong_ordering operator<=>(const Fraction& lhs, const Fraction& rhs) {
auto res = cmp(lhs.value, rhs.value);
if (res < 0) {
return std::strong_ordering::less;
} else if (res == 0) {
return std::strong_ordering::equal;
} else {
return std::strong_ordering::greater;
}
};
std::ostream& operator<<(std::ostream& os, const Fraction& obj) {
os << obj.value.get_str();
return os;
}
std::int64_t convert_to_i64(const mpz_class& z) {
if (z < mpz_int64_min or z > mpz_int64_max) {
throw std::range_error(fmt::format(
"number {} is too large to be represented by an std::int64_t",
z.get_str()
));
}
/* In absolute value, INT64_MIN is greater than INT64_MAX, so I can't take
the absolute value of INT64_MIN and convert it to std::int64_t without
triggering a signed overflow (which is UB ?).
So I deal with it separately */
if (z == mpz_int64_min) {
return INT64_MIN;
}
const bool positive = z >= 0;
const mpz_class abs = positive ? z : -z;
/* We can only get unsigned longs from GMP (32 garanteed bits), so we have
to split between low and high */
const mpz_class low = abs & INT64_C(0x00000000ffffffff);
const mpz_class high = abs >> 32;
const auto low_ul = low.get_ui();
const auto high_ul = high.get_ui();
const auto low_i64 = static_cast<std::int64_t>(low_ul);
const auto high_i64 = static_cast<std::int64_t>(high_ul);
const auto abs_i64 = low_i64 + (high_i64 << 32);
if (positive) {
return abs_i64;
} else {
return -abs_i64;
}
}
std::uint64_t convert_to_u64(const mpz_class& z) {
if (z < 0 or z > mpz_uint64_max) {
throw std::range_error(fmt::format(
"number {} is too large to be represented by an std::uint64_t",
z.get_str()
));
}
/* We can only get unsigned longs from GMP (32 garanteed bits), so we have
to split between low and high */
const mpz_class low = z & INT64_C(0x00000000ffffffff);
const mpz_class high = z >> 32;
const auto low_ul = low.get_ui();
const auto high_ul = high.get_ui();
const auto low_i64 = static_cast<std::uint64_t>(low_ul);
const auto high_i64 = static_cast<std::uint64_t>(high_ul);
return low_i64 + (high_i64 << 32);
}
Fraction floor_fraction(const Fraction& f) {
return f - (f % Fraction{1});
};

View File

@ -1,16 +1,59 @@
#pragma once
#include <compare>
#include <concepts>
#include <cstdint>
#include <ostream>
#include <type_traits>
#include <fmt/core.h>
#include <gmpxx.h>
#include <libmpdec++/decimal.hh>
using Fraction = mpq_class;
using Decimal = decimal::Decimal;
std::strong_ordering operator<=>(const Fraction& lhs, const Fraction& rhs);
Fraction operator%(Fraction a, const Fraction& b);
class Fraction {
public:
template<class ...Ts>
Fraction(Ts&&... Args) requires (std::constructible_from<mpq_class, Ts...>) :
value(std::forward<Ts>(Args)...)
{
value.canonicalize();
};
explicit Fraction(const Decimal& d);
explicit operator std::int64_t() const;
explicit operator std::uint64_t() const;
explicit operator double() const;
const mpz_class& numerator() const;
const mpz_class& denominator() const;
Fraction& operator+=(const Fraction& rhs);
Fraction& operator-=(const Fraction& rhs);
Fraction& operator*=(const Fraction& rhs);
Fraction& operator/=(const Fraction& rhs);
bool operator==(const Fraction&) const;
friend Fraction operator+(Fraction a, const Fraction& b);
friend Fraction operator-(Fraction a, const Fraction& b);
friend Fraction operator*(Fraction a, const Fraction& b);
friend Fraction operator/(Fraction a, const Fraction& b);
friend Fraction operator%(Fraction a, const Fraction& b);
friend std::strong_ordering operator<=>(const Fraction& lhs, const Fraction& rhs);
friend std::ostream& operator<<(std::ostream& os, const Fraction& obj);
private:
mpq_class value;
};
const auto mpz_uint64_max = mpz_class(fmt::format("{}", UINT64_MAX));
const auto mpz_int64_min = mpz_class(fmt::format("{}", INT64_MIN));
const auto mpz_int64_max = mpz_class(fmt::format("{}", INT64_MAX));
std::int64_t convert_to_i64(const mpz_class& z);
std::uint64_t convert_to_u64(const mpz_class& z);
Fraction floor_fraction(const Fraction& f);
Fraction round_fraction(const Fraction& f);
Fraction convert_to_fraction(const Decimal& d);

View File

@ -0,0 +1,84 @@
#include <doctest.h>
#include "../../special_numeric_types.hpp"
TEST_CASE("Fractions") {
SUBCASE("can be exactly constructed from") {
SUBCASE("integers") {
CHECK(Fraction{1} == Fraction{1,1});
CHECK(Fraction{-2} == Fraction{-2,1});
}
SUBCASE("std::uint64_t") {
CHECK(Fraction{UINT64_MAX, UINT64_C(1)} == Fraction{"18446744073709551615/1"});
}
SUBCASE("decimals") {
CHECK(Fraction{Decimal{"0.0000000000000000001"}} == Fraction{"1/10000000000000000000"});
CHECK(Fraction{Decimal{"12345.6789"}} == Fraction{123456789,10000});
}
}
SUBCASE("can be cast to") {
SUBCASE("std::int64_t, returing the integral part") {
CHECK(static_cast<std::int64_t>(Fraction{1,2}) == INT64_C(0));
CHECK(static_cast<std::int64_t>(Fraction{-5,2}) == INT64_C(-2));
CHECK(static_cast<std::int64_t>(Fraction{"-9223372036854775808/1"}) == INT64_MIN);
CHECK(static_cast<std::int64_t>(Fraction{"9223372036854775807/1"}) == INT64_MAX);
CHECK(static_cast<std::int64_t>(Fraction{"-9000000000000000000/1"}) == INT64_C(-9000000000000000000));
CHECK(static_cast<std::int64_t>(Fraction{"9000000000000000000/1"}) == INT64_C(9000000000000000000));
}
SUBCASE("std::uint64_t, returing the (positive) integral part") {
CHECK(static_cast<std::uint64_t>(Fraction{1,2}) == UINT64_C(0));
CHECK(static_cast<std::uint64_t>(Fraction{5,2}) == UINT64_C(2));
CHECK(static_cast<std::uint64_t>(Fraction{"18446744073709551615/1"}) == UINT64_MAX);
CHECK(static_cast<std::uint64_t>(Fraction{"12345678912345678912/1"}) == UINT64_C(12345678912345678912));
}
SUBCASE("doubles, returing the approximated value") {
CHECK(static_cast<double>(Fraction{1,10}) == doctest::Approx(0.1));
CHECK(static_cast<double>(Fraction{31,10}) == doctest::Approx(3.1));
CHECK(static_cast<double>(Fraction{-1,10}) == doctest::Approx(-0.1));
}
}
SUBCASE("are canonicalized upon creation") {
CHECK(Fraction(0,4) == Fraction(0,1));
CHECK(Fraction{2,4} == Fraction{1,2});
CHECK(Fraction{1,-2} == Fraction{-1,2});
}
SUBCASE("are correctly compared") {
CHECK(Fraction{1,2} > Fraction{1,3});
CHECK(Fraction{-1,2} < Fraction{1,3});
CHECK(Fraction{12,2} < Fraction{28, 4});
}
SUBCASE("are not turned into another type after binary operands") {
CHECK(std::is_same_v<decltype(Fraction{1,2} + Fraction{1,3}), Fraction>);
CHECK(std::is_same_v<decltype(Fraction{1,2} - Fraction{1,3}), Fraction>);
CHECK(std::is_same_v<decltype(Fraction{1,2} * Fraction{1,3}), Fraction>);
CHECK(std::is_same_v<decltype(Fraction{1,2} / Fraction{1,3}), Fraction>);
CHECK(std::is_same_v<decltype(Fraction{1,2} % Fraction{1,3}), Fraction>);
}
SUBCASE("add correctly") {
CHECK(Fraction{1,2} + Fraction{1,3} == Fraction{5,6});
CHECK(Fraction{-1,2} + Fraction{1,2} == Fraction{0,1});
}
SUBCASE("subtract correctly") {
CHECK(Fraction{1,2} - Fraction{1,3} == Fraction{1,6});
CHECK(Fraction{-1,2} - Fraction{1,2} == Fraction{-1,1});
}
SUBCASE("multiply correctly") {
CHECK(Fraction{1,2} * Fraction{1,7} == Fraction{1,14});
CHECK(Fraction{-1,2} * Fraction{1,2} == Fraction{-1,4});
}
SUBCASE("divide correctly") {
CHECK(Fraction{1,2} / Fraction{1,7} == Fraction{7,2});
CHECK(Fraction{-1,2} / Fraction{1,2} == Fraction{-1,1});
}
SUBCASE("support binary operand with") {
SUBCASE("integer literals") {
CHECK(Fraction{1,2} + 1 == Fraction{3,2});
CHECK(Fraction{1,3} - 1 == Fraction{-2,3});
CHECK(Fraction{1,4} * 2 == Fraction{1,2});
CHECK(Fraction{1,5} / 2 == Fraction{1,10});
}
SUBCASE("floating point literals") {
CHECK(Fraction{1,2} + 1.0 == Fraction{3,2});
}
}
}

View File

@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest.h>

View File

@ -0,0 +1,19 @@
doctest_tests = executable(
'doctest_tests',
'main.cpp',
'fractions.cpp',
'../../special_numeric_types.cpp',
include_sources['fmt'],
dependencies: [
gmp_dep,
mpdecpp_dep,
],
include_directories: inc,
)
test(
'doctest',
doctest_tests,
args: ['--force-colors=true'],
verbose: true,
)

2
src/tests/meson.build Normal file
View File

@ -0,0 +1,2 @@
subdir('doctest')
subdir('rapidcheck')

View File

@ -0,0 +1,19 @@
rapidcheck_tests = executable(
'rapidcheck_tests',
'rapidcheck_main.cpp',
'test_fractions.cpp',
'../../special_numeric_types.cpp',
include_sources['fmt'],
dependencies: [
gmp_dep,
mpdecpp_dep,
dependency('rapidcheck')
],
include_directories: inc,
)
test(
'rapidcheck',
rapidcheck_tests,
verbose: true,
)

View File

@ -0,0 +1,6 @@
#include <rapidcheck.h>
int main() {
return 0;
}

View File

@ -0,0 +1,7 @@
[wrap-git]
url = https://github.com/emil-e/rapidcheck
# closest commit to v11.1.4 on the standalone branch
revision = head
# Overlays subproject/packagefiles/nowide over the fresh git
# clone of nowide
patch_directory = rapidcheck