/*
  Copyright 2011-2020 David Robillard <http://drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#undef NDEBUG

#include "../src/bigint.h"

#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#define MAX_HEX_LEN 512

/* Some test data borrowed from http://github.com/google/double-conversion
   which uses a completely different bigint representation (so if these agree,
   everything is probably fine).  Others cases are either made up to hit the
   edges of the implementation, or interesting cases collected from testing the
   decimal implementation.  Almost everything here uses the hex representation
   so it is easy to dump into Python as a sanity check. */

static inline SerdBigint
bigint_from_hex(const char* str)
{
	SerdBigint num;
	serd_bigint_set_hex_string(&num, str);
	return num;
}

static bool
check_string_equals(const char* const actual, const char* const expected)
{
	if (strcmp(actual, expected)) {
		fprintf(stderr, "error: result   \"%s\"\n", actual);
		fprintf(stderr, "note:  expected \"%s\"\n", expected);
		return false;
	}

	return true;
}

static bool
check_hex_equals(const char* const str, const SerdBigint* const num)
{
	char buffer[MAX_HEX_LEN] = {0};
	serd_bigint_to_hex_string(num, buffer, sizeof(buffer));

	const char* s = str;
	while (*s == ' ') {
		++s; // Skip leading whitespace
	}

	return check_string_equals(buffer, s);
}

#define CHECK_HEXEQ(str, num) assert(check_hex_equals(str, num))
#define CHECK_STREQ(s1, s2) assert(check_string_equals(s1, s2))

#define CHECK_SET(setter, value, expected)     \
	do {                                       \
		SerdBigint num;                        \
		serd_bigint_set_##setter(&num, value); \
		CHECK_HEXEQ(expected, &num);           \
	} while (0)

static void
test_set(void)
{
	CHECK_SET(u32, 0, "0");
	CHECK_SET(u32, 0xA, "A");
	CHECK_SET(u32, 0x20, "20");
	CHECK_SET(u32, 0x12345678, "12345678");

	CHECK_SET(u64, 0, "0");
	CHECK_SET(u64, 0xA, "A");
	CHECK_SET(u64, 0x20, "20");
	CHECK_SET(u64, 0x12345678, "12345678");
	CHECK_SET(u64, 0xFFFFFFFFFFFFFFFFull, "FFFFFFFFFFFFFFFF");
	CHECK_SET(u64, 0x123456789ABCDEF0ull, "123456789ABCDEF0");
	CHECK_SET(u64, 0x123456789ABCDEF0ull, "123456789ABCDEF0");

	CHECK_SET(decimal_string, "0", "0");
	CHECK_SET(decimal_string, "1", "1");
	CHECK_SET(decimal_string, "01234567890", "499602D2");
	CHECK_SET(decimal_string, "12345.67890", "499602D2");
	CHECK_SET(decimal_string, "12345.67890EOF", "499602D2");
	CHECK_SET(decimal_string, "012345678901", "2DFDC1C35");
	CHECK_SET(decimal_string, "12345.678901", "2DFDC1C35");
	CHECK_SET(decimal_string,
	          "340282366920938463463374607431768211456",
	          "100000000000000000000000000000000");

	CHECK_SET(hex_string, "0", "0");
	CHECK_SET(hex_string, "123456789ABCDEF0", "123456789ABCDEF0");

	const SerdBigint orig = bigint_from_hex("123456789ABCDEF01");
	SerdBigint       copy;
	serd_bigint_set(&copy, &orig);
	CHECK_HEXEQ("123456789ABCDEF01", &copy);
}

static void
check_print_hex(const char* value, const size_t len)
{
	assert(len <= MAX_HEX_LEN);

	FILE* const      stream = tmpfile();
	const SerdBigint num    = bigint_from_hex(value);

	serd_bigint_print_hex(stream, &num);
	fseek(stream, 0, SEEK_SET);

	char         buf[MAX_HEX_LEN] = {0};
	const size_t n_read           = fread(buf, 1, len, stream);

	fclose(stream);

	CHECK_STREQ(buf, value);
	assert(n_read == len);
}

static void
check_to_hex_string(const char*  value,
                    const size_t len,
                    const char*  expected,
                    const size_t expected_n_written)
{
	const SerdBigint num              = bigint_from_hex(value);
	char             buf[MAX_HEX_LEN] = {0};
	const size_t     n_written = serd_bigint_to_hex_string(&num, buf, len);

	assert(n_written == expected_n_written);
	CHECK_STREQ(buf, expected);
}

