Skip to content

Commit

Permalink
Implement integer to string conversion via itoa/itos (#17)
Browse files Browse the repository at this point in the history
* add .DS_Store to gitignore

* add convert.h/c and impl str_frombuf

* basic itoa/s impl

* added some docs for new functions

* add unit tests and fix bugs
  • Loading branch information
a-lafrance authored Mar 23, 2022
1 parent 940d61b commit 0b372db
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
target/
.DS_Store
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ tests: $(TARGET_DIR) liblfc.a
../$(SRC_DIR)/lfc/collections/tests/set_tests.c \
../$(SRC_DIR)/lfc/collections/tests/str_tests.c \
../$(SRC_DIR)/lfc/collections/tests/vector_tests.c \
../$(SRC_DIR)/lfc/utils/tests/convert_tests.c \
../$(SRC_DIR)/lfc/utils/tests/hash_tests.c \
../$(SRC_DIR)/tests/utils.c \
../$(SRC_DIR)/tests/main.c
Expand All @@ -43,6 +44,7 @@ collections: $(TARGET_DIR)

utils: $(TARGET_DIR)
cd $(TARGET_DIR) && $(CC) $(LFC_CFLAGS) \
../$(SRC_DIR)/lfc/utils/convert.c \
../$(SRC_DIR)/lfc/utils/hash.c \
../$(SRC_DIR)/lfc/utils/mem.c \
../$(SRC_DIR)/lfc/utils/pair.c
Expand Down
6 changes: 6 additions & 0 deletions include/lfc/collections/str.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#define STR_DEFAULT_CAPACITY (size_t)32

// FUTURE: better string encoding that doesn't rely on char and ascii (like utf-8 or something)
// FUTURE: the current str_t is very difficult to output because it's not null-terminated. it might be worth
// adding null termination and some kind of printing function just to make that easier

/// An ASCII-encoded string wrapper around char* that provides additional safety & error-handling guarantees.
/// Much like Rust's `String`, `str_t` is an owned, mutable string type that manages its own internal string "buffer",
Expand All @@ -27,6 +29,10 @@ void str_init(str_t* str);
// Initialize a string from the string literal, copying it into the buffer.
void str_from(str_t* str, char* literal);

// Initialize a string from a character buffer. This differs from str_from() because
// it doesn't copy the contents of the buffer, and instead takes ownership of them.
void str_frombuf(str_t* str, char* buf, size_t buflen);

// Free the string & its contents.
void str_free(str_t* str);

Expand Down
17 changes: 17 additions & 0 deletions include/lfc/utils/convert.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef LFC_CONVERT_HEADER
#define LFC_CONVERT_HEADER

#include <stdint.h>
#include "lfc/collections/str.h"

// itoa and itos convert an integer to its string representation, storing it either in a null-terminated
// character buffer or an lfc owned string
char* itoa(int32_t n);
str_t itos(int32_t n);

// FUTURE: extend this for...
// different bases
// long integers
// unsigned integers?

#endif
8 changes: 7 additions & 1 deletion src/lfc/collections/str.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@ void str_init(str_t* str) {

void str_from(str_t* str, char* literal) {
str->len = strlen(literal);
str->capacity = str->len + STR_DEFAULT_CAPACITY;
str->capacity = str->len;
str->buffer = malloc_unwrap(sizeof(char), str->capacity, "[str_init] failed to alloc string data");
memcpy(str->buffer, literal, str->len);
}

void str_frombuf(str_t* str, char* buf, size_t buflen) {
str->len = buflen;
str->capacity = buflen;
str->buffer = buf;
}

void str_free(str_t* str) {
free(str->buffer);
}
Expand Down
2 changes: 1 addition & 1 deletion src/lfc/collections/tests/str_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void test_str_init_from_literal_correctly() {
str_from(&str, text);

assert_eq(str.len, text_len);
assert_eq(str.capacity, STR_DEFAULT_CAPACITY + text_len); // impl detail?
assert_eq(str.capacity, text_len); // impl detail?
assert_false(str_is_empty(&str));
assert(strncmp(str.buffer, text, text_len) == 0);

Expand Down
83 changes: 83 additions & 0 deletions src/lfc/utils/convert.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "lfc/utils/convert.h"

#include <math.h>
#include <stdint.h>

#include "lfc/collections/str.h"
#include "lfc/utils/mem.h"

// Because the max number of digits in an int is 10, it can be stored in an unsigned char
// This function also takes into account whether or not a negative sign is required
uint8_t __n_chars_required(int32_t n) {
uint8_t sign = 0;
uint32_t magnitude = n;

if (n < 0) {
sign = 1;
magnitude = (int64_t)n * -1;
} else if (n == 0) {
return 1;
}

return ceil(log10(magnitude)) + sign;
}

// NOTE: this function assumes the buffer has enough space to store the
// contents of the number's string repr inside of it. Violating this assumption
// leads to buffer overflow, which obviously is undefined behavior.
void __fill_buf(int32_t n, char* buf) {
uint8_t buflen = __n_chars_required(n);
uint32_t magnitude = n;

if (n < 0) {
// If n is negative, add the sign at the front of the buffer and revert to positive
buf[0] = '-';
magnitude = (int64_t)n * -1;
} else if (n == 0) {
buf[0] = '0';
return;
}

uint8_t i = 0;

while (magnitude > 0) {
// Grab each digit and insert it in the back of the buffer
uint8_t digit = magnitude % 10;

// NOTE: a bit of unsafety here: there's overflow if buflen is 0, but I think
// that's not possible because of the range of log
uint8_t index = buflen - 1 - i;
buf[index] = '0' + digit;

magnitude /= 10;
i++;
}
}

char* itoa(int32_t n) {
// Allocate a buffer that can hold the number's string repr,
// _and_ a null terminator
uint8_t buflen = __n_chars_required(n) + 1;
char* buf = malloc_unwrap(sizeof(char), buflen, "[itoa] failed to alloc buffer");

// Fill the buffer and add a null terminator
__fill_buf(n, buf);
buf[buflen - 1] = '\0';

return buf;
}

str_t itos(int32_t n) {
// Allocate a buffer that can hold the number's string repr,
// without a null terminator
uint8_t buflen = __n_chars_required(n);
char* buf = malloc_unwrap(sizeof(char), buflen, "[itoa] failed to alloc buffer");

// Fill the buffer
__fill_buf(n, buf);

// Initialize the string from the buffer
str_t str;
str_frombuf(&str, buf, buflen);
return str;
}
66 changes: 66 additions & 0 deletions src/lfc/utils/tests/convert_tests.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "lfc/utils/convert.h"

#include <stdint.h>
#include <string.h>

#include "tests/assert.h"
#include "tests/setup.h"

#define RUN_ITOA_TEST(n) start_test(); itoa_test_body(n, #n); end_test();

#include <stdio.h>

void itoa_test_body(int32_t n, char* exp_lit) {
str_t exp_str;
str_from(&exp_str, exp_lit);

char* result_buf = itoa(n);
assert(strcmp(exp_lit, result_buf) == 0);

str_t result_str = itos(n);
assert(str_eq(&exp_str, &result_str));
}

void test_itoa_zero_converted_correctly() {
RUN_ITOA_TEST(0);
}

void test_itoa_small_positive_converted_correctly() {
RUN_ITOA_TEST(150);
}

void test_itoa_large_positive_converted_correctly() {
RUN_ITOA_TEST(12500000);
}

void test_itoa_small_negative_converted_correctly() {
RUN_ITOA_TEST(-225);
}

void test_itoa_large_negative_converted_correctly() {
RUN_ITOA_TEST(-7500000);
}

void test_itoa_max_value_converted_correctly() {
// hardcode INT32_MAX to get the test to run w/ the macro
RUN_ITOA_TEST(2147483647);
}

void test_itoa_min_value_converted_correctly() {
// hardcode INT32_MIN to get the test to run w/ the macro
RUN_ITOA_TEST(-2147483648);
}

void run_convert_tests() {
start_suite();

test_itoa_zero_converted_correctly();
test_itoa_small_positive_converted_correctly();
test_itoa_large_positive_converted_correctly();
test_itoa_small_negative_converted_correctly();
test_itoa_large_negative_converted_correctly();
test_itoa_max_value_converted_correctly();
test_itoa_min_value_converted_correctly();

end_suite();
}
2 changes: 0 additions & 2 deletions src/lfc/utils/tests/hash_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ void barestr_simple_hash_sanity_check() {

void run_hash_tests() {
start_suite();

barestr_simple_hash_sanity_check();

end_suite();
}
4 changes: 4 additions & 0 deletions src/tests/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ void run_str_tests();
void run_vector_tests();

// utils
void run_convert_tests();
void run_hash_tests();

// TODO: fancier interface
Expand All @@ -23,13 +24,16 @@ int main(int argc, char** argv) {
run_str_tests();
run_vector_tests();

run_convert_tests();
run_hash_tests();
} else {
for (int i = 1; i < argc; i++) {
char* suite = argv[i];

if (strcmp(suite, "array") == 0) {
run_array_tests();
} else if (strcmp(suite, "convert") == 0) {
run_convert_tests();
} else if (strcmp(suite, "hash") == 0) {
run_hash_tests();
} else if (strcmp(suite, "linkedlist") == 0) {
Expand Down

0 comments on commit 0b372db

Please sign in to comment.