diff --git a/axl_string.c b/axl_string.c index 6707b3a..db7b3f9 100644 --- a/axl_string.c +++ b/axl_string.c @@ -263,3 +263,171 @@ i8* axl_itoa(i64 val, i8* str, u8 base) return axl_strrev(str); } + +#if !__STDC_HOSTED__ +int _fltused; +#endif // !__STDC_HOSTED__ + +/* + * @brief Converts float to string with specified precision + * + * @param val: Float value to convert + * @param str: Destination buffer (must be large enough) + * @param precision: Digits after decimal point (0-AXL_F32_MAX_PRECISION) + * @return Pointer to start of string + * + * @note Handles nan, inf, -inf, zero + * @note Uses scientific notation for very large/small numbers + * @note Removes trailing zeros from fractional part + */ +i8* axl_ftoa(f32 val, i8* str, u32 precision) +{ + if (!str) return NULL; + + // Clamp precision to meaningful range + if (precision > AXL_F32_MAX_PRECISION) + { + precision = AXL_F32_MAX_PRECISION; + } + + // Extract float components + union { f32 f; u32 u; } bits = { .f = val }; + u32 sign = bits.u >> 31; + u32 exponent = (bits.u >> 23) & 0xFF; + u32 mantissa = bits.u & 0x7FFFFF; + + // Handle NaN + if (exponent == 0xFF && mantissa != 0) + { + axl_strcpy(str, "nan"); + return str; + } + + // Handle Infinity + if (exponent == 0xFF && mantissa == 0) + { + if (sign) axl_strcpy(str, "-inf"); + else axl_strcpy(str, "inf"); + return str; + } + + // Handle zero and denormalized numbers (treat as zero) + if (exponent == 0) + { + i8* p = str; + if (sign) *p++ = '-'; + *p++ = '0'; + if (precision > 0) + { + *p++ = '.'; + for (u32 i = 0; i < precision; i++) + { + *p++ = '0'; + } + } + *p = '\0'; + return str; + } + + i8* start = str; + f32 abs_val = val; + + // Apply sign + if (sign) + { + *str++ = '-'; + abs_val = -abs_val; + } + + // Calculate magnitude for format selection + i32 magnitude = 0; + f32 temp = abs_val; + while (temp >= 10.0f) + { temp /= 10.0f; magnitude++; } + while (temp > 0.0f && temp < 1.0f) + { temp *= 10.0f; magnitude--; } + + // Use scientific notation for extreme ranges + if (magnitude > 15 || magnitude < -15) + { + // Normalize mantissa to [1, 10) + f32 mant = abs_val; + while (mant >= 10.0f) mant /= 10.0f; + while (mant > 0.0f && mant < 1.0f) mant *= 10.0f; + + // Integer part (single digit) + u64 int_part = (u64)mant; + axl_utoa(int_part, str, 10); + str += axl_strlen(str); + + // Fractional part + if (precision > 0) + { + *str++ = '.'; + f32 frac = mant - (f32)int_part; + for (u32 i = 0; i < precision; i++) + { + frac *= 10.0f; + u32 digit = (u32)frac; + *str++ = '0' + digit; + frac -= (f32)digit; + } + // Trim trailing zeros + while (*(str-1) == '0' && *(str-2) != '.') str--; + } + + // Add exponent + *str++ = 'e'; + if (magnitude >= 0) *str++ = '+'; + axl_itoa(magnitude, str, 10); + return start; + } + + // Fixed-point representation for normal range + + // Split into integer and fractional parts + u64 int_part = (u64)abs_val; + f32 frac_part = abs_val - (f32)int_part; + + // Write integer part + if (int_part == 0) + { + *str++ = '0'; + } + else + { + i8 buf[32]; + axl_utoa(int_part, buf, 10); + axl_strcpy(str, buf); + str += axl_strlen(buf); + } + + // Write fractional part + if (precision > 0) + { + *str++ = '.'; + f32 f = frac_part; + u32 i; + for (i = 0; i < precision; i++) + { + f *= 10.0f; + u32 digit = (u32)f; + *str++ = '0' + digit; + f -= (f32)digit; + } + + // Remove trailing zeros + while (str > start + 2 && *(str-1) == '0') + { + str--; + } + // Remove decimal point if no fractional digits remain + if (*(str-1) == '.') + { + str--; + } + } + + *str = '\0'; + return start; +} diff --git a/axl_string.h b/axl_string.h index ffd6878..22afe61 100644 --- a/axl_string.h +++ b/axl_string.h @@ -14,5 +14,6 @@ i8* axl_strstr(const i8* str, const i8* substr); i8* axl_strrev(i8* str); i8* axl_utoa(u64 val, i8* str, u8 base); i8* axl_itoa(i64 val, i8* str, u8 base); +i8* axl_ftoa(f32 val, i8* str, u32 precision); #endif // AXL_STRING diff --git a/axl_types.h b/axl_types.h index 423f797..6876a57 100644 --- a/axl_types.h +++ b/axl_types.h @@ -8,6 +8,7 @@ typedef signed short i16; typedef unsigned short u16; typedef signed int i32; typedef unsigned int u32; +typedef float f32; typedef signed long long i64; typedef unsigned long long u64; @@ -32,4 +33,6 @@ typedef unsigned long long u64; #define I64_MAX 9223372036854775807LL #define U64_MAX 18446744073709551615ULL +#define AXL_F32_MAX_PRECISION 6 + #endif // !AXL_TYPES_H diff --git a/main.c b/main.c index a601f20..df39e82 100644 --- a/main.c +++ b/main.c @@ -4,6 +4,7 @@ #include "axl_string.h" #include "axl_rle.h" #include "axl_memory.h" +#include "axl_types.h" #include void* memset(void *s, int c, size_t n) @@ -89,6 +90,12 @@ int _start(void) axl_strcat(text_buff, enc_size_str); axl_puts(text_buff); + + + axl_memset(text_buff, '\0', sizeof(text_buff)); + axl_ftoa(666.32456f, text_buff, 2); + axl_puts(text_buff); + /*u32 new_len = 0;*/ /*u8 src[] = "Bonjour le monde!";*/ /*u32 src_len = axl_strlen((i8*)src);*/ diff --git a/makefile b/makefile index 661ef4c..211dbda 100644 --- a/makefile +++ b/makefile @@ -1,29 +1,40 @@ CC = clang -AXL_SOURCES = $(filter-out main.c %_test.c, $(wildcard *.c)) -TEST_EXES = $(patsubst %.c,%.exe,$(wildcard *_test.c)) -TEST_CFLAGS = -Wall -Wextra -Werror -pedantic -std=c17 -static -Oz -fsigned-char -CFLAGS = $(TEST_CFLAGS) -nostdlib -ffreestanding -fno-builtin -mno-stack-arg-probe -LDFLAGS = -Wl,/SUBSYSTEM:CONSOLE,/ENTRY:_start -fuse-ld=lld -LIBS = -lkernel32 -SOURCES = $(AXL_SOURCES) main.c -TARGET = prog.exe -all: clean test $(TARGET) +AXL_SOURCES = $(filter-out main.c %_test.c, $(wildcard *.c)) +TEST_EXES = $(patsubst %.c,%.exe,$(wildcard *_test.c)) + +TEST_CFLAGS = -Wall -Wextra -Werror -pedantic -std=c17 -static -Oz -fsigned-char + +MINIMAL_CFLAGS = -nostdlib -ffreestanding -fno-builtin -mno-stack-arg-probe \ + -fno-stack-protector -fno-unwind-tables -fno-exceptions -fno-rtti \ + -fno-asynchronous-unwind-tables -fno-ident + +LDFLAGS = -fuse-ld=lld -static \ + -Wl,/SUBSYSTEM:CONSOLE \ + -Wl,/ENTRY:_start \ + -Wl,/OPT:NOICF \ + -Wl,/OPT:NOREF \ + -Wl,/NODEFAULTLIB \ + -Wl,/MERGE:.rdata=.text + +LIBS = -lkernel32 + +SOURCES = $(AXL_SOURCES) main.c +TARGET = prog.exe + +all: clean $(TARGET) %_test.exe: %_test.c $(CC) $(TEST_CFLAGS) $(AXL_SOURCES) $< -o $@ -test: $(TEST_EXES) - for %%i in ($(TEST_EXES)) do ( \ - echo Running %%i && \ - %%i || exit /b 1 \ - ) +test: clean $(TEST_EXES) + @echo "=== Running tests ===" + @for %%i in ($(TEST_EXES)) do (echo ---- %%i ---- && %%i || exit /b 1) -$(TARGET): $(SOURCES) - $(CC) $(CFLAGS) $(SOURCES) -o $(TARGET) $(LDFLAGS) $(LIBS) +$(TARGET): $(SOURCES) + $(CC) $(TEST_CFLAGS) $(MINIMAL_CFLAGS) $^ -o $@ $(LDFLAGS) $(LIBS) clean: - rm -f $(TARGET) - rm -f $(TEST_EXES) + -del /q *.exe *.o 2>nul .PHONY: all clean test