static void
test_output(void)
{
	check_print_hex("0", 1);
	check_print_hex("1", 1);
	check_print_hex("1234567", 7);
	check_print_hex("12345678", 8);
	check_print_hex("123456789ABCDEF0", 16);
	check_print_hex("123456789ABCDEF01", 17);

	check_to_hex_string("123456789", 1, "", 0);
	check_to_hex_string("123456789", 9, "12345678", 9);
	check_to_hex_string("123456789", 10, "123456789", 9);
	check_to_hex_string("123456789ABCDEF", 16, "123456789ABCDEF", 15);
}

static void
check_left_shifted_bigit(const char*    value,
                         const unsigned amount,
                         const unsigned index,
                         const Bigit    expected)
{
	const SerdBigint num = bigint_from_hex(value);
	const Bigit actual   = serd_bigint_left_shifted_bigit(&num, amount, index);

	assert(expected == actual);
}

static void
test_left_shifted_bigit(void)
{
	check_left_shifted_bigit("0", 100, 1, 0x0);
	check_left_shifted_bigit("1", 0, 0, 0x1);
	check_left_shifted_bigit("1", 1, 0, 0x2);
	check_left_shifted_bigit("1", 4, 0, 0x10);
	check_left_shifted_bigit("1", 32, 0, 0x0);
	check_left_shifted_bigit("1", 32, 1, 0x1);
	check_left_shifted_bigit("1", 64, 0, 0x0);
	check_left_shifted_bigit("1", 64, 1, 0x0);
	check_left_shifted_bigit("1", 64, 2, 0x1);
	check_left_shifted_bigit("123456789ABCDEF", 64, 0, 0x0);
	check_left_shifted_bigit("123456789ABCDEF", 64, 1, 0x0);
	check_left_shifted_bigit("123456789ABCDEF", 64, 2, 0x89ABCDEF);
	check_left_shifted_bigit("123456789ABCDEF", 64, 3, 0x1234567);
	check_left_shifted_bigit("123456789ABCDEF", 64, 4, 0x0);
	check_left_shifted_bigit("123456789ABCDEF", 65, 0, 0x0);
	check_left_shifted_bigit("123456789ABCDEF", 65, 1, 0x0);
	check_left_shifted_bigit("123456789ABCDEF", 65, 2, 0x13579BDE);
	check_left_shifted_bigit("123456789ABCDEF", 65, 3, 0x2468ACF);
	check_left_shifted_bigit("123456789ABCDEF", 65, 4, 0x0);
}

static void
check_shift_left(const char* value, const unsigned amount, const char* expected)
{
	SerdBigint num = bigint_from_hex(value);
	serd_bigint_shift_left(&num, amount);
	CHECK_HEXEQ(expected, &num);
}

static void
test_shift_left(void)
{
	check_shift_left("0", 100, "0");
	check_shift_left("1", 1, "2");
	check_shift_left("1", 4, "10");
	check_shift_left("1", 32, "100000000");
	check_shift_left("1", 64, "10000000000000000");
	check_shift_left("123456789ABCDEF", 0, "123456789ABCDEF");
	check_shift_left("123456789ABCDEF", 64, "123456789ABCDEF0000000000000000");
	check_shift_left("123456789ABCDEF", 65, "2468ACF13579BDE0000000000000000");
	check_shift_left("16B8B5E06EDC79", 23, "B5C5AF0376E3C800000");
}

static void
check_add_u32(const char* value, const uint32_t rhs, const char* expected)
{
	SerdBigint num = bigint_from_hex(value);
	serd_bigint_add_u32(&num, rhs);
	CHECK_HEXEQ(expected, &num);
}

static void
test_add_u32(void)
{
	check_add_u32("0", 1, "1");
	check_add_u32("1", 1, "2");
	check_add_u32("FFFFFFF", 1, "10000000");
	check_add_u32("FFFFFFFFFFFFFF", 1, "100000000000000");

	check_add_u32("10000000000000000000000000000000000080000000",
	              0x80000000,
	              "10000000000000000000000000000000000100000000");

	check_add_u32("10000000000000000000000000000000000000000000",
	              0x1,
	              "10000000000000000000000000000000000000000001");
}

static void
check_add(const char* lhs_hex, const char* rhs_hex, const char* expected)
{
	SerdBigint       lhs = bigint_from_hex(lhs_hex);
	const SerdBigint rhs = bigint_from_hex(rhs_hex);

	serd_bigint_add(&lhs, &rhs);
	CHECK_HEXEQ(expected, &lhs);
}

static void
test_add(void)
{
	check_add("1", "0", "1");
	check_add("1", "1", "2");
	check_add("FFFFFFF", "1", "10000000");
	check_add("FFFFFFFFFFFFFF", "1", "100000000000000");
	check_add("1", "1000000000000", "1000000000001");
	check_add("FFFFFFF", "1000000000000", "100000FFFFFFF");

	check_add("10000000000000000000000000000000000000000000",
	          "1",
	          "10000000000000000000000000000000000000000001");

	check_add("10000000000000000000000000000000000000000000",
	          "1000000000000",
	          "10000000000000000000000000000001000000000000");

	check_add("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
	          "1000000000000",
	          "1000000000000000000000000000000FFFFFFFFFFFF");

	check_add("10000000000000000000000000",
	          "1000000000000",
	          "10000000000001000000000000");

	check_add("1",
	          "10000000000000000000000000000",
	          "10000000000000000000000000001");

	check_add("FFFFFFF",
	          "10000000000000000000000000000",
	          "1000000000000000000000FFFFFFF");

	check_add("10000000000000000000000000000000000000000000",
	          "10000000000000000000000000000",
	          "10000000000000010000000000000000000000000000");

	check_add("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
	          "10000000000000000000000000000",
	          "100000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFF");

	check_add("10000000000000000000000000",
	          "10000000000000000000000000000",
	          "10010000000000000000000000000");
}

static void
check_subtract(const char* lhs_hex, const char* rhs_hex, const char* expected)
{
	SerdBigint       lhs = bigint_from_hex(lhs_hex);
	const SerdBigint rhs = bigint_from_hex(rhs_hex);

	serd_bigint_subtract(&lhs, &rhs);
	CHECK_HEXEQ(expected, &lhs);
}

static void
test_subtract(void)
{
	check_subtract("1", "0", "1");
	check_subtract("2", "0", "2");
	check_subtract("10000000", "1", "FFFFFFF");
	check_subtract("1FFFFFFFF00000000", "FFFFFFFF", "1FFFFFFFE00000001");
	check_subtract("100000000000000", "1", "FFFFFFFFFFFFFF");
	check_subtract("1000000000001", "1000000000000", "1");
	check_subtract("100000FFFFFFF", "1000000000000", "FFFFFFF");

	check_subtract("11F2678326EA00000000",
	               "0878678326EAC9000000",
	               "979FFFFFFFF37000000");

	check_subtract("10000000000000000000000000000000000000000001",
	               "00000000000000000000000000000000000000000001",
	               "10000000000000000000000000000000000000000000");

	check_subtract("10000000000000000000000000000001000000000000",
	               "00000000000000000000000000000001000000000000",
	               "10000000000000000000000000000000000000000000");

	check_subtract("1000000000000000000000000000000FFFFFFFFFFFF",
	               "0000000000000000000000000000001000000000000",
	               " FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");

	check_subtract("10000000000000000000000000",
	               "00000000000001000000000000",
	               " FFFFFFFFFFFFF000000000000");

	check_subtract("10000000000000000000000000",
	               "1000000000000000000000000",
	               "F000000000000000000000000");

	check_subtract("FFFFFFF000000000000000",
	               "0000000000000800000000",
	               "FFFFFFEFFFFFF800000000");

	check_subtract("10000000000000000000000000000000000000000000",
	               "00000000000000000000000000000000000800000000",
	               "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF800000000");

	check_subtract("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
	               "000000000000000000000000000000000800000000",
	               "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFF");
}

static void
check_subtract_left_shifted(const char*    lhs_hex,
                            const char*    rhs_hex,
                            const unsigned amount,
                            const char*    expected)
{
	SerdBigint       lhs = bigint_from_hex(lhs_hex);
	const SerdBigint rhs = bigint_from_hex(rhs_hex);

	serd_bigint_subtract_left_shifted(&lhs, &rhs, amount);
	CHECK_HEXEQ(expected, &lhs);
}

