axl_ftoa
This commit is contained in:
parent
1282fa4523
commit
5736c02eb1
5 changed files with 208 additions and 18 deletions
168
axl_string.c
168
axl_string.c
|
|
@ -263,3 +263,171 @@ i8* axl_itoa(i64 val, i8* str, u8 base)
|
||||||
|
|
||||||
return axl_strrev(str);
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,6 @@ i8* axl_strstr(const i8* str, const i8* substr);
|
||||||
i8* axl_strrev(i8* str);
|
i8* axl_strrev(i8* str);
|
||||||
i8* axl_utoa(u64 val, i8* str, u8 base);
|
i8* axl_utoa(u64 val, i8* str, u8 base);
|
||||||
i8* axl_itoa(i64 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
|
#endif // AXL_STRING
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ typedef signed short i16;
|
||||||
typedef unsigned short u16;
|
typedef unsigned short u16;
|
||||||
typedef signed int i32;
|
typedef signed int i32;
|
||||||
typedef unsigned int u32;
|
typedef unsigned int u32;
|
||||||
|
typedef float f32;
|
||||||
typedef signed long long i64;
|
typedef signed long long i64;
|
||||||
typedef unsigned long long u64;
|
typedef unsigned long long u64;
|
||||||
|
|
||||||
|
|
@ -32,4 +33,6 @@ typedef unsigned long long u64;
|
||||||
#define I64_MAX 9223372036854775807LL
|
#define I64_MAX 9223372036854775807LL
|
||||||
#define U64_MAX 18446744073709551615ULL
|
#define U64_MAX 18446744073709551615ULL
|
||||||
|
|
||||||
|
#define AXL_F32_MAX_PRECISION 6
|
||||||
|
|
||||||
#endif // !AXL_TYPES_H
|
#endif // !AXL_TYPES_H
|
||||||
|
|
|
||||||
7
main.c
7
main.c
|
|
@ -4,6 +4,7 @@
|
||||||
#include "axl_string.h"
|
#include "axl_string.h"
|
||||||
#include "axl_rle.h"
|
#include "axl_rle.h"
|
||||||
#include "axl_memory.h"
|
#include "axl_memory.h"
|
||||||
|
#include "axl_types.h"
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
void* memset(void *s, int c, size_t n)
|
void* memset(void *s, int c, size_t n)
|
||||||
|
|
@ -89,6 +90,12 @@ int _start(void)
|
||||||
axl_strcat(text_buff, enc_size_str);
|
axl_strcat(text_buff, enc_size_str);
|
||||||
axl_puts(text_buff);
|
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;*/
|
/*u32 new_len = 0;*/
|
||||||
/*u8 src[] = "Bonjour le monde!";*/
|
/*u8 src[] = "Bonjour le monde!";*/
|
||||||
/*u32 src_len = axl_strlen((i8*)src);*/
|
/*u32 src_len = axl_strlen((i8*)src);*/
|
||||||
|
|
|
||||||
47
makefile
47
makefile
|
|
@ -1,29 +1,40 @@
|
||||||
CC = clang
|
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
|
%_test.exe: %_test.c
|
||||||
$(CC) $(TEST_CFLAGS) $(AXL_SOURCES) $< -o $@
|
$(CC) $(TEST_CFLAGS) $(AXL_SOURCES) $< -o $@
|
||||||
|
|
||||||
test: $(TEST_EXES)
|
test: clean $(TEST_EXES)
|
||||||
for %%i in ($(TEST_EXES)) do ( \
|
@echo "=== Running tests ==="
|
||||||
echo Running %%i && \
|
@for %%i in ($(TEST_EXES)) do (echo ---- %%i ---- && %%i || exit /b 1)
|
||||||
%%i || exit /b 1 \
|
|
||||||
)
|
|
||||||
|
|
||||||
$(TARGET): $(SOURCES)
|
$(TARGET): $(SOURCES)
|
||||||
$(CC) $(CFLAGS) $(SOURCES) -o $(TARGET) $(LDFLAGS) $(LIBS)
|
$(CC) $(TEST_CFLAGS) $(MINIMAL_CFLAGS) $^ -o $@ $(LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(TARGET)
|
-del /q *.exe *.o 2>nul
|
||||||
rm -f $(TEST_EXES)
|
|
||||||
|
|
||||||
.PHONY: all clean test
|
.PHONY: all clean test
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue