Skip to content

Commit

Permalink
Bigint append (#596)
Browse files Browse the repository at this point in the history
* Bigint append as binary implementation

Co-authored-by: Bret Ambrose <[email protected]>
  • Loading branch information
bretambrose and Bret Ambrose authored Mar 25, 2020
1 parent 7772e22 commit df9d981
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 6 deletions.
12 changes: 12 additions & 0 deletions include/aws/common/bigint.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ struct aws_bigint *aws_bigint_new_from_cursor(struct aws_allocator *allocator, s
AWS_COMMON_API
int aws_bigint_bytebuf_debug_output(const struct aws_bigint *bigint, struct aws_byte_buf *buffer);

/**
* Writes a bigint to a buffer as a big endian sequence of octets.
*
* If minimum_length is non-zero, then leading zero-bytes will pad the output as necessary. Otherwise only the minimum
* number of bytes will be written.
*/
AWS_COMMON_API
int aws_bigint_bytebuf_append_as_big_endian(
const struct aws_bigint *bigint,
struct aws_byte_buf *buffer,
size_t minimum_length);

/**
* Returns true if this integer is negative, false otherwise.
*/
Expand Down
113 changes: 107 additions & 6 deletions source/bigint.c
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,110 @@ int aws_bigint_bytebuf_debug_output(const struct aws_bigint *bigint, struct aws_
return AWS_OP_SUCCESS;
}

static size_t s_aws_bigint_byte_length(const struct aws_bigint *bigint) {
size_t digit_count = aws_array_list_length(&bigint->digits);

uint32_t high_digit = 0;
aws_array_list_get_at(&bigint->digits, &high_digit, digit_count - 1);

size_t non_zero_high_digit_bytes = 4;
if (high_digit < (1U << BITS_PER_BYTE)) {
non_zero_high_digit_bytes = 1;
} else if (high_digit < (1U << (2 * BITS_PER_BYTE))) {
non_zero_high_digit_bytes = 2;
} else if (high_digit < (1U << (3 * BITS_PER_BYTE))) {
non_zero_high_digit_bytes = 3;
}

return (digit_count - 1) * BYTES_PER_BASE_DIGIT + non_zero_high_digit_bytes;
}

#if _MSC_VER
# pragma warning(push)
# pragma warning(disable : 4221) /* allow struct member to reference format_buffer */
# pragma warning(disable : 4204) /* non-constant aggregate initializer */
#endif

int aws_bigint_bytebuf_append_as_big_endian(
const struct aws_bigint *bigint,
struct aws_byte_buf *buffer,
size_t minimum_length) {

AWS_PRECONDITION(aws_bigint_is_valid(bigint));
AWS_PRECONDITION(aws_byte_buf_is_valid(buffer));

size_t digit_count = aws_array_list_length(&bigint->digits);
size_t bigint_bytes = s_aws_bigint_byte_length(bigint);
size_t padding_bytes = 0;
if (bigint_bytes < minimum_length) {
padding_bytes = minimum_length - bigint_bytes;
}

int result = AWS_OP_ERR;

size_t required_capacity = bigint_bytes + padding_bytes;
if (aws_byte_buf_reserve_relative(buffer, required_capacity)) {
goto done;
}

if (padding_bytes > 0) {
uint8_t padding = 0;
struct aws_byte_cursor padding_cursor = {
.ptr = &padding,
.len = 1,
};

for (size_t i = 0; i < padding_bytes; ++i) {
if (aws_byte_buf_append(buffer, &padding_cursor)) {
goto done;
}
}
}

uint8_t digit_byte = 0;
struct aws_byte_cursor digit_bytes_cursor = {
.ptr = &digit_byte,
.len = 1,
};

bool should_write = false;

for (size_t i = 0; i < digit_count; ++i) {
size_t digit_index = digit_count - i - 1;

uint32_t current_digit = 0;
aws_array_list_get_at(&bigint->digits, &current_digit, digit_index);

for (size_t j = 0; j < BYTES_PER_BASE_DIGIT; ++j) {
digit_byte = (uint8_t)(current_digit >> (3 * BITS_PER_BYTE)) & ((1U << BITS_PER_BYTE) - 1);
current_digit <<= BITS_PER_BYTE;

if (!should_write) {
should_write = digit_byte > 0 || j + 1 == BYTES_PER_BASE_DIGIT;
}

if (should_write) {
if (aws_byte_buf_append(buffer, &digit_bytes_cursor)) {
goto done;
}
}
}
}

result = AWS_OP_SUCCESS;

done:

AWS_POSTCONDITION(aws_bigint_is_valid(bigint));
AWS_POSTCONDITION(aws_byte_buf_is_valid(buffer));

return result;
}

#if _MSC_VER
# pragma warning(pop)
#endif

bool aws_bigint_is_negative(const struct aws_bigint *bigint) {
AWS_PRECONDITION(aws_bigint_is_valid(bigint));

Expand Down Expand Up @@ -678,7 +782,6 @@ static int s_aws_bigint_subtract_magnitudes(

const struct aws_bigint *larger = lhs;
const struct aws_bigint *smaller = rhs;

if (ordering == AWS_BI_LESS_THAN) {
larger = rhs;
smaller = lhs;
Expand Down Expand Up @@ -723,7 +826,6 @@ static int s_aws_bigint_subtract_magnitudes(
}

int aws_bigint_add(struct aws_bigint *output, const struct aws_bigint *lhs, const struct aws_bigint *rhs) {

AWS_PRECONDITION(aws_bigint_is_valid(output));
AWS_PRECONDITION(aws_bigint_is_valid(lhs));
AWS_PRECONDITION(aws_bigint_is_valid(rhs));
Expand Down Expand Up @@ -768,7 +870,6 @@ int aws_bigint_add(struct aws_bigint *output, const struct aws_bigint *lhs, cons
}

int aws_bigint_subtract(struct aws_bigint *output, const struct aws_bigint *lhs, const struct aws_bigint *rhs) {

AWS_PRECONDITION(aws_bigint_is_valid(output));
AWS_PRECONDITION(aws_bigint_is_valid(lhs));
AWS_PRECONDITION(aws_bigint_is_valid(rhs));
Expand Down Expand Up @@ -1087,8 +1188,8 @@ static int s_aws_bigint_divide_by_single_digit(
aws_array_list_get_at(&dividend->digits, &current_dividend_digit, quotient_length - 1 - i);

const uint64_t two_digit_dividend = (current_remainder << BASE_BITS) + current_dividend_digit;

const uint32_t quotient_digit = (uint32_t)(two_digit_dividend / wide_divisor);

aws_array_list_set_at(&temp_quotient->digits, &quotient_digit, quotient_length - 1 - i);

current_remainder = two_digit_dividend % wide_divisor;
Expand All @@ -1100,6 +1201,7 @@ static int s_aws_bigint_divide_by_single_digit(
quotient->sign = 1;

const uint32_t final_remainder = (uint32_t)current_remainder;

aws_array_list_clear(&remainder->digits);
aws_array_list_push_back(&remainder->digits, &final_remainder);
remainder->sign = 1;
Expand Down Expand Up @@ -1203,7 +1305,6 @@ static int s_aws_bigint_normalized_divide(

for (size_t i = 0; i < quotient_digit_count; ++i) {
const size_t lhs_index = lhs_digit_count - i - 1;

AWS_FATAL_ASSERT(lhs_index >= 2);

uint32_t highest_digit = 0;
Expand Down Expand Up @@ -1238,7 +1339,6 @@ static int s_aws_bigint_normalized_divide(
aws_array_list_get_at(&lhs->digits, &current_digit, lhs_index - rhs_digit_count + j);

const uint64_t product_digit = q_guess * divisor_digit;

const uint64_t difference = (uint64_t)current_digit - product_digit - borrow;

current_digit = (uint32_t)(difference & LOWER_32_BIT_MASK);
Expand All @@ -1261,6 +1361,7 @@ static int s_aws_bigint_normalized_divide(
aws_array_list_get_at(&lhs->digits, &current_digit, lhs_index - rhs_digit_count + j);

const uint64_t digit_sum = (uint64_t)divisor_digit + (uint64_t)current_digit + carry;

carry = digit_sum >> BASE_BITS;
current_digit = (uint32_t)(digit_sum & LOWER_32_BIT_MASK);

Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ add_test_case(test_bigint_divide_error)
add_test_case(test_bigint_divide_edge)
add_test_case(test_bigint_divide_single_digit_divisor)
add_test_case(test_bigint_divide_general)
add_test_case(test_bigint_append_binary)

generate_test_driver(${PROJECT_NAME}-tests)

Expand Down
91 changes: 91 additions & 0 deletions tests/bigint_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -1957,3 +1957,94 @@ static int s_test_bigint_divide_general(struct aws_allocator *allocator, void *c
}

AWS_TEST_CASE(test_bigint_divide_general, s_test_bigint_divide_general)

struct bigint_append_binary_test {
const char *input_hex;
size_t minimum_length;
const char *expected_binary_data;
size_t expected_length;
};

static struct bigint_append_binary_test s_append_binary_cases[] = {
{
.input_hex = "0x0",
.minimum_length = 0,
.expected_binary_data = "\x00",
.expected_length = 1,
},
{
.input_hex = "0xff",
.minimum_length = 0,
.expected_binary_data = "\xFF",
.expected_length = 1,
},
{
.input_hex = "0x3aff",
.minimum_length = 0,
.expected_binary_data = "\x3a\xFF",
.expected_length = 2,
},
{
.input_hex = "0x3a78ff3483637",
.minimum_length = 0,
.expected_binary_data = "\x03\xa7\x8f\xf3\x48\x36\x37",
.expected_length = 7,
},
{
.input_hex = "fd3758b8a20010baa583fde3e7bb8532f4abd",
.minimum_length = 0,
.expected_binary_data = "\x0f\xd3\x75\x8b\x8a\x20\x01\x0b\xaa\x58\x3f\xde\x3e\x7b\xb8\x53\x2f\x4a\xbd",
.expected_length = 19,
},
{
.input_hex = "0xff",
.minimum_length = 5,
.expected_binary_data = "\x00\x00\x00\x00\xFF",
.expected_length = 5,
},
{
.input_hex = "0xffaabb88",
.minimum_length = 5,
.expected_binary_data = "\x00\xff\xaa\xbb\x88",
.expected_length = 5,
},
{
.input_hex = "940eb4747e656dd3f0c2679dca69c64baf7ea",
.minimum_length = 32,
.expected_binary_data = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x40\xeb\x47\x47\xe6\x56\xdd"
"\x3f\x0c\x26\x79\xdc\xa6\x9c\x64\xba\xf7\xea",
.expected_length = 32,
},
{
.input_hex = "10000000000000000000000000000000000000001",
.minimum_length = 32,
.expected_binary_data = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
.expected_length = 32,
},
};

static int s_test_bigint_append_binary(struct aws_allocator *allocator, void *ctx) {
(void)ctx;

for (size_t i = 0; i < AWS_ARRAY_SIZE(s_append_binary_cases); ++i) {
struct aws_byte_buf buffer;
aws_byte_buf_init(&buffer, allocator, 1);

struct bigint_append_binary_test *testcase = &s_append_binary_cases[i];

struct aws_bigint *test = aws_bigint_new_from_hex(allocator, aws_byte_cursor_from_c_str(testcase->input_hex));
ASSERT_NOT_NULL(test);

ASSERT_SUCCESS(aws_bigint_bytebuf_append_as_big_endian(test, &buffer, testcase->minimum_length));
ASSERT_TRUE(buffer.len == testcase->expected_length);
ASSERT_BIN_ARRAYS_EQUALS(testcase->expected_binary_data, testcase->expected_length, buffer.buffer, buffer.len);

aws_bigint_destroy(test);
aws_byte_buf_clean_up(&buffer);
}

return AWS_OP_SUCCESS;
}

AWS_TEST_CASE(test_bigint_append_binary, s_test_bigint_append_binary)

0 comments on commit df9d981

Please sign in to comment.