static void
test_subtract_left_shifted(void)
{
	check_subtract_left_shifted("1", "0", 1, "1");
	check_subtract_left_shifted("10000000", "1", 1, "FFFFFFE");
	check_subtract_left_shifted("100000000", "40000000", 2, "0");
	check_subtract_left_shifted("1000000000000000", "400000000000000", 2, "0");
	check_subtract_left_shifted("1000000000000000", "800000000000000", 1, "0");
	check_subtract_left_shifted("1000000000000000", "F", 16, "FFFFFFFFFF10000");
	check_subtract_left_shifted("1000000000000000", "F", 24, "FFFFFFFF1000000");
	check_subtract_left_shifted("100000000000000", "1", 0, "FFFFFFFFFFFFFF");
	check_subtract_left_shifted("100000000000000", "1", 56, "0");

	check_subtract_left_shifted("11F2678326EA00000000",
	                            "43C33C1937564800000",
	                            1,
	                            "979FFFFFFFF37000000");
}

static void
check_multiply_u32(const char* value, const uint32_t rhs, const char* expected)
{
	SerdBigint num = bigint_from_hex(value);
	serd_bigint_multiply_u32(&num, rhs);
	CHECK_HEXEQ(expected, &num);
}

static void
test_multiply_u32(void)
{
	check_multiply_u32("0", 0x25, "0");
	check_multiply_u32("123456789ABCDEF", 0, "0");
	check_multiply_u32("2", 0x5, "A");
	check_multiply_u32("10000000", 0x9, "90000000");
	check_multiply_u32("100000000000000", 0xFFFF, "FFFF00000000000000");
	check_multiply_u32("100000000000000", 0xFFFFFFFF, "FFFFFFFF00000000000000");
	check_multiply_u32("1234567ABCD", 0xFFF, "12333335552433");
	check_multiply_u32("1234567ABCD", 0xFFFFFFF, "12345679998A985433");
	check_multiply_u32("FFFFFFFFFFFFFFFF", 0x2, "1FFFFFFFFFFFFFFFE");
	check_multiply_u32("FFFFFFFFFFFFFFFF", 0x4, "3FFFFFFFFFFFFFFFC");
	check_multiply_u32("FFFFFFFFFFFFFFFF", 0xF, "EFFFFFFFFFFFFFFF1");
	check_multiply_u32("FFFFFFFFFFFFFFFF", 0xFFFFFF, "FFFFFEFFFFFFFFFF000001");
	check_multiply_u32("377654D193A171", 10000000, "210EDD6D4CDD2580EE80");
	check_multiply_u32("2E3F36D108373C00000", 10, "1CE78242A52285800000");

	check_multiply_u32("10000000000000000000000000",
	                   0x00000002,
	                   "20000000000000000000000000");

	check_multiply_u32("10000000000000000000000000",
	                   0x0000000F,
	                   "F0000000000000000000000000");

	check_multiply_u32("FFFF0000000000000000000000000",
	                   0xFFFF,
	                   "FFFE00010000000000000000000000000");

	check_multiply_u32("FFFF0000000000000000000000000",
	                   0xFFFFFFFF,
	                   "FFFEFFFF00010000000000000000000000000");

	check_multiply_u32("FFFF0000000000000000000000000",
	                   0xFFFFFFFF,
	                   "FFFEFFFF00010000000000000000000000000");
}

static void
check_multiply_u64(const char* value, const uint64_t rhs, const char* expected)
{
	SerdBigint num = bigint_from_hex(value);
	serd_bigint_multiply_u64(&num, rhs);
	CHECK_HEXEQ(expected, &num);
}

static void
test_multiply_u64(void)
{
	check_multiply_u64("0", 0x25, "0");
	check_multiply_u64("123456789ABCDEF", 0, "0");
	check_multiply_u64("123456789ABCDEF", 1, "123456789ABCDEF");
	check_multiply_u64("2", 0x5, "A");
	check_multiply_u64("10000000", 0x9, "90000000");
	check_multiply_u64("100000000000000", 0xFFFF, "FFFF00000000000000");
	check_multiply_u64("1234567ABCD", 0xFFF, "12333335552433");
	check_multiply_u64("1234567ABCD", 0xFFFFFFFFFFull, "1234567ABCBDCBA985433");
	check_multiply_u64("FFFFFFFFFFFFFFFF", 0x2, "1FFFFFFFFFFFFFFFE");
	check_multiply_u64("FFFFFFFFFFFFFFFF", 0x4, "3FFFFFFFFFFFFFFFC");
	check_multiply_u64("FFFFFFFFFFFFFFFF", 0xF, "EFFFFFFFFFFFFFFF1");

	check_multiply_u64("100000000000000",
	                   0xFFFFFFFFFFFFFFFFull,
	                   "FFFFFFFFFFFFFFFF00000000000000");

	check_multiply_u64("FFFFFFFFFFFFFFFF",
	                   0xFFFFFFFFFFFFFFFFull,
	                   "FFFFFFFFFFFFFFFE0000000000000001");

	check_multiply_u64("10000000000000000000000000",
	                   0x00000002,
	                   "20000000000000000000000000");

	check_multiply_u64("10000000000000000000000000",
	                   0x0000000F,
	                   "F0000000000000000000000000");

	check_multiply_u64("FFFF0000000000000000000000000",
	                   0xFFFF,
	                   "FFFE00010000000000000000000000000");

	check_multiply_u64("FFFF0000000000000000000000000",
	                   0xFFFFFFFF,
	                   "FFFEFFFF00010000000000000000000000000");

	check_multiply_u64("FFFF0000000000000000000000000",
	                   0xFFFFFFFFFFFFFFFFull,
	                   "FFFEFFFFFFFFFFFF00010000000000000000000000000");

	check_multiply_u64("377654D193A171",
	                   0x8AC7230489E80000ull,
	                   "1E10EE4B11D15A7F3DE7F3C7680000");
}

static void
check_multiply_pow10(const char*    value,
                     const unsigned exponent,
                     const char*    expected)
{
	SerdBigint num = bigint_from_hex(value);
	serd_bigint_multiply_pow10(&num, exponent);
	CHECK_HEXEQ(expected, &num);
}

