70 lines
2.8 KiB
Plaintext
70 lines
2.8 KiB
Plaintext
import "@ffi/c"
|
|
import "@std/mem"
|
|
|
|
ParseError :: enum {
|
|
invalid_number
|
|
out_of_range
|
|
}
|
|
|
|
#! parse a complete ASCII decimal with an optional sign, decimal point, and exponent.
|
|
#! at least one mantissa digit and, after an exponent marker, one exponent digit are required.
|
|
#! whitespace, embedded NUL bytes, trailing bytes, hexadecimal, infinities, and NaNs are invalid.
|
|
#! finite subnormals and signed zero succeed; overflow and nonzero values rounded to zero are out of range.
|
|
#! temporary storage uses `allocator` and is released before returning; the input is only borrowed.
|
|
#! the Darwin backend uses strtod_l with LC_C_LOCALE, independent of the process and thread locales.
|
|
#! decimal rounding follows native libc and the caller's floating-point rounding mode.
|
|
parse_float proc(allocator mem.Allocator, text []u8) f64 ! (mem.AllocError | ParseError) {
|
|
if (text.len == 0) return .invalid_number
|
|
cursor := usize(0)
|
|
if (text[cursor] == '+' or text[cursor] == '-') cursor += 1
|
|
|
|
digits := usize(0)
|
|
nonzero := false
|
|
point := false
|
|
while cursor < text.len {
|
|
byte :: text[cursor]
|
|
if (byte >= '0' and byte <= '9') {
|
|
digits += 1
|
|
if (byte != '0') nonzero = true
|
|
} else if (byte == '.' and !point) {
|
|
point = true
|
|
} else break
|
|
cursor += 1
|
|
}
|
|
if (digits == 0) return .invalid_number
|
|
|
|
if (cursor < text.len and (text[cursor] == 'e' or text[cursor] == 'E')) {
|
|
cursor += 1
|
|
if (cursor < text.len and (text[cursor] == '+' or text[cursor] == '-')) cursor += 1
|
|
start :: cursor
|
|
while (cursor < text.len and text[cursor] >= '0' and text[cursor] <= '9') cursor += 1
|
|
if (cursor == start) return .invalid_number
|
|
}
|
|
if (cursor != text.len) return .invalid_number
|
|
if (text.len == maxval!(usize)) return .out_of_memory
|
|
|
|
buffer :: try mem.alloc(u8, allocator, text.len + 1)
|
|
defer mem.free(allocator, buffer)
|
|
memcopy!(buffer[..text.len], text)
|
|
buffer[text.len] = 0
|
|
c.__error()?^ = 0
|
|
value :: f64(strtod_l(ptrcast!(buffer.ptr), null, null))
|
|
range_error :: c.__error()?^ == c.ERANGE
|
|
|
|
# binary64 magnitudes preserve subnormals despite libc's ERANGE; infinity starts at 9218868437227405312.
|
|
# ERANGE at magnitude >= 1 also catches overflow saturated by directed rounding.
|
|
magnitude :: ptrcast!(@u64, &value)^ & u64(9223372036854775807)
|
|
if (
|
|
magnitude >= u64(9218868437227405312)
|
|
or (magnitude == 0 and nonzero)
|
|
or (range_error and magnitude >= u64(4607182418800017408))
|
|
) {
|
|
return .out_of_range
|
|
}
|
|
return value
|
|
}
|
|
|
|
# Darwin's locale_t is an opaque pointer and LC_C_LOCALE is null (<_locale_posix2008.h>).
|
|
@hide:file
|
|
strtod_l c_func(text ?*c_char, end ?*mut ?*mut c_char, locale ?*mut anyopaque) c_double
|