Skip to content

Commit

Permalink
Add shift operators for 128-bit integers (#163)
Browse files Browse the repository at this point in the history
Logical left- and right-shift
  • Loading branch information
dcreager committed Apr 23, 2020
1 parent a28c0ee commit c802919
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 39 deletions.
6 changes: 6 additions & 0 deletions Makefile.am
Expand Up @@ -260,6 +260,8 @@ u128_tests = \
tests/u128-tests-le.c.in \
tests/u128-tests-gt.c.in \
tests/u128-tests-ge.c.in \
tests/u128-tests-shl.c.in \
tests/u128-tests-shr.c.in \
tests/u128-tests-add.c.in \
tests/u128-tests-sub.c.in

Expand All @@ -277,6 +279,10 @@ tests/u128-tests-gt.c.in: tests/create-u128-test-cases.py
$(AM_V_GEN) $(PYTHON) $< gt $@
tests/u128-tests-ge.c.in: tests/create-u128-test-cases.py
$(AM_V_GEN) $(PYTHON) $< ge $@
tests/u128-tests-shl.c.in: tests/create-u128-test-cases.py
$(AM_V_GEN) $(PYTHON) $< shl $@
tests/u128-tests-shr.c.in: tests/create-u128-test-cases.py
$(AM_V_GEN) $(PYTHON) $< shr $@
tests/u128-tests-add.c.in: tests/create-u128-test-cases.py
$(AM_V_GEN) $(PYTHON) $< add $@
tests/u128-tests-sub.c.in: tests/create-u128-test-cases.py
Expand Down
126 changes: 95 additions & 31 deletions include/libcork/core/u128.h
Expand Up @@ -77,6 +77,13 @@ cork_u128_from_64(uint64_t i0, uint64_t i1)
return value;
}

CORK_INLINE
cork_u128
cork_u128_zero(void)
{
return cork_u128_from_64(0, 0);
}


#if CORK_HOST_ENDIANNESS == CORK_BIG_ENDIAN
#define cork_u128_be8(val, idx) ((val)._.u8[(idx)])
Expand All @@ -91,37 +98,6 @@ cork_u128_from_64(uint64_t i0, uint64_t i1)
#endif


CORK_INLINE
cork_u128
cork_u128_add(cork_u128 a, cork_u128 b)
{
cork_u128 result;
#if CORK_U128_HAVE_U128
result._.u128 = a._.u128 + b._.u128;
#else
result._.be64.lo = a._.be64.lo + b._.be64.lo;
result._.be64.hi =
a._.be64.hi + b._.be64.hi + (result._.be64.lo < a._.be64.lo);
#endif
return result;
}

CORK_INLINE
cork_u128
cork_u128_sub(cork_u128 a, cork_u128 b)
{
cork_u128 result;
#if CORK_U128_HAVE_U128
result._.u128 = a._.u128 - b._.u128;
#else
result._.be64.lo = a._.be64.lo - b._.be64.lo;
result._.be64.hi =
a._.be64.hi - b._.be64.hi - (result._.be64.lo > a._.be64.lo);
#endif
return result;
}


CORK_INLINE
bool
cork_u128_eq(cork_u128 a, cork_u128 b)
Expand Down Expand Up @@ -205,6 +181,94 @@ cork_u128_ge(cork_u128 a, cork_u128 b)
}


CORK_INLINE
cork_u128
cork_u128_shl(cork_u128 a, unsigned int b)
{
#if CORK_U128_HAVE_U128
cork_u128 result;
result._.u128 = a._.u128 << b;
return result;
#else
if (b == 0) {
return a;
}
if (b == 64) {
return cork_u128_from_64(a._.be64.lo, 0);
}
if (b >= 128) {
/* This is undefined behavior */
return cork_u128_zero();
}
if (b >= 64) {
return cork_u128_from_64(a._.be64.lo << (b - 64), 0);
}
return cork_u128_from_64(
(a._.be64.hi << b) + (a._.be64.lo >> (64 - b)),
a._.be64.lo << b);
#endif
}

CORK_INLINE
cork_u128
cork_u128_shr(cork_u128 a, unsigned int b)
{
#if CORK_U128_HAVE_U128
cork_u128 result;
result._.u128 = a._.u128 >> b;
return result;
#else
if (b == 0) {
return a;
}
if (b == 64) {
return cork_u128_from_64(0, a._.be64.hi);
}
if (b >= 128) {
/* This is undefined behavior */
return cork_u128_zero();
}
if (b >= 64) {
return cork_u128_from_64(0, a._.be64.hi >> (b - 64));
}
return cork_u128_from_64(
a._.be64.hi >> b,
(a._.be64.lo >> b) + (a._.be64.hi << (64 - b)));
#endif
}


CORK_INLINE
cork_u128
cork_u128_add(cork_u128 a, cork_u128 b)
{
cork_u128 result;
#if CORK_U128_HAVE_U128
result._.u128 = a._.u128 + b._.u128;
#else
result._.be64.lo = a._.be64.lo + b._.be64.lo;
result._.be64.hi =
a._.be64.hi + b._.be64.hi + (result._.be64.lo < a._.be64.lo);
#endif
return result;
}

CORK_INLINE
cork_u128
cork_u128_sub(cork_u128 a, cork_u128 b)
{
cork_u128 result;
#if CORK_U128_HAVE_U128
result._.u128 = a._.u128 - b._.u128;
#else
result._.be64.lo = a._.be64.lo - b._.be64.lo;
result._.be64.hi =
a._.be64.hi - b._.be64.hi - (result._.be64.lo > a._.be64.lo);
#endif
return result;
}


/* log10(x) = log2(x) / log2(10) ~= log2(x) / 3.322 */
#define CORK_U128_DECIMAL_LENGTH 44 /* ~= 128 / 3 + 1 + 1 */

Expand Down
17 changes: 13 additions & 4 deletions src/libcork/core/u128.c
Expand Up @@ -95,10 +95,7 @@ cork_u128
cork_u128_from_64(uint64_t i0, uint64_t i1);

cork_u128
cork_u128_add(cork_u128 a, cork_u128 b);

cork_u128
cork_u128_sub(cork_u128 a, cork_u128 b);
cork_u128_zero(void);

bool
cork_u128_eq(cork_u128 a, cork_u128 b);
Expand All @@ -117,3 +114,15 @@ cork_u128_gt(cork_u128 a, cork_u128 b);

bool
cork_u128_ge(cork_u128 a, cork_u128 b);

cork_u128
cork_u128_shl(cork_u128 a, unsigned int b);

cork_u128
cork_u128_shr(cork_u128 a, unsigned int b);

cork_u128
cork_u128_add(cork_u128 a, cork_u128 b);

cork_u128
cork_u128_sub(cork_u128 a, cork_u128 b);
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Expand Up @@ -73,6 +73,8 @@ make_u128_suite(lt)
make_u128_suite(le)
make_u128_suite(gt)
make_u128_suite(ge)
make_u128_suite(shl)
make_u128_suite(shr)
make_u128_suite(add)
make_u128_suite(sub)

Expand Down
40 changes: 40 additions & 0 deletions tests/create-u128-test-cases.py
Expand Up @@ -17,6 +17,10 @@
test_count = 25000
random.seed()

def random_shift_count():
# We want to test values [0, 128)
return int(random.random() * 128)

def random_128():
result = 0
for i in range(4):
Expand Down Expand Up @@ -54,6 +58,38 @@ def create_one_cmp_test_case(op, op_str):
output_one_cmp_test_case(op, op_str, rhs, rhs)


def create_one_shl_test_case():
lhs = random_128()
rhs = random_shift_count()
result = (lhs << rhs) % 2**128
print()
print("/* ", dec_128(lhs), sep="")
print(" * << ", rhs, sep="")
print(" * = ", dec_128(result), sep="")
print(" */")
print("{", sep="")
print(" ", hex_128(lhs), ",", sep="")
print(" ", rhs, ",", sep="")
print(" ", hex_128(result), sep="")
print("},", sep="")


def create_one_shr_test_case():
lhs = random_128()
rhs = random_shift_count()
result = (lhs >> rhs) % 2**128
print()
print("/* ", dec_128(lhs), sep="")
print(" * >> ", rhs, sep="")
print(" * = ", dec_128(result), sep="")
print(" */")
print("{", sep="")
print(" ", hex_128(lhs), ",", sep="")
print(" ", rhs, ",", sep="")
print(" ", hex_128(result), sep="")
print("},", sep="")


def create_one_add_test_case():
lhs = random_128()
rhs = random_128()
Expand Down Expand Up @@ -110,6 +146,10 @@ def create_one_sub_test_case():
create_one_cmp_test_case(operator.gt, "> ")
elif sys.argv[1] == "ge":
create_one_cmp_test_case(operator.ge, ">=")
elif sys.argv[1] == "shl":
create_one_shl_test_case()
elif sys.argv[1] == "shr":
create_one_shr_test_case()
elif sys.argv[1] == "add":
create_one_add_test_case()
elif sys.argv[1] == "sub":
Expand Down
85 changes: 81 additions & 4 deletions tests/test-u128.c
Expand Up @@ -147,6 +147,81 @@ START_TEST(test_u128_print)
END_TEST


struct shift_test {
uint64_t i0;
uint64_t i1;
unsigned int j;
uint64_t res0;
uint64_t res1;
};

static void
check_shift_test(cork_u128(op)(cork_u128, unsigned int), const char *op_str,
const struct shift_test *test)
{
cork_u128 value1 = cork_u128_from_64(test->i0, test->i1);
cork_u128 expected = cork_u128_from_64(test->res0, test->res1);
cork_u128 result = op(value1, test->j);
if (!cork_u128_eq(result, expected)) {
char buf1[CORK_U128_HEX_LENGTH];
char buf2[CORK_U128_HEX_LENGTH];
char buf3[CORK_U128_HEX_LENGTH];
const char *value1_str = cork_u128_to_hex(buf1, value1);
const char *expected_str = cork_u128_to_hex(buf2, expected);
const char *result_str = cork_u128_to_hex(buf3, result);
fprintf(stderr, "# %40s\n", value1_str);
fprintf(stderr, "# %s %40u\n", op_str, test->j);
fprintf(stderr, "# = %40s\n", expected_str);
fprintf(stderr, "# got %40s\n", result_str);
fail("Unexpected shift error");
}
}

static void
check_shift_tests_(cork_u128(op)(cork_u128, unsigned int), const char *op_str,
const struct shift_test *test, size_t count)
{
size_t i;
for (i = 0; i < count; i++) {
check_shift_test(op, op_str, test + i);
}
}

#define check_shift_tests(op, op_str, tests) \
check_shift_tests_(op, op_str, \
(tests), sizeof(tests) / sizeof(tests[0]))


static const struct shift_test SHL_TESTS[] = {
{0, 1, 1, 0, 2},
{0, UINT64_C(0x8000000000000000), 1, 1, 0},
{UINT64_C(0x8000000000000000), 0, 1, 0, 0},
#include "u128-tests-shl.c.in"
};

START_TEST(test_u128_shl)
{
DESCRIBE_TEST;
check_shift_tests(cork_u128_shl, "<<", SHL_TESTS);
}
END_TEST


static const struct shift_test SHR_TESTS[] = {
{0, 1, 1, 0, 0},
{0, 2, 1, 0, 1},
{1, 0, 1, 0, UINT64_C(0x8000000000000000)},
#include "u128-tests-shr.c.in"
};

START_TEST(test_u128_shr)
{
DESCRIBE_TEST;
check_shift_tests(cork_u128_shr, ">>", SHR_TESTS);
}
END_TEST


struct arithmetic_test {
uint64_t i0;
uint64_t i1;
Expand All @@ -163,20 +238,20 @@ check_arithmetic_test(cork_u128(op)(cork_u128, cork_u128), const char *op_str,
cork_u128 value1 = cork_u128_from_64(test->i0, test->i1);
cork_u128 value2 = cork_u128_from_64(test->j0, test->j1);
cork_u128 expected = cork_u128_from_64(test->res0, test->res1);
cork_u128 sum = op(value1, value2);
if (!cork_u128_eq(sum, expected)) {
cork_u128 result = op(value1, value2);
if (!cork_u128_eq(result, expected)) {
char buf1[CORK_U128_HEX_LENGTH];
char buf2[CORK_U128_HEX_LENGTH];
char buf3[CORK_U128_HEX_LENGTH];
char buf4[CORK_U128_HEX_LENGTH];
const char *value1_str = cork_u128_to_hex(buf1, value1);
const char *value2_str = cork_u128_to_hex(buf2, value2);
const char *expected_str = cork_u128_to_hex(buf3, expected);
const char *sum_str = cork_u128_to_hex(buf4, sum);
const char *result_str = cork_u128_to_hex(buf4, result);
fprintf(stderr, "# %40s\n", value1_str);
fprintf(stderr, "# %s %40s\n", op_str, value2_str);
fprintf(stderr, "# = %40s\n", expected_str);
fprintf(stderr, "# got %40s\n", sum_str);
fprintf(stderr, "# got %40s\n", result_str);
fail("Unexpected arithmetic error");
}
}
Expand Down Expand Up @@ -375,6 +450,8 @@ test_suite()

TCase *tc_u128 = tcase_create("u128");
tcase_add_test(tc_u128, test_u128_print);
tcase_add_test(tc_u128, test_u128_shl);
tcase_add_test(tc_u128, test_u128_shr);
tcase_add_test(tc_u128, test_u128_add);
tcase_add_test(tc_u128, test_u128_sub);
tcase_add_test(tc_u128, test_u128_eq);
Expand Down

0 comments on commit c802919

Please sign in to comment.