static void
test_multiply_pow10(void)
{
	check_multiply_pow10("0", 10, "0");
	check_multiply_pow10("1234", 0, "1234");
	check_multiply_pow10("4D2", 1, "3034");
	check_multiply_pow10("4D2", 2, "1E208");
	check_multiply_pow10("4D2", 3, "12D450");
	check_multiply_pow10("4D2", 4, "BC4B20");
	check_multiply_pow10("4D2", 5, "75AEF40");
	check_multiply_pow10("4D2", 6, "498D5880");
	check_multiply_pow10("4D2", 7, "2DF857500");
	check_multiply_pow10("4D2", 8, "1CBB369200");
	check_multiply_pow10("4D2", 9, "11F5021B400");
	check_multiply_pow10("4D2", 10, "B3921510800");
	check_multiply_pow10("4D2", 11, "703B4D2A5000");
	check_multiply_pow10("4D2", 12, "4625103A72000");
	check_multiply_pow10("4D2", 13, "2BD72A24874000");
	check_multiply_pow10("4D2", 14, "1B667A56D488000");
	check_multiply_pow10("4D2", 15, "11200C7644D50000");
	check_multiply_pow10("4D2", 16, "AB407C9EB0520000");
	check_multiply_pow10("4D2", 17, "6B084DE32E3340000");
	check_multiply_pow10("4D2", 18, "42E530ADFCE0080000");
	check_multiply_pow10("4D2", 19, "29CF3E6CBE0C0500000");
	check_multiply_pow10("4D2", 20, "1A218703F6C783200000");
	check_multiply_pow10("4D2", 21, "1054F4627A3CB1F400000");
	check_multiply_pow10("4D2", 22, "A3518BD8C65EF38800000");
	check_multiply_pow10("4D2", 23, "6612F7677BFB5835000000");
	check_multiply_pow10("4D2", 24, "3FCBDAA0AD7D17212000000");
	check_multiply_pow10("4D2", 25, "27DF68A46C6E2E74B4000000");
	check_multiply_pow10("4D2", 26, "18EBA166C3C4DD08F08000000");
	check_multiply_pow10("4D2", 27, "F9344E03A5B0A259650000000");
	check_multiply_pow10("4D2", 28, "9BC0B0C2478E6577DF20000000");
	check_multiply_pow10("4D2", 29, "61586E796CB8FF6AEB740000000");
	check_multiply_pow10("4D2", 30, "3CD7450BE3F39FA2D32880000000");
	check_multiply_pow10("4D2", 31, "26068B276E7843C5C3F9500000000");

	check_multiply_pow10("4D2",
	                     50,
	                     "149D1B4CFED03B23AB5F4E1196EF45C0"
	                     "8000000000000");

	check_multiply_pow10("4D2",
	                     100,
	                     "5827249F27165024FBC47DFCA9359BF3"
	                     "16332D1B91ACEECF471FBAB06D9B2000"
	                     "0000000000000000000000");

	check_multiply_pow10("4D2",
	                     305,
	                     "AFBA390D657B0829339F5B98DC852A89"
	                     "682758E01829EADFD016D1528D4D548B"
	                     "80894B9ED9C2EC6A9CABB4881302A637"
	                     "9FF3058908FEAC310C52FCA009799718"
	                     "8260B0B2E2EC96E471B7892AD9B4F9F9"
	                     "A448CBF150D2E87F3934000000000000"
	                     "00000000000000000000000000000000"
	                     "00000000000000000000000000000000");

	check_multiply_pow10("123456789ABCDEF0", 0, "123456789ABCDEF0");
	check_multiply_pow10("123456789ABCDEF0",
	                     44,
	                     "51A1AD66ACE4E5C79209330F58F52DE3"
	                     "7CEFFF1F000000000000");
	check_multiply_pow10("123456789ABCDEF0",
	                     88,
	                     "16E0C6D18F4BFA7D0289B88382F56151"
	                     "EB9DA5DB09D56C9BA5D8305619CEE057"
	                     "4F00000000000000000000000");
	check_multiply_pow10("123456789ABCDEF0",
	                     132,
	                     "6696B1DA27BEA173B5EFCAABBB8492A9"
	                     "2AE3D97F7EE3C7314FB7E2FF8AEFD329"
	                     "F5F8202C22650BB79A7D9F3867F00000"
	                     "00000000000000000000000000000");
	check_multiply_pow10("123456789ABCDEF0",
	                     176,
	                     "1CC05FF0499D8BC7D8EBE0C6DC2FDC09"
	                     "E93765F3448235FB16AD09D98BBB3A0A"
	                     "843372D33A318EE63DAE6998DA59EF34"
	                     "B15C40A65B9B65ABF3CAF00000000000"
	                     "00000000000000000000000000000000"
	                     "00");
	check_multiply_pow10("123456789ABCDEF0",
	                     220,
	                     "80ED0FD9A6C0F56A495F466320D34E22"
	                     "507FAA83F0519E7FF909FDDBDA184682"
	                     "BB70D38D43284C828A3681540722E550"
	                     "960567BAB1C25389C1BE7705228BE8CC"
	                     "AF3EBD382829DF000000000000000000"
	                     "00000000000000000000000000000000"
	                     "000000");
	check_multiply_pow10("123456789ABCDEF0",
	                     264,
	                     "2421FD0F55C486D05211339D45EC2DC4"
	                     "12AE7A64DDFE619DA81B73C069088D3E"
	                     "83D7AA9F99B571815DE939A5275FB4A6"
	                     "9D8930798C01FB96781B9D633BB59AD5"
	                     "A7F322A7EC14154D1B8B5DF1718779A5"
	                     "2291FE0F000000000000000000000000"
	                     "00000000000000000000000000000000"
	                     "00000000000");
	check_multiply_pow10("123456789ABCDEF0",
	                     308,
	                     "A206620F35C83E9E780ECC07DCAF13BB"
	                     "0A7EE2E213747914340BC172D783BA56"
	                     "661E8DCFFD03C398BD66F5570F445AC6"
	                     "737126283C64AE1A289B9D8BB4531033"
	                     "8C3E34DE2D534187092ABA1F4706100E"
	                     "ECF66D14059461A05A9BEBBCCBA0F693"
	                     "F0000000000000000000000000000000"
	                     "00000000000000000000000000000000"
	                     "000000000000000");
}

static void
check_divmod(const char*    lhs_hex,
             const char*    rhs_hex,
             const uint32_t expected_divisor,
             const char*    expected_mod_hex)
{
	SerdBigint       lhs     = bigint_from_hex(lhs_hex);
	const SerdBigint rhs     = bigint_from_hex(rhs_hex);
	const uint32_t   divisor = serd_bigint_divmod(&lhs, &rhs);

	assert(divisor == expected_divisor);
	CHECK_HEXEQ(expected_mod_hex, &lhs);
}

static void
test_divmod(void)
{
	check_divmod("A", "2", 5, "0");
	check_divmod("B", "2", 5, "1");
	check_divmod("C", "2", 6, "0");
	check_divmod("A", "1234567890", 0, "A");
	check_divmod("FFFFFFFF", "3", 0x55555555, "0");
	check_divmod("12345678", "3789012", 5, "D9861E");
	check_divmod("70000001", "1FFFFFFF", 3, "10000004");
	check_divmod("28000000", "12A05F20", 2, "2BF41C0");
	check_divmod("FFFFFFFFF", "FFFFFFFF", 16, "F");
	check_divmod("100000000000001", "FFFFFFF", 0x10000001, "2");
	check_divmod("40000000000002", "2FAF0800000000", 1, "1050F800000002");
	check_divmod("40000000000000", "40000000000000", 1, "0");

	check_divmod("43DE72C3DF858FC278A361EEB5A000000",
	             "80000000000000000000000000000000",
	             8,
	             "3DE72C3DF858FC278A361EEB5A000000");

	check_divmod("B5C5AF0376E3C800000",
	             "43C33C1937564800000",
	             2,
	             "2E3F36D108373800000");


	check_divmod("A0000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "000000000000000000000000000000",
	             "20000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "000000000000000000000000000000",
	             5,
	             "0");

	check_divmod("A0000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "000000000000000000000000000001",
	             "20000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "000000000000000000000000000000",
	             5,
	             "1");

	check_divmod("B6080000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "000000000000000000000000000000FF"
	             "F",
	             "A0000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "000000000000000000000000000000",
	             0x1234,
	             "FFF");

	check_divmod("B6080000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "00000000000000000000000000000000"
	             "0",
	             "9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
	             "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
	             "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
	             "FFFFFFFFFFFFFFFFFFFFFFFFFFF001",
	             0x1234,
	             "1232DCC");
}

static void
check_compare(const char* lhs_hex, const char* rhs_hex, const int expected_cmp)
{
	const SerdBigint lhs = bigint_from_hex(lhs_hex);
	const SerdBigint rhs = bigint_from_hex(rhs_hex);
	const int        cmp = serd_bigint_compare(&lhs, &rhs);

	assert(cmp == expected_cmp);

	if (cmp) {
		const int rcmp = serd_bigint_compare(&rhs, &lhs);
		assert(rcmp == -cmp);
	}
}

static void
test_compare(void)
{
	check_compare("1", "1", 0);
	check_compare("0", "1", -1);
	check_compare("F", "FF", -1);
	check_compare("F", "FFFFFFFFF", -1);
	check_compare("10000000000000000", "10000000000000000", 0);
	check_compare("10000000000000000", "10000000000000001", -1);
	check_compare("FFFFFFFFFFFFFFFFF", "100000000000000000", -1);
	check_compare("10000000000000000", "10000000000000001", -1);
	check_compare("1234567890ABCDEF12345", "1234567890ABCDEF12345", 0);
	check_compare("1234567890ABCDEF12345", "1234567890ABCDEF12346", -1);

	const char* const huge = "123456789ABCDEF0123456789ABCDEF0"
	                         "123456789ABCDEF0123456789ABCDEF0"
	                         "123456789ABCDEF0123456789ABCDEF0"
	                         "123456789ABCDEF0123456789ABCDEF0";

	const char* const huger = "123456789ABCDEF0123456789ABCDEF0"
	                          "123456789ABCDEF0123456789ABCDEF0"
	                          "123456789ABCDEF0123456789ABCDEF0"
	                          "123456789ABCDEF0123456789ABCDEF1";

	check_compare(huge, huge, 0);
	check_compare(huger, huger, 0);
	check_compare(huge, huger, -1);
}

static void
check_plus_compare(const char* l_hex,
                   const char* p_hex,
                   const char* c_hex,
                   const int   expected_cmp)
{
	const SerdBigint l    = bigint_from_hex(l_hex);
	const SerdBigint p    = bigint_from_hex(p_hex);
	const SerdBigint c    = bigint_from_hex(c_hex);
	const int        cmp  = serd_bigint_plus_compare(&l, &p, &c);
	const int        rcmp = serd_bigint_plus_compare(&p, &l, &c);

	assert(cmp == expected_cmp);
	assert(rcmp == expected_cmp);
}

static void
test_plus_compare(void)
{
	check_plus_compare("1", "0", "1", 0);
	check_plus_compare("0", "0", "1", -1);
	check_plus_compare("FFFFFFFFF", "F", "F", 1);
	check_plus_compare("F", "F", "800000000", -1);
	check_plus_compare("F", "F", "80000000000000000", -1);
	check_plus_compare("800000000", "F", "80000000000000000", -1);
	check_plus_compare("2D79883D20000", "2D79883D20000", "5AF3107A40000", 0);
	check_plus_compare("20000000000000", "1", "20000000000000", +1);

	check_plus_compare("0588A503282FE00000",
	                   "0588A503282FE00000",
	                   "0AD78EBC5AC6200000",
	                   +1);

	check_plus_compare("2F06018572BEADD1280000000",
	                   "0204FCE5E3E25026110000000",
	                   "4000000000000000000000000",
	                   -1);

	check_plus_compare("1234567890ABCDEF12345",
	                   "000000000000000000001",
	                   "1234567890ABCDEF12345",
	                   +1);

	check_plus_compare("1234567890ABCDEF12344",
	                   "000000000000000000001",
	                   "1234567890ABCDEF12345",
	                   0);

	check_plus_compare("123456789000000000000",
	                   "0000000000ABCDEF12345",
	                   "1234567890ABCDEF12345",
	                   0);

	check_plus_compare("123456789000000000000",
	                   "0000000000ABCDEF12344",
	                   "1234567890ABCDEF12345",
	                   -1);

	check_plus_compare("123456789000000000000",
	                   "0000000000ABCDEF12346",
	                   "1234567890ABCDEF12345",
	                   1);

	check_plus_compare("123456789100000000000",
	                   "0000000000ABCDEF12345",
	                   "1234567890ABCDEF12345",
	                   1);

	check_plus_compare("123456788900000000000",
	                   "0000000000ABCDEF12345",
	                   "1234567890ABCDEF12345",
	                   -1);

	check_plus_compare("12345678900000000000000000000",
	                   "0000000000ABCDEF1234500000000",
	                   "1234567890ABCDEF1234500000000",
	                   0);

	check_plus_compare("12345678900000000000000000000",
	                   "0000000000ABCDEF1234400000000",
	                   "1234567890ABCDEF1234500000000",
	                   -1);

	check_plus_compare("12345678900000000000000000000",
	                   "0000000000ABCDEF1234600000000",
	                   "1234567890ABCDEF1234500000000",
	                   1);

	check_plus_compare("12345678910000000000000000000",
	                   "0000000000ABCDEF1234500000000",
	                   "1234567890ABCDEF1234500000000",
	                   1);
	check_plus_compare("12345678890000000000000000000",
	                   "0000000000ABCDEF1234500000000",
	                   "1234567890ABCDEF1234500000000",
	                   -1);

	check_plus_compare("12345678900000000000000000000",
	                   "000000000000000000ABCDEF12345",
	                   "123456789000000000ABCDEF12345",
	                   0);

	check_plus_compare("12345678900000000000000000000",
	                   "000000000000000000ABCDEF12346",
	                   "123456789000000000ABCDEF12345",
	                   1);

	check_plus_compare("12345678900000000000000000000",
	                   "000000000000000000ABCDEF12344",
	                   "123456789000000000ABCDEF12345",
	                   -1);

	check_plus_compare("12345678900000000000000000000",
	                   "000000000000000000ABCDEF12345",
	                   "12345678900000ABCDEF123450000",
	                   -1);

	check_plus_compare("12345678900000000000000000000",
	                   "000000000000000000ABCDEF12344",
	                   "12345678900000ABCDEF123450000",
	                   -1);

	check_plus_compare("12345678900000000000000000000",
	                   "000000000000000000ABCDEF12345",
	                   "12345678900000ABCDEF123450001",
	                   -1);

	check_plus_compare("12345678900000000000000000000",
	                   "00000000000000ABCDEF123460000",
	                   "12345678900000ABCDEF123450000",
	                   1);
}

static void
check_pow10(const unsigned exponent, const char* expected)
{
	SerdBigint num;
	serd_bigint_set_pow10(&num, exponent);
	CHECK_HEXEQ(expected, &num);
}

static void
test_set_pow10(void)
{
	check_pow10(0, "1");
	check_pow10(1, "A");
	check_pow10(2, "64");
	check_pow10(5, "186A0");
	check_pow10(8, "5F5E100");
	check_pow10(16, "2386F26FC10000");
	check_pow10(30, "C9F2C9CD04674EDEA40000000");
	check_pow10(31, "7E37BE2022C0914B2680000000");
}

int
main(void)
{
	test_set();
	test_output();
	test_left_shifted_bigit();
	test_shift_left();
	test_add_u32();
	test_add();
	test_subtract();
	test_subtract_left_shifted();
	test_multiply_u32();
	test_multiply_u64();
	test_multiply_pow10();
	test_divmod();
	test_compare();
	test_plus_compare();
	test_set_pow10();

	return 0;
}