Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
## Description
`strconv` provides functions for converting strings to numbers and numbers to strings.
+474
View File
@@ -0,0 +1,474 @@
module strconv
// Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
// This file contains utilities for converting a string to a f64 variable.
// IEEE 754 standard is used.
// Know limitation: limited to 18 significant digits
//
// The code is inspired by:
// Grzegorz Kraszewski krashan@teleinfo.pb.edu.pl
// URL: http://krashan.ppa.pl/articles/stringtofloat/
// Original license: MIT
// 96 bit operation utilities
//
// Note: when u128 will be available, these function can be refactored.
// f32 constants
pub const single_plus_zero = u32(0x0000_0000)
pub const single_minus_zero = u32(0x8000_0000)
pub const single_plus_infinity = u32(0x7F80_0000)
pub const single_minus_infinity = u32(0xFF80_0000)
// f64 constants
pub const digits = 18
pub const double_plus_zero = u64(0x0000000000000000)
pub const double_minus_zero = u64(0x8000000000000000)
pub const double_plus_infinity = u64(0x7FF0000000000000)
pub const double_minus_infinity = u64(0xFFF0000000000000)
// char constants
pub const c_dpoint = `.`
pub const c_plus = `+`
pub const c_minus = `-`
pub const c_zero = `0`
pub const c_nine = `9`
pub const c_ten = u32(10)
// right logical shift 96 bit
fn lsr96(s2 u32, s1 u32, s0 u32) (u32, u32, u32) {
mut r0 := u32(0)
mut r1 := u32(0)
mut r2 := u32(0)
r0 = (s0 >> 1) | ((s1 & u32(1)) << 31)
r1 = (s1 >> 1) | ((s2 & u32(1)) << 31)
r2 = s2 >> 1
return r2, r1, r0
}
// left logical shift 96 bit
fn lsl96(s2 u32, s1 u32, s0 u32) (u32, u32, u32) {
mut r0 := u32(0)
mut r1 := u32(0)
mut r2 := u32(0)
r2 = (s2 << 1) | ((s1 & (u32(1) << 31)) >> 31)
r1 = (s1 << 1) | ((s0 & (u32(1) << 31)) >> 31)
r0 = s0 << 1
return r2, r1, r0
}
// sum on 96 bit
fn add96(s2 u32, s1 u32, s0 u32, d2 u32, d1 u32, d0 u32) (u32, u32, u32) {
mut w := u64(0)
mut r0 := u32(0)
mut r1 := u32(0)
mut r2 := u32(0)
w = u64(s0) + u64(d0)
r0 = u32(w)
w >>= 32
w += u64(s1) + u64(d1)
r1 = u32(w)
w >>= 32
w += u64(s2) + u64(d2)
r2 = u32(w)
return r2, r1, r0
}
// subtraction on 96 bit
fn sub96(s2 u32, s1 u32, s0 u32, d2 u32, d1 u32, d0 u32) (u32, u32, u32) {
mut w := u64(0)
mut r0 := u32(0)
mut r1 := u32(0)
mut r2 := u32(0)
w = u64(s0) - u64(d0)
r0 = u32(w)
w >>= 32
w += u64(s1) - u64(d1)
r1 = u32(w)
w >>= 32
w += u64(s2) - u64(d2)
r2 = u32(w)
return r2, r1, r0
}
// Utility functions
fn is_digit(x u8) bool {
return x >= c_zero && x <= c_nine
}
fn is_space(x u8) bool {
return x == `\t` || x == `\n` || x == `\v` || x == `\f` || x == `\r` || x == ` `
}
fn is_exp(x u8) bool {
return x == `E` || x == `e`
}
// Possible parser return values.
enum ParserState {
ok // parser finished OK
pzero // no digits or number is smaller than +-2^-1022
mzero // number is negative, module smaller
pinf // number is higher than +HUGE_VAL
minf // number is lower than -HUGE_VAL
invalid_number // invalid number, used for '#@%^' for example
extra_char // extra char after number
}
// parser tries to parse the given string into a number
// FIXME: need one char after the last char of the number
@[direct_array_access]
fn parser(s string) (ParserState, PrepNumber) {
mut digx := 0
mut result := ParserState.ok
mut expneg := false
mut expexp := 0
mut i := 0
mut pn := PrepNumber{}
// skip spaces
for i < s.len && s[i].is_space() {
i++
}
// check negatives
if s[i] == `-` {
pn.negative = true
i++
}
// positive sign ignore it
if s[i] == `+` {
i++
}
// read mantissa
for i < s.len && s[i].is_digit() {
if pn.mantissa == 0 && s[i] == c_zero {
i++
continue
}
// println("${i} => ${s[i]}")
if digx < digits {
pn.mantissa *= 10
pn.mantissa += u64(s[i] - c_zero)
digx++
} else if pn.exponent < 2147483647 {
pn.exponent++
}
i++
}
// read mantissa decimals
if i < s.len && s[i] == `.` {
i++
for i < s.len && s[i].is_digit() {
if pn.mantissa == 0 && s[i] == c_zero {
pn.exponent--
i++
continue
}
if digx < digits {
pn.mantissa *= 10
pn.mantissa += u64(s[i] - c_zero)
pn.exponent--
digx++
}
i++
}
}
// read exponent
if i < s.len && (s[i] == `e` || s[i] == `E`) {
i++
if i < s.len {
// esponent sign
if s[i] == c_plus {
i++
} else if s[i] == c_minus {
expneg = true
i++
}
for i < s.len && s[i].is_digit() {
if expexp < 214748364 {
expexp *= 10
expexp += int(s[i] - c_zero)
}
i++
}
}
}
if expneg {
expexp = -expexp
}
pn.exponent += expexp
if pn.mantissa == 0 {
if pn.negative {
result = .mzero
} else {
result = .pzero
}
} else if pn.exponent > 309 {
if pn.negative {
result = .minf
} else {
result = .pinf
}
} else if pn.exponent < -328 {
if pn.negative {
result = .mzero
} else {
result = .pzero
}
}
if i == 0 && s.len > 0 {
return ParserState.invalid_number, pn
}
if i != s.len {
return ParserState.extra_char, pn
}
return result, pn
}
// converter returns a u64 with the bit image of the f64 number
fn converter(mut pn PrepNumber) u64 {
mut binexp := 92
// s0,s1,s2 are the parts of a 96-bit precision integer
mut s2 := u32(0)
mut s1 := u32(0)
mut s0 := u32(0)
// q0,q1,q2 are the parts of a 96-bit precision integer
mut q2 := u32(0)
mut q1 := u32(0)
mut q0 := u32(0)
// r0,r1,r2 are the parts of a 96-bit precision integer
mut r2 := u32(0)
mut r1 := u32(0)
mut r0 := u32(0)
mask28 := u32(u64(0xF) << 28)
mut result := u64(0)
// working on 3 u32 to have 96 bit precision
s0 = u32(pn.mantissa & u64(0x00000000FFFFFFFF))
s1 = u32(pn.mantissa >> 32)
s2 = u32(0)
if pn.mantissa == 0 && pn.exponent <= 0 {
return if pn.negative { double_minus_zero } else { double_plus_zero }
}
// so we take the decimal exponent off
for pn.exponent > 0 {
q2, q1, q0 = lsl96(s2, s1, s0) // q = s * 2
r2, r1, r0 = lsl96(q2, q1, q0) // r = s * 4 <=> q * 2
s2, s1, s0 = lsl96(r2, r1, r0) // s = s * 8 <=> r * 2
s2, s1, s0 = add96(s2, s1, s0, q2, q1, q0) // s = (s * 8) + (s * 2) <=> s*10
pn.exponent--
for (s2 & mask28) != 0 {
q2, q1, q0 = lsr96(s2, s1, s0)
binexp++
s2 = q2
s1 = q1
s0 = q0
}
}
for pn.exponent < 0 {
for !((s2 & (u32(1) << 31)) != 0) {
q2, q1, q0 = lsl96(s2, s1, s0)
binexp--
s2 = q2
s1 = q1
s0 = q0
}
q2 = s2 / c_ten
r1 = s2 % c_ten
r2 = (s1 >> 8) | (r1 << 24)
q1 = r2 / c_ten
r1 = r2 % c_ten
r2 = ((s1 & u32(0xFF)) << 16) | (s0 >> 16) | (r1 << 24)
r0 = r2 / c_ten
r1 = r2 % c_ten
q1 = (q1 << 8) | ((r0 & u32(0x00FF0000)) >> 16)
q0 = r0 << 16
r2 = (s0 & u32(0xFFFF)) | (r1 << 16)
q0 |= r2 / c_ten
s2 = q2
s1 = q1
s0 = q0
pn.exponent++
}
// C.printf(c"mantissa before normalization: %08x%08x%08x binexp: %d \n", s2,s1,s0,binexp)
// normalization, the 28 bit in s2 must the leftest one in the variable
if s2 != 0 || s1 != 0 || s0 != 0 {
for (s2 & mask28) == 0 {
q2, q1, q0 = lsl96(s2, s1, s0)
binexp--
s2 = q2
s1 = q1
s0 = q0
}
}
// Handle subnormal (denormalized) numbers - very small numbers near zero
//
// Normal floats have an implicit leading 1 bit in their mantissa (like 1.xxxxx).
// When numbers get too small (binexp < -1022), we can't represent them normally.
// Instead, we use subnormals: set exponent to 0 and shift the mantissa right,
// losing precision gradually. This prevents abrupt underflow to zero.
//
// Example: 1.23e-308 is smaller than the minimum normal float, so we:
// 1. Keep the normalized mantissa from s2 and s1
// 2. Shift it right to "denormalize" it (the leading 1 moves into the mantissa)
// 3. Round correctly using the bits that were shifted out
// 4. Return with exponent = 0 (subnormal marker)
if binexp < -1022 && (s2 | s1) != 0 {
shift := -1022 - binexp
if shift > 60 {
return if pn.negative { double_minus_zero } else { double_plus_zero }
}
shifted := ((u64(s2) << 32) | u64(s1)) >> u32(shift)
q := (shifted >> 8) +
u64((shifted >> 7) & 1 != 0 && ((shifted & 0x7F) != 0 || (shifted >> 8) & 1 != 0))
return (q & 0x000FFFFFFFFFFFFF) | (u64(pn.negative) << 63)
}
// rounding if needed
/*
* "round half to even" algorithm
* Example for f32, just a reminder
*
* If bit 54 is 0, round down
* If bit 54 is 1
* If any bit beyond bit 54 is 1, round up
* If all bits beyond bit 54 are 0 (meaning the number is halfway between two floating-point numbers)
* If bit 53 is 0, round down
* If bit 53 is 1, round up
*/
/*
test case 1 complete
s2=0x1FFFFFFF
s1=0xFFFFFF80
s0=0x0
*/
/*
test case 1 check_round_bit
s2=0x18888888
s1=0x88888880
s0=0x0
*/
/*
test case check_round_bit + normalization
s2=0x18888888
s1=0x88888F80
s0=0x0
*/
// C.printf(c"mantissa before rounding: %08x%08x%08x binexp: %d \n", s2,s1,s0,binexp)
// s1 => 0xFFFFFFxx only F are represented
nbit := 7
check_round_bit := u32(1) << u32(nbit)
check_round_mask := u32(0xFFFFFFFF) << u32(nbit)
if (s1 & check_round_bit) != 0 {
// C.printf(c"need round!! check mask: %08x\n", s1 & ~check_round_mask )
if (s1 & ~check_round_mask) != 0 {
// C.printf(c"Add 1!\n")
s2, s1, s0 = add96(s2, s1, s0, 0, check_round_bit, 0)
} else {
// C.printf(c"All 0!\n")
if (s1 & (check_round_bit << u32(1))) != 0 {
// C.printf(c"Add 1 form -1 bit control!\n")
s2, s1, s0 = add96(s2, s1, s0, 0, check_round_bit, 0)
}
}
s1 = s1 & check_round_mask
s0 = u32(0)
// recheck normalization
if s2 & (mask28 << u32(1)) != 0 {
// C.printf(c"Renormalize!!\n")
q2, q1, q0 = lsr96(s2, s1, s0)
binexp++
// dump(binexp)
s2 = q2
s1 = q1
s0 = q0
}
}
// tmp := ( u64(s2 & ~mask28) << 24) | ((u64(s1) + u64(128)) >> 8)
// C.printf(c"mantissa after rounding : %08x %08x %08x binexp: %d \n", s2,s1,s0,binexp)
// C.printf(c"Tmp result: %016x\n",tmp)
// end rounding
// offset the binary exponent IEEE 754
binexp += 1023
if binexp > 2046 {
if pn.negative {
result = double_minus_infinity
} else {
result = double_plus_infinity
}
} else if binexp < 1 {
// Should not reach here for subnormals anymore (handled earlier)
// This is now only for true zeros
if pn.negative {
result = double_minus_zero
} else {
result = double_plus_zero
}
} else if s2 != 0 {
mut q := u64(0)
binexs2 := u64(binexp) << 52
q = (u64(s2 & ~mask28) << 24) | ((u64(s1) + u64(128)) >> 8) | binexs2
if pn.negative {
q |= (u64(1) << 63)
}
result = q
}
return result
}
@[params]
pub struct AtoF64Param {
pub:
allow_extra_chars bool // allow extra characters after number
}
// atof64 parses the string `s`, and if possible, converts it into a f64 number
pub fn atof64(s string, param AtoF64Param) !f64 {
if s.len == 0 {
return error('expected a number found an empty string')
}
mut res := Float64u{}
res_parsing, mut pn := parser(s)
match res_parsing {
.ok {
res.u = converter(mut pn)
}
.pzero {
res.u = double_plus_zero
}
.mzero {
res.u = double_minus_zero
}
.pinf {
res.u = double_plus_infinity
}
.minf {
res.u = double_minus_infinity
}
.extra_char {
if param.allow_extra_chars {
res.u = converter(mut pn)
} else {
return error('extra char after number')
}
}
.invalid_number {
return error('not a number')
}
}
return unsafe { res.f }
}
+12
View File
@@ -0,0 +1,12 @@
module strconv
// atof64 return a f64 from a string doing a parsing operation
pub fn atof64(s string) !f64 {
// TODO: handle parsing invalid numbers as close as possible to the pure V version
// that may be slower, but more portable, and will guarantee that higher level code
// works the same in the JS version, as well as in the C and Native versions.
res := 0.0
#res.val = Number(s.str)
return res
}
+142
View File
@@ -0,0 +1,142 @@
import strconv
/**********************************************************************
*
* String to float Test
*
**********************************************************************/
fn test_atof() {
//
// test set
//
// float64
src_num := [
f64(0.3),
-0.3,
0.004,
-0.004,
0.0,
-0.0,
31234567890123,
0.01,
2000,
-300,
]
// strings
src_num_str := [
'0.3',
'-0.3',
'0.004',
'-0.004',
'0.0',
'-0.0',
'31234567890123',
'1e-2',
'+2e+3',
'-3.0e+2',
]
// check conversion case 1 string <=> string
for c, x in src_num {
// slow atof
val := strconv.atof64(src_num_str[c]) or { panic(err) }
assert val.strlong() == x.strlong()
// quick atof
mut s1 := (strconv.atof_quick(src_num_str[c]).str())
mut s2 := (x.str())
delta := s1.f64() - s2.f64()
// println("${s1} ${s2} ${delta}")
assert delta < f64(1e-16)
// test C.atof
n1 := x.strsci(18)
n2 := f64(C.atof(&char(src_num_str[c].str))).strsci(18)
// println("${n1} ${n2}")
assert n1 == n2
}
// check conversion case 2 string <==> f64
// we don't test atof_quick because we already know the rounding error
for c, x in src_num_str {
b := src_num[c].strlong()
value := strconv.atof64(x) or { panic(err) }
a1 := value.strlong()
assert a1 == b
}
// special cases
mut f1 := f64(0.0)
mut ptr := unsafe { &u64(&f1) }
ptr = unsafe { &u64(&f1) }
// double_plus_zero
f1 = 0.0
assert *ptr == u64(0x0000000000000000)
// double_minus_zero
f1 = -0.0
assert *ptr == u64(0x8000000000000000)
println('DONE!')
}
fn test_atof_subnormal() {
// Test subnormal (denormalized) float numbers and edge cases
// These are very small numbers close to the f64 minimum
// IMPORTANT: Compare with hardcoded f64 literals, not .f64() which uses the same parser
// Normal numbers
assert strconv.atof64('1.0e-250') or { panic('parse error') } == 1.0e-250
assert strconv.atof64('2.5e-260') or { panic('parse error') } == 2.5e-260
// Transition zone
assert strconv.atof64('1.0e-300') or { panic('parse error') } == 1.0e-300
assert strconv.atof64('2.2250738585072014e-308') or { panic('parse error') } == 2.2250738585072014e-308
// Subnormal numbers (these fail without the fix)
assert strconv.atof64('1.23e-308') or { panic('parse error') } == 1.23e-308
assert strconv.atof64('1.0e-310') or { panic('parse error') } == 1.0e-310
assert strconv.atof64('5.0e-320') or { panic('parse error') } == 5.0e-320
assert strconv.atof64('5e-324') or { panic('parse error') } == 5e-324
// Negative subnormal
assert strconv.atof64('-1.0e-320') or { panic('parse error') } == -1.0e-320
}
fn test_atof_small_decimal_with_many_leading_zeroes() {
assert strconv.atof64('0.0000000000000000005') or { panic('parse error') } == strconv.atof64('5e-19') or {
panic('parse error')
}
assert strconv.atof64('-0.0000000000000000005') or { panic('parse error') } == strconv.atof64('-5e-19') or {
panic('parse error')
}
}
fn test_atof_errors() {
if x := strconv.atof64('') {
eprintln('> x: ${x}')
assert false // strconv.atof64 should have failed
} else {
assert err.str() == 'expected a number found an empty string'
}
if x := strconv.atof64('####') {
eprintln('> x: ${x}')
assert false // strconv.atof64 should have failed
} else {
assert err.str() == 'not a number'
}
if x := strconv.atof64('uu577.01') {
eprintln('> x: ${x}')
assert false // strconv.atof64 should have failed
} else {
assert err.str() == 'not a number'
}
if x := strconv.atof64('123.33xyz') {
eprintln('> x: ${x}')
assert false // strconv.atof64 should have failed
} else {
assert err.str() == 'extra char after number'
}
}
+347
View File
@@ -0,0 +1,347 @@
module strconv
/*
atof util
Copyright (c) 2019 Dario Deledda. All rights reserved.
Use of this source code is governed by an MIT license
that can be found in the LICENSE file.
This file contains utilities for convert a string in a f64 variable in a very quick way
IEEE 754 standard is used
Know limitation:
- round to 0 approximation
- loos of precision with big exponents
*/
// atof_quick return a f64 number from a string in a quick way
@[direct_array_access]
pub fn atof_quick(s string) f64 {
mut f := Float64u{} // result
mut sign := f64(1.0) // result sign
mut i := 0 // index
// skip white spaces
for i < s.len && s[i] == ` ` {
i++
}
// check sign
if i < s.len {
if s[i] == `-` {
sign = -1.0
i++
} else if s[i] == `+` {
i++
}
}
// infinite
if s[i] == `i` && i + 2 < s.len && s[i + 1] == `n` && s[i + 2] == `f` {
if sign > 0.0 {
f.u = double_plus_infinity
} else {
f.u = double_minus_infinity
}
return unsafe { f.f }
}
// skip zeros
for i < s.len && s[i] == `0` {
i++
// we have a zero, manage it
if i >= s.len {
if sign > 0.0 {
f.u = double_plus_zero
} else {
f.u = double_minus_zero
}
return unsafe { f.f }
}
}
// integer part
for i < s.len && (s[i] >= `0` && s[i] <= `9`) {
f.f *= f64(10.0)
f.f += f64(s[i] - `0`)
i++
}
// decimal point
if i < s.len && s[i] == `.` {
i++
mut frac_mul := f64(0.1)
for i < s.len && (s[i] >= `0` && s[i] <= `9`) {
f.f += f64(s[i] - `0`) * frac_mul
frac_mul *= f64(0.1)
i++
}
}
// exponent management
if i < s.len && (s[i] == `e` || s[i] == `E`) {
i++
mut exp := 0
mut exp_sign := 1
// negative exponent
if i < s.len {
if s[i] == `-` {
exp_sign = -1
i++
} else if s[i] == `+` {
i++
}
}
// skip zeros
for i < s.len && s[i] == `0` {
i++
}
for i < s.len && (s[i] >= `0` && s[i] <= `9`) {
exp *= 10
exp += int(s[i] - `0`)
i++
}
if exp_sign == 1 {
if exp > pos_exp.len {
if sign > 0 {
f.u = double_plus_infinity
} else {
f.u = double_minus_infinity
}
return unsafe { f.f }
}
tmp_mul := Float64u{
u: pos_exp[exp]
}
// C.printf("exp: %d [0x%016llx] %f,",exp,pos_exp[exp],tmp_mul)
f.f = unsafe { f.f * tmp_mul.f }
} else {
if exp > neg_exp.len {
if sign > 0 {
f.u = double_plus_zero
} else {
f.u = double_minus_zero
}
return unsafe { f.f }
}
tmp_mul := Float64u{
u: neg_exp[exp]
}
// C.printf("exp: %d [0x%016llx] %f,",exp,pos_exp[exp],tmp_mul)
f.f = unsafe { f.f * tmp_mul.f }
}
}
unsafe {
f.f = f.f * sign
return f.f
}
}
// positive exp of 10 binary form
const pos_exp = [u64(0x3ff0000000000000), u64(0x4024000000000000), u64(0x4059000000000000),
u64(0x408f400000000000), u64(0x40c3880000000000), u64(0x40f86a0000000000),
u64(0x412e848000000000), u64(0x416312d000000000), u64(0x4197d78400000000),
u64(0x41cdcd6500000000), u64(0x4202a05f20000000), u64(0x42374876e8000000),
u64(0x426d1a94a2000000), u64(0x42a2309ce5400000), u64(0x42d6bcc41e900000),
u64(0x430c6bf526340000), u64(0x4341c37937e08000), u64(0x4376345785d8a000),
u64(0x43abc16d674ec800), u64(0x43e158e460913d00), u64(0x4415af1d78b58c40),
u64(0x444b1ae4d6e2ef50), u64(0x4480f0cf064dd592), u64(0x44b52d02c7e14af6),
u64(0x44ea784379d99db4), u64(0x45208b2a2c280291), u64(0x4554adf4b7320335),
u64(0x4589d971e4fe8402), u64(0x45c027e72f1f1281), u64(0x45f431e0fae6d721),
u64(0x46293e5939a08cea), u64(0x465f8def8808b024), u64(0x4693b8b5b5056e17),
u64(0x46c8a6e32246c99c), u64(0x46fed09bead87c03), u64(0x4733426172c74d82),
u64(0x476812f9cf7920e3), u64(0x479e17b84357691b), u64(0x47d2ced32a16a1b1),
u64(0x48078287f49c4a1d), u64(0x483d6329f1c35ca5), u64(0x48725dfa371a19e7),
u64(0x48a6f578c4e0a061), u64(0x48dcb2d6f618c879), u64(0x4911efc659cf7d4c),
u64(0x49466bb7f0435c9e), u64(0x497c06a5ec5433c6), u64(0x49b18427b3b4a05c),
u64(0x49e5e531a0a1c873), u64(0x4a1b5e7e08ca3a8f), u64(0x4a511b0ec57e649a),
u64(0x4a8561d276ddfdc0), u64(0x4ababa4714957d30), u64(0x4af0b46c6cdd6e3e),
u64(0x4b24e1878814c9ce), u64(0x4b5a19e96a19fc41), u64(0x4b905031e2503da9),
u64(0x4bc4643e5ae44d13), u64(0x4bf97d4df19d6057), u64(0x4c2fdca16e04b86d),
u64(0x4c63e9e4e4c2f344), u64(0x4c98e45e1df3b015), u64(0x4ccf1d75a5709c1b),
u64(0x4d03726987666191), u64(0x4d384f03e93ff9f5), u64(0x4d6e62c4e38ff872),
u64(0x4da2fdbb0e39fb47), u64(0x4dd7bd29d1c87a19), u64(0x4e0dac74463a989f),
u64(0x4e428bc8abe49f64), u64(0x4e772ebad6ddc73d), u64(0x4eacfa698c95390c),
u64(0x4ee21c81f7dd43a7), u64(0x4f16a3a275d49491), u64(0x4f4c4c8b1349b9b5),
u64(0x4f81afd6ec0e1411), u64(0x4fb61bcca7119916), u64(0x4feba2bfd0d5ff5b),
u64(0x502145b7e285bf99), u64(0x50559725db272f7f), u64(0x508afcef51f0fb5f),
u64(0x50c0de1593369d1b), u64(0x50f5159af8044462), u64(0x512a5b01b605557b),
u64(0x516078e111c3556d), u64(0x5194971956342ac8), u64(0x51c9bcdfabc1357a),
u64(0x5200160bcb58c16c), u64(0x52341b8ebe2ef1c7), u64(0x526922726dbaae39),
u64(0x529f6b0f092959c7), u64(0x52d3a2e965b9d81d), u64(0x53088ba3bf284e24),
u64(0x533eae8caef261ad), u64(0x53732d17ed577d0c), u64(0x53a7f85de8ad5c4f),
u64(0x53ddf67562d8b363), u64(0x5412ba095dc7701e), u64(0x5447688bb5394c25),
u64(0x547d42aea2879f2e), u64(0x54b249ad2594c37d), u64(0x54e6dc186ef9f45c),
u64(0x551c931e8ab87173), u64(0x5551dbf316b346e8), u64(0x558652efdc6018a2),
u64(0x55bbe7abd3781eca), u64(0x55f170cb642b133f), u64(0x5625ccfe3d35d80e),
u64(0x565b403dcc834e12), u64(0x569108269fd210cb), u64(0x56c54a3047c694fe),
u64(0x56fa9cbc59b83a3d), u64(0x5730a1f5b8132466), u64(0x5764ca732617ed80),
u64(0x5799fd0fef9de8e0), u64(0x57d03e29f5c2b18c), u64(0x58044db473335def),
u64(0x583961219000356b), u64(0x586fb969f40042c5), u64(0x58a3d3e2388029bb),
u64(0x58d8c8dac6a0342a), u64(0x590efb1178484135), u64(0x59435ceaeb2d28c1),
u64(0x59783425a5f872f1), u64(0x59ae412f0f768fad), u64(0x59e2e8bd69aa19cc),
u64(0x5a17a2ecc414a03f), u64(0x5a4d8ba7f519c84f), u64(0x5a827748f9301d32),
u64(0x5ab7151b377c247e), u64(0x5aecda62055b2d9e), u64(0x5b22087d4358fc82),
u64(0x5b568a9c942f3ba3), u64(0x5b8c2d43b93b0a8c), u64(0x5bc19c4a53c4e697),
u64(0x5bf6035ce8b6203d), u64(0x5c2b843422e3a84d), u64(0x5c6132a095ce4930),
u64(0x5c957f48bb41db7c), u64(0x5ccadf1aea12525b), u64(0x5d00cb70d24b7379),
u64(0x5d34fe4d06de5057), u64(0x5d6a3de04895e46d), u64(0x5da066ac2d5daec4),
u64(0x5dd4805738b51a75), u64(0x5e09a06d06e26112), u64(0x5e400444244d7cab),
u64(0x5e7405552d60dbd6), u64(0x5ea906aa78b912cc), u64(0x5edf485516e7577f),
u64(0x5f138d352e5096af), u64(0x5f48708279e4bc5b), u64(0x5f7e8ca3185deb72),
u64(0x5fb317e5ef3ab327), u64(0x5fe7dddf6b095ff1), u64(0x601dd55745cbb7ed),
u64(0x6052a5568b9f52f4), u64(0x60874eac2e8727b1), u64(0x60bd22573a28f19d),
u64(0x60f2357684599702), u64(0x6126c2d4256ffcc3), u64(0x615c73892ecbfbf4),
u64(0x6191c835bd3f7d78), u64(0x61c63a432c8f5cd6), u64(0x61fbc8d3f7b3340c),
u64(0x62315d847ad00087), u64(0x6265b4e5998400a9), u64(0x629b221effe500d4),
u64(0x62d0f5535fef2084), u64(0x630532a837eae8a5), u64(0x633a7f5245e5a2cf),
u64(0x63708f936baf85c1), u64(0x63a4b378469b6732), u64(0x63d9e056584240fe),
u64(0x64102c35f729689f), u64(0x6444374374f3c2c6), u64(0x647945145230b378),
u64(0x64af965966bce056), u64(0x64e3bdf7e0360c36), u64(0x6518ad75d8438f43),
u64(0x654ed8d34e547314), u64(0x6583478410f4c7ec), u64(0x65b819651531f9e8),
u64(0x65ee1fbe5a7e7861), u64(0x6622d3d6f88f0b3d), u64(0x665788ccb6b2ce0c),
u64(0x668d6affe45f818f), u64(0x66c262dfeebbb0f9), u64(0x66f6fb97ea6a9d38),
u64(0x672cba7de5054486), u64(0x6761f48eaf234ad4), u64(0x679671b25aec1d89),
u64(0x67cc0e1ef1a724eb), u64(0x680188d357087713), u64(0x6835eb082cca94d7),
u64(0x686b65ca37fd3a0d), u64(0x68a11f9e62fe4448), u64(0x68d56785fbbdd55a),
u64(0x690ac1677aad4ab1), u64(0x6940b8e0acac4eaf), u64(0x6974e718d7d7625a),
u64(0x69aa20df0dcd3af1), u64(0x69e0548b68a044d6), u64(0x6a1469ae42c8560c),
u64(0x6a498419d37a6b8f), u64(0x6a7fe52048590673), u64(0x6ab3ef342d37a408),
u64(0x6ae8eb0138858d0a), u64(0x6b1f25c186a6f04c), u64(0x6b537798f4285630),
u64(0x6b88557f31326bbb), u64(0x6bbe6adefd7f06aa), u64(0x6bf302cb5e6f642a),
u64(0x6c27c37e360b3d35), u64(0x6c5db45dc38e0c82), u64(0x6c9290ba9a38c7d1),
u64(0x6cc734e940c6f9c6), u64(0x6cfd022390f8b837), u64(0x6d3221563a9b7323),
u64(0x6d66a9abc9424feb), u64(0x6d9c5416bb92e3e6), u64(0x6dd1b48e353bce70),
u64(0x6e0621b1c28ac20c), u64(0x6e3baa1e332d728f), u64(0x6e714a52dffc6799),
u64(0x6ea59ce797fb817f), u64(0x6edb04217dfa61df), u64(0x6f10e294eebc7d2c),
u64(0x6f451b3a2a6b9c76), u64(0x6f7a6208b5068394), u64(0x6fb07d457124123d),
u64(0x6fe49c96cd6d16cc), u64(0x7019c3bc80c85c7f), u64(0x70501a55d07d39cf),
u64(0x708420eb449c8843), u64(0x70b9292615c3aa54), u64(0x70ef736f9b3494e9),
u64(0x7123a825c100dd11), u64(0x7158922f31411456), u64(0x718eb6bafd91596b),
u64(0x71c33234de7ad7e3), u64(0x71f7fec216198ddc), u64(0x722dfe729b9ff153),
u64(0x7262bf07a143f6d4), u64(0x72976ec98994f489), u64(0x72cd4a7bebfa31ab),
u64(0x73024e8d737c5f0b), u64(0x7336e230d05b76cd), u64(0x736c9abd04725481),
u64(0x73a1e0b622c774d0), u64(0x73d658e3ab795204), u64(0x740bef1c9657a686),
u64(0x74417571ddf6c814), u64(0x7475d2ce55747a18), u64(0x74ab4781ead1989e),
u64(0x74e10cb132c2ff63), u64(0x75154fdd7f73bf3c), u64(0x754aa3d4df50af0b),
u64(0x7580a6650b926d67), u64(0x75b4cffe4e7708c0), u64(0x75ea03fde214caf1),
u64(0x7620427ead4cfed6), u64(0x7654531e58a03e8c), u64(0x768967e5eec84e2f),
u64(0x76bfc1df6a7a61bb), u64(0x76f3d92ba28c7d15), u64(0x7728cf768b2f9c5a),
u64(0x775f03542dfb8370), u64(0x779362149cbd3226), u64(0x77c83a99c3ec7eb0),
u64(0x77fe494034e79e5c), u64(0x7832edc82110c2f9), u64(0x7867a93a2954f3b8),
u64(0x789d9388b3aa30a5), u64(0x78d27c35704a5e67), u64(0x79071b42cc5cf601),
u64(0x793ce2137f743382), u64(0x79720d4c2fa8a031), u64(0x79a6909f3b92c83d),
u64(0x79dc34c70a777a4d), u64(0x7a11a0fc668aac70), u64(0x7a46093b802d578c),
u64(0x7a7b8b8a6038ad6f), u64(0x7ab137367c236c65), u64(0x7ae585041b2c477f),
u64(0x7b1ae64521f7595e), u64(0x7b50cfeb353a97db), u64(0x7b8503e602893dd2),
u64(0x7bba44df832b8d46), u64(0x7bf06b0bb1fb384c), u64(0x7c2485ce9e7a065f),
u64(0x7c59a742461887f6), u64(0x7c9008896bcf54fa), u64(0x7cc40aabc6c32a38),
u64(0x7cf90d56b873f4c7), u64(0x7d2f50ac6690f1f8), u64(0x7d63926bc01a973b),
u64(0x7d987706b0213d0a), u64(0x7dce94c85c298c4c), u64(0x7e031cfd3999f7b0),
u64(0x7e37e43c8800759c), u64(0x7e6ddd4baa009303), u64(0x7ea2aa4f4a405be2),
u64(0x7ed754e31cd072da), u64(0x7f0d2a1be4048f90), u64(0x7f423a516e82d9ba),
u64(0x7f76c8e5ca239029), u64(0x7fac7b1f3cac7433), u64(0x7fe1ccf385ebc8a0)]!
// negative exp of 10 binary form
const neg_exp = [u64(0x3ff0000000000000), u64(0x3fb999999999999a), u64(0x3f847ae147ae147b),
u64(0x3f50624dd2f1a9fc), u64(0x3f1a36e2eb1c432d), u64(0x3ee4f8b588e368f1),
u64(0x3eb0c6f7a0b5ed8d), u64(0x3e7ad7f29abcaf48), u64(0x3e45798ee2308c3a),
u64(0x3e112e0be826d695), u64(0x3ddb7cdfd9d7bdbb), u64(0x3da5fd7fe1796495),
u64(0x3d719799812dea11), u64(0x3d3c25c268497682), u64(0x3d06849b86a12b9b),
u64(0x3cd203af9ee75616), u64(0x3c9cd2b297d889bc), u64(0x3c670ef54646d497),
u64(0x3c32725dd1d243ac), u64(0x3bfd83c94fb6d2ac), u64(0x3bc79ca10c924223),
u64(0x3b92e3b40a0e9b4f), u64(0x3b5e392010175ee6), u64(0x3b282db34012b251),
u64(0x3af357c299a88ea7), u64(0x3abef2d0f5da7dd9), u64(0x3a88c240c4aecb14),
u64(0x3a53ce9a36f23c10), u64(0x3a1fb0f6be506019), u64(0x39e95a5efea6b347),
u64(0x39b4484bfeebc2a0), u64(0x398039d665896880), u64(0x3949f623d5a8a733),
u64(0x3914c4e977ba1f5c), u64(0x38e09d8792fb4c49), u64(0x38aa95a5b7f87a0f),
u64(0x38754484932d2e72), u64(0x3841039d428a8b8f), u64(0x380b38fb9daa78e4),
u64(0x37d5c72fb1552d83), u64(0x37a16c262777579c), u64(0x376be03d0bf225c7),
u64(0x37364cfda3281e39), u64(0x3701d7314f534b61), u64(0x36cc8b8218854567),
u64(0x3696d601ad376ab9), u64(0x366244ce242c5561), u64(0x362d3ae36d13bbce),
u64(0x35f7624f8a762fd8), u64(0x35c2b50c6ec4f313), u64(0x358dee7a4ad4b81f),
u64(0x3557f1fb6f10934c), u64(0x352327fc58da0f70), u64(0x34eea6608e29b24d),
u64(0x34b8851a0b548ea4), u64(0x34839dae6f76d883), u64(0x344f62b0b257c0d2),
u64(0x34191bc08eac9a41), u64(0x33e41633a556e1ce), u64(0x33b011c2eaabe7d8),
u64(0x3379b604aaaca626), u64(0x3344919d5556eb52), u64(0x3310747ddddf22a8),
u64(0x32da53fc9631d10d), u64(0x32a50ffd44f4a73d), u64(0x3270d9976a5d5297),
u64(0x323af5bf109550f2), u64(0x32059165a6ddda5b), u64(0x31d1411e1f17e1e3),
u64(0x319b9b6364f30304), u64(0x316615e91d8f359d), u64(0x3131ab20e472914a),
u64(0x30fc45016d841baa), u64(0x30c69d9abe034955), u64(0x309217aefe690777),
u64(0x305cf2b1970e7258), u64(0x3027288e1271f513), u64(0x2ff286d80ec190dc),
u64(0x2fbda48ce468e7c7), u64(0x2f87b6d71d20b96c), u64(0x2f52f8ac174d6123),
u64(0x2f1e5aacf2156838), u64(0x2ee8488a5b445360), u64(0x2eb36d3b7c36a91a),
u64(0x2e7f152bf9f10e90), u64(0x2e48ddbcc7f40ba6), u64(0x2e13e497065cd61f),
u64(0x2ddfd424d6faf031), u64(0x2da97683df2f268d), u64(0x2d745ecfe5bf520b),
u64(0x2d404bd984990e6f), u64(0x2d0a12f5a0f4e3e5), u64(0x2cd4dbf7b3f71cb7),
u64(0x2ca0aff95cc5b092), u64(0x2c6ab328946f80ea), u64(0x2c355c2076bf9a55),
u64(0x2c0116805effaeaa), u64(0x2bcb5733cb32b111), u64(0x2b95df5ca28ef40d),
u64(0x2b617f7d4ed8c33e), u64(0x2b2bff2ee48e0530), u64(0x2af665bf1d3e6a8d),
u64(0x2ac1eaff4a98553d), u64(0x2a8cab3210f3bb95), u64(0x2a56ef5b40c2fc77),
u64(0x2a225915cd68c9f9), u64(0x29ed5b561574765b), u64(0x29b77c44ddf6c516),
u64(0x2982c9d0b1923745), u64(0x294e0fb44f50586e), u64(0x29180c903f7379f2),
u64(0x28e33d4032c2c7f5), u64(0x28aec866b79e0cba), u64(0x2878a0522c7e7095),
u64(0x2843b374f06526de), u64(0x280f8587e7083e30), u64(0x27d9379fec069826),
u64(0x27a42c7ff0054685), u64(0x277023998cd10537), u64(0x2739d28f47b4d525),
u64(0x2704a8729fc3ddb7), u64(0x26d086c219697e2c), u64(0x269a71368f0f3047),
u64(0x2665275ed8d8f36c), u64(0x2630ec4be0ad8f89), u64(0x25fb13ac9aaf4c0f),
u64(0x25c5a956e225d672), u64(0x2591544581b7dec2), u64(0x255bba08cf8c979d),
u64(0x25262e6d72d6dfb0), u64(0x24f1bebdf578b2f4), u64(0x24bc6463225ab7ec),
u64(0x2486b6b5b5155ff0), u64(0x24522bc490dde65a), u64(0x241d12d41afca3c3),
u64(0x23e7424348ca1c9c), u64(0x23b29b69070816e3), u64(0x237dc574d80cf16b),
u64(0x2347d12a4670c123), u64(0x23130dbb6b8d674f), u64(0x22de7c5f127bd87e),
u64(0x22a8637f41fcad32), u64(0x227382cc34ca2428), u64(0x223f37ad21436d0c),
u64(0x2208f9574dcf8a70), u64(0x21d3faac3e3fa1f3), u64(0x219ff779fd329cb9),
u64(0x216992c7fdc216fa), u64(0x2134756ccb01abfb), u64(0x21005df0a267bcc9),
u64(0x20ca2fe76a3f9475), u64(0x2094f31f8832dd2a), u64(0x2060c27fa028b0ef),
u64(0x202ad0cc33744e4b), u64(0x1ff573d68f903ea2), u64(0x1fc1297872d9cbb5),
u64(0x1f8b758d848fac55), u64(0x1f55f7a46a0c89dd), u64(0x1f2192e9ee706e4b),
u64(0x1eec1e43171a4a11), u64(0x1eb67e9c127b6e74), u64(0x1e81fee341fc585d),
u64(0x1e4ccb0536608d61), u64(0x1e1708d0f84d3de7), u64(0x1de26d73f9d764b9),
u64(0x1dad7becc2f23ac2), u64(0x1d779657025b6235), u64(0x1d42deac01e2b4f7),
u64(0x1d0e3113363787f2), u64(0x1cd8274291c6065b), u64(0x1ca3529ba7d19eaf),
u64(0x1c6eea92a61c3118), u64(0x1c38bba884e35a7a), u64(0x1c03c9539d82aec8),
u64(0x1bcfa885c8d117a6), u64(0x1b99539e3a40dfb8), u64(0x1b6442e4fb671960),
u64(0x1b303583fc527ab3), u64(0x1af9ef3993b72ab8), u64(0x1ac4bf6142f8eefa),
u64(0x1a90991a9bfa58c8), u64(0x1a5a8e90f9908e0d), u64(0x1a253eda614071a4),
u64(0x19f0ff151a99f483), u64(0x19bb31bb5dc320d2), u64(0x1985c162b168e70e),
u64(0x1951678227871f3e), u64(0x191bd8d03f3e9864), u64(0x18e6470cff6546b6),
u64(0x18b1d270cc51055f), u64(0x187c83e7ad4e6efe), u64(0x1846cfec8aa52598),
u64(0x18123ff06eea847a), u64(0x17dd331a4b10d3f6), u64(0x17a75c1508da432b),
u64(0x1772b010d3e1cf56), u64(0x173de6815302e556), u64(0x1707eb9aa8cf1dde),
u64(0x16d322e220a5b17e), u64(0x169e9e369aa2b597), u64(0x16687e92154ef7ac),
u64(0x16339874ddd8c623), u64(0x15ff5a549627a36c), u64(0x15c91510781fb5f0),
u64(0x159410d9f9b2f7f3), u64(0x15600d7b2e28c65c), u64(0x1529af2b7d0e0a2d),
u64(0x14f48c22ca71a1bd), u64(0x14c0701bd527b498), u64(0x148a4cf9550c5426),
u64(0x14550a6110d6a9b8), u64(0x1420d51a73deee2d), u64(0x13eaee90b964b047),
u64(0x13b58ba6fab6f36c), u64(0x13813c85955f2923), u64(0x134b9408eefea839),
u64(0x1316100725988694), u64(0x12e1a66c1e139edd), u64(0x12ac3d79c9b8fe2e),
u64(0x12769794a160cb58), u64(0x124212dd4de70913), u64(0x120ceafbafd80e85),
u64(0x11d72262f3133ed1), u64(0x11a281e8c275cbda), u64(0x116d9ca79d89462a),
u64(0x1137b08617a104ee), u64(0x1102f39e794d9d8b), u64(0x10ce5297287c2f45),
u64(0x1098421286c9bf6b), u64(0x1063680ed23aff89), u64(0x102f0ce4839198db),
u64(0x0ff8d71d360e13e2), u64(0x0fc3df4a91a4dcb5), u64(0x0f8fcbaa82a16121),
u64(0x0f596fbb9bb44db4), u64(0x0f245962e2f6a490), u64(0x0ef047824f2bb6da),
u64(0x0eba0c03b1df8af6), u64(0x0e84d6695b193bf8), u64(0x0e50ab877c142ffa),
u64(0x0e1aac0bf9b9e65c), u64(0x0de5566ffafb1eb0), u64(0x0db111f32f2f4bc0),
u64(0x0d7b4feb7eb212cd), u64(0x0d45d98932280f0a), u64(0x0d117ad428200c08),
u64(0x0cdbf7b9d9cce00d), u64(0x0ca65fc7e170b33e), u64(0x0c71e6398126f5cb),
u64(0x0c3ca38f350b22df), u64(0x0c06e93f5da2824c), u64(0x0bd25432b14ecea3),
u64(0x0b9d53844ee47dd1), u64(0x0b677603725064a8), u64(0x0b32c4cf8ea6b6ec),
u64(0x0afe07b27dd78b14), u64(0x0ac8062864ac6f43), u64(0x0a9338205089f29c),
u64(0x0a5ec033b40fea93), u64(0x0a2899c2f6732210), u64(0x09f3ae3591f5b4d9),
u64(0x09bf7d228322baf5), u64(0x098930e868e89591), u64(0x0954272053ed4474),
u64(0x09201f4d0ff10390), u64(0x08e9cbae7fe805b3), u64(0x08b4a2f1ffecd15c),
u64(0x0880825b3323dab0), u64(0x084a6a2b85062ab3), u64(0x081521bc6a6b555c),
u64(0x07e0e7c9eebc444a), u64(0x07ab0c764ac6d3a9), u64(0x0775a391d56bdc87),
u64(0x07414fa7ddefe3a0), u64(0x070bb2a62fe638ff), u64(0x06d62884f31e93ff),
u64(0x06a1ba03f5b21000), u64(0x066c5cd322b67fff), u64(0x0636b0a8e891ffff),
u64(0x060226ed86db3333), u64(0x05cd0b15a491eb84), u64(0x05973c115074bc6a),
u64(0x05629674405d6388), u64(0x052dbd86cd6238d9), u64(0x04f7cad23de82d7b),
u64(0x04c308a831868ac9), u64(0x048e74404f3daadb), u64(0x04585d003f6488af),
u64(0x04237d99cc506d59), u64(0x03ef2f5c7a1a488e), u64(0x03b8f2b061aea072),
u64(0x0383f559e7bee6c1), u64(0x034feef63f97d79c), u64(0x03198bf832dfdfb0),
u64(0x02e46ff9c24cb2f3), u64(0x02b059949b708f29), u64(0x027a28edc580e50e),
u64(0x0244ed8b04671da5), u64(0x0210be08d0527e1d), u64(0x01dac9a7b3b7302f),
u64(0x01a56e1fc2f8f359), u64(0x017124e63593f5e1), u64(0x013b6e3d22865634),
u64(0x0105f1ca820511c3), u64(0x00d18e3b9b374169), u64(0x009c16c5c5253575),
u64(0x0066789e3750f791), u64(0x0031fa182c40c60d), u64(0x000730d67819e8d2),
u64(0x0000b8157268fdaf), u64(0x000012688b70e62b), u64(0x000001d74124e3d1),
u64(0x0000002f201d49fb), u64(0x00000004b6695433), u64(0x0000000078a42205),
u64(0x000000000c1069cd), u64(0x000000000134d761), u64(0x00000000001ee257),
u64(0x00000000000316a2), u64(0x0000000000004f10), u64(0x00000000000007e8),
u64(0x00000000000000ca), u64(0x0000000000000014), u64(0x0000000000000002)]!
+366
View File
@@ -0,0 +1,366 @@
module strconv
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
// TODO: use options, or some way to return default with error.
// int_size is the size in bits of an int or uint value.
// int_size = 32 << (~u32(0) >> 63)
// max_u64 = u64(u64(1 << 63) - 1)
const int_size = 32
@[inline]
pub fn byte_to_lower(c u8) u8 {
return c | 32
}
// common_parse_uint is called by parse_uint and allows the parsing
// to stop on non or invalid digit characters and return with an error
pub fn common_parse_uint(s string, _base int, _bit_size int, error_on_non_digit bool, error_on_high_digit bool) !u64 {
result, err := common_parse_uint2(s, _base, _bit_size)
// TODO: error_on_non_digit and error_on_high_digit have no difference
if err != 0 && (error_on_non_digit || error_on_high_digit) {
match err {
-1 { return error('common_parse_uint: wrong base ${_base} for ${s}') }
-2 { return error('common_parse_uint: wrong bit size ${_bit_size} for ${s}') }
-3 { return error('common_parse_uint: integer overflow ${s}') }
else { return error('common_parse_uint: syntax error ${s}') }
}
}
return result
}
// the first returned value contains the parsed value,
// the second returned value contains the error code (0 = OK, >1 = index of first non-parseable character + 1, -1 = wrong base, -2 = wrong bit size, -3 = overflow)
@[direct_array_access]
pub fn common_parse_uint2(s string, _base int, _bit_size int) (u64, int) {
if s == '' {
return u64(0), 1
}
mut bit_size := _bit_size
mut base := _base
mut start_index := 0
if base == 0 {
// Look for octal, binary and hex prefix.
base = 10
if s[0] == `0` {
ch := if s.len > 1 { s[1] | 32 } else { `0` }
if s.len >= 3 {
if ch == `b` {
base = 2
start_index += 2
} else if ch == `o` {
base = 8
start_index += 2
} else if ch == `x` {
base = 16
start_index += 2
}
// check for underscore after the base prefix
if s[start_index] == `_` {
start_index++
}
}
// manage leading zeros in decimal base's numbers
// otherwise it is an octal for C compatibility
// TODO: Check if this behaviour is logically right
else if s.len >= 2 && (s[1] >= `0` && s[1] <= `9`) {
base = 10
start_index++
} else {
base = 8
start_index++
}
}
}
if bit_size == 0 {
bit_size = int_size
} else if bit_size < 0 || bit_size > 64 {
return u64(0), -2
}
// Cutoff is the smallest number such that cutoff*base > maxUint64.
// Use compile-time constants for common cases.
cutoff := max_u64 / u64(base) + u64(1)
max_val := if bit_size == 64 { max_u64 } else { (u64(1) << u64(bit_size)) - u64(1) }
basem1 := base - 1
mut n := u64(0)
for i in start_index .. s.len {
mut c := s[i]
// manage underscore inside the number
if c == `_` {
if i == start_index || i >= (s.len - 1) {
// println("_ limit")
return u64(0), 1
}
if s[i - 1] == `_` || s[i + 1] == `_` {
// println("_ *2")
return u64(0), 1
}
continue
}
mut sub_count := 0
// get the 0-9 digit
c -= 48 // subtract the rune `0`
// check if we are in the superior base rune interval [A..Z]
if c >= 17 { // (65 - 48)
sub_count++
c -= 7 // subtract the `A` - `0` rune to obtain the value of the digit
// check if we are in the superior base rune interval [a..z]
if c >= 42 { // (97 - 7 - 48)
sub_count++
c -= 32 // subtract the `a` - `0` rune to obtain the value of the digit
}
}
// check for digit over base
if c > basem1 || (sub_count == 0 && c > 9) {
return n, i + 1
}
// check if we are in the cutoff zone
if n >= cutoff {
// n*base overflows
// return error('parse_uint: range error ${s}')
return max_val, -3
}
n *= u64(base)
n1 := n + u64(c)
if n1 < n || n1 > max_val {
// n+v overflows
// return error('parse_uint: range error ${s}')
return max_val, -3
}
n = n1
}
return n, 0
}
// parse_uint is like parse_int but for unsigned numbers.
pub fn parse_uint(s string, _base int, _bit_size int) !u64 {
return common_parse_uint(s, _base, _bit_size, true, true)
}
// common_parse_int is called by parse int and allows the parsing
// to stop on non or invalid digit characters and return with an error
@[direct_array_access]
pub fn common_parse_int(_s string, base int, _bit_size int, error_on_non_digit bool, error_on_high_digit bool) !i64 {
if _s == '' {
// return error('parse_int: syntax error ${s}')
return i64(0)
}
mut bit_size := _bit_size
if bit_size == 0 {
bit_size = int_size
}
mut s := _s
// Pick off leading sign.
mut neg := false
if s[0] == `+` {
// s = s[1..]
unsafe {
s = tos(s.str + 1, s.len - 1)
}
} else if s[0] == `-` {
neg = true
// s = s[1..]
unsafe {
s = tos(s.str + 1, s.len - 1)
}
}
// Convert unsigned and check range.
// un := parse_uint(s, base, bit_size) or {
// return i64(0)
// }
un := common_parse_uint(s, base, bit_size, error_on_non_digit, error_on_high_digit)!
if un == 0 {
return i64(0)
}
// TODO: check should u64(bit_size-1) be size of int (32)?
cutoff := u64(1) << u64(bit_size - 1)
if !neg && un >= cutoff {
if error_on_high_digit {
return error('common_parse_int: integer overflow ${_s}')
}
return i64(cutoff - u64(1))
}
if neg && un > cutoff {
if error_on_high_digit {
return error('common_parse_int: integer overflow ${_s}')
}
return -i64(cutoff)
}
return if neg { -i64(un) } else { i64(un) }
}
// parse_int interprets a string s in the given base (0, 2 to 36) and
// bit size (0 to 64) and returns the corresponding value i.
//
// If the base argument is 0, the true base is implied by the string's
// prefix: 2 for "0b", 8 for "0" or "0o", 16 for "0x", and 10 otherwise.
// Also, for argument base 0 only, underscore characters are permitted
// as defined by the Go syntax for integer literals.
//
// The bitSize argument specifies the integer type
// that the result must fit into. Bit sizes 0, 8, 16, 32, and 64
// correspond to int, int8, int16, int32, and int64.
// If bitSize is below 0 or above 64, an error is returned.
pub fn parse_int(_s string, base int, _bit_size int) !i64 {
return common_parse_int(_s, base, _bit_size, true, false)
}
// atoi_common_check perform basics check on string to parse:
// Test emptiness, + or - sign presence, presence of digit after signs and no
// underscore as first character.
// returns +1 or -1 depending on sign, and s first digit index or an error.
@[direct_array_access]
fn atoi_common_check(s string) !(i64, int) {
if s == '' {
return error('strconv.atoi: parsing "": empty string')
}
mut start_idx := 0
mut sign := i64(1)
if s[0] == `-` || s[0] == `+` {
start_idx++
if s[0] == `-` {
sign = -1
}
}
if s.len - start_idx < 1 {
return error('strconv.atoi: parsing "${s}": no number after sign')
}
if s[start_idx] == `_` || s[s.len - 1] == `_` {
return error('strconv.atoi: parsing "${s}": values cannot start or end with underscores')
}
return sign, start_idx
}
// atoi_common performs computation for all i8, i16 and i32 type, excluding i64.
// Parse values, and returns consistent error message over differents types.
// s is string to parse, type_min/max are respective types min/max values.
@[direct_array_access]
fn atoi_common(s string, type_min i64, type_max i64) !i64 {
mut sign, mut start_idx := atoi_common_check(s)!
mut x := i64(0)
mut underscored := false
for i in start_idx .. s.len {
c := s[i] - `0`
if c == 47 { // 47 = Ascii(`_`) - ascii(`0`) = 95 - 48.
if underscored == true { // Two consecutives underscore
return error('strconv.atoi: parsing "${s}": consecutives underscores are not allowed')
}
underscored = true
continue // Skip underscore
} else {
if c > 9 {
return error('strconv.atoi: parsing "${s}": invalid radix 10 character')
}
underscored = false
x = (x * 10) + (c * sign)
if sign == 1 && x > type_max {
return error('strconv.atoi: parsing "${s}": integer overflow')
} else {
if x < type_min {
return error('strconv.atoi: parsing "${s}": integer underflow')
}
}
}
}
return x
}
// atoi is equivalent to parse_int(s, 10, 0), converted to type int.
// It follows V scanner as much as observed.
pub fn atoi(s string) !int {
return int(atoi_common(s, i64_min_int32, i64_max_int32)!)
}
// atoi8 is equivalent to atoi(s), converted to type i8.
// returns an i8 [-128 .. 127] or an error.
pub fn atoi8(s string) !i8 {
return i8(atoi_common(s, min_i8, max_i8)!)
}
// atoi16 is equivalent to atoi(s), converted to type i16.
// returns an i16 [-32678 .. 32767] or an error.
pub fn atoi16(s string) !i16 {
return i16(atoi_common(s, min_i16, max_i16)!)
}
// atoi32 is equivalent to atoi(s), converted to type i32.
// returns an i32 [-2147483648 .. 2147483647] or an error.
pub fn atoi32(s string) !i32 {
return i32(atoi_common(s, min_i32, max_i32)!)
}
// atoi64 converts radix 10 string to i64 type.
// returns an i64 [-9223372036854775808 .. 9223372036854775807] or an error.
@[direct_array_access]
pub fn atoi64(s string) !i64 {
mut sign, mut start_idx := atoi_common_check(s)!
mut x := i64(0)
mut underscored := false
for i in start_idx .. s.len {
c := s[i] - `0`
if c == 47 { // 47 = Ascii(`_`) - ascii(`0`) = 95 - 48.
if underscored == true { // Two consecutives underscore
return error('strconv.atoi64: parsing "${s}": consecutives underscores are not allowed')
}
underscored = true
continue // Skip underscore
} else {
if c > 9 {
return error('strconv.atoi64: parsing "${s}": invalid radix 10 character')
}
underscored = false
x = safe_mul10_64bits(x) or { return error('strconv.atoi64: parsing "${s}": ${err}') }
x = safe_add_64bits(x, int(c * sign)) or {
return error('strconv.atoi64: parsing "${s}": ${err}')
}
}
}
return x
}
// safe_add64 performs a signed 64 bits addition and returns an error
// in case of overflow or underflow.
@[inline]
fn safe_add_64bits(a i64, b i64) !i64 {
if a > 0 && b > (max_i64 - a) {
return error('integer overflow')
} else if a < 0 && b < (min_i64 - a) {
return error('integer underflow')
}
return a + b
}
// safe_mul10 performs a * 10 multiplication and returns an error
// in case of overflow or underflow.
@[inline]
fn safe_mul10_64bits(a i64) !i64 {
if a > 0 && a > (max_i64 / 10) {
return error('integer overflow')
}
if a < 0 && a < (min_i64 / 10) {
return error('integer underflow')
}
return a * 10
}
const i64_min_int32 = i64(-2147483647) - 1 // msvc has a bug that treats just i64(min_int) as 2147483648 :-(; this is a workaround for it
const i64_max_int32 = i64(2147483646) + 1
+447
View File
@@ -0,0 +1,447 @@
import strconv
struct StrInt { // test struct
str_value string
int_value int
}
// test what should be caught by atoi_common_check
fn test_common_check() {
// Parsing of these strings should fail on all types.
ko := [
'', // Empty string
'-', // Only sign
'+', // Only sign
'_', // Only Underscore
'_10', // Start with underscore
'+_10', // Start with underscore after sign.
'-_16', // Start with underscore after sign.
'123_', // End with underscore
]
for v in ko {
if r := strconv.atoi(v) {
// These conversions should fail so force assertion !
assert false, 'The string "${v}" should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for : "${v}')
assert true
}
}
}
// Test things accepted, and rejected in atoi_common function.
fn test_atoi_common() {
// Parsing of theses value should succeed on all types.
ok := [
StrInt{'1', 1},
StrInt{'-1', -1},
StrInt{'0', 0},
StrInt{'+0', 0},
StrInt{'-0', 0},
StrInt{'-0_00', 0},
StrInt{'+0_00', 0},
StrInt{'+1', 1},
StrInt{'+123', 123},
StrInt{'-1_2_1', -121},
StrInt{'00000006', 6},
StrInt{'0_0_0_0_0_0_0_6', 6},
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atoi(v.str_value)! == v.int_value
}
ko := [// Parsing of these strings should fail on all types.
'-3__1', // Two consecutives underscore.
'-3_1A', // Non radix 10 char.
'A42', // Non radix 10 char.
]
for v in ko {
if r := strconv.atoi(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} int extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for : "${v}')
assert true
}
}
}
// performs numeric (bounds) tests over int type.
fn test_atoi() {
ok := [
StrInt{'1', 1},
StrInt{'-1', -1},
StrInt{'0', 0},
StrInt{'+3_14159', 314159},
StrInt{'-1_00_1', -1001},
StrInt{'-1_024', -1024},
StrInt{'123_456_789', 123456789},
StrInt{'00000006', 6},
StrInt{'0_0_0_0_0_0_0_6', 6},
StrInt{'2147483647', max_i32},
StrInt{'-2147483648', min_i32},
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atoi(v.str_value)! == v.int_value
}
// Parsing of these values should fail !
ko := [
'-2147483649', // 32bits underflow by 1.
'+2147483648', // 32 bit overflow by 1.
'+3147483648', // 32 bit overflow by a lot.
'-2147244836470', // Large underflow.
'+86842255899621148766244',
]
for v in ko {
if r := strconv.atoi(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} int extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for : "${v}')
assert true
}
}
}
// performs numeric (bounds) tests over i8 type.
fn test_atoi8() {
struct StrI8 { // Inner test struct
str_value string
int_value i8
}
ok := [
StrI8{'0', 0}, // All kind of zeroes
StrI8{'+0', 0},
StrI8{'-0', 0},
StrI8{'-0_00', 0},
StrI8{'+0_00', 0},
StrI8{'1', 1},
StrI8{'+1', 1},
StrI8{'-1', -1},
StrI8{'+123', 123},
StrI8{'-1_2_1', -121},
StrI8{'0_0_0_0_0_0_0_6', 6},
StrI8{'127', max_i8},
StrI8{'-128', min_i8},
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atoi8(v.str_value)! == v.int_value
}
// Parsing of these values should fail !
ko := [
'-129', // i8 bits underflow by 1.
'+128', // i8 bit overflow by 1.
'+256', // i8 overflow with value equal to max u8.
'+3147483648', // i8 bit overflow by a lot.
'-4836470', // Large i8 underflow.
]
for v in ko {
if r := strconv.atoi8(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} int extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for : "${v}')
assert true
}
}
}
// performs numeric (bounds) tests over i16 type.
fn test_atoi16() {
struct StrI16 { // Inner test struct
str_value string
int_value i16
}
ok := [
StrI16{'0', 0}, // All kind of zeroes
StrI16{'+0', 0},
StrI16{'-0', 0},
StrI16{'-0_00', 0},
StrI16{'+0_00', 0},
StrI16{'1', 1},
StrI16{'+1', 1},
StrI16{'-1', -1},
StrI16{'+123', 123},
StrI16{'-1_2_1', -121},
StrI16{'0_0_0_0_0_0_0_6', 6},
StrI16{'32767', max_i16},
StrI16{'-32768', min_i16},
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atoi16(v.str_value)! == v.int_value
}
// Parsing of these values should fail !
ko := [
'-32769', // i16 bits underflow by 1.
'+32768', // i16 bit overflow by 1.
'+45_000', // i16 bit overflow by a lot.
'65536', // i16 overflow with value equal to u16 max.
'-483_647_909', // Large i16 underflow.
]
for v in ko {
if r := strconv.atoi16(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} int extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for : "${v}')
assert true
}
}
}
// performs numeric (bounds) tests over i32 type. This test is redundant with atoi
// which performs same on int (actually 32bits). In the future, int COULD be mapped
// on arch with (e.g 64bits). That's why this test exists.
fn test_atoi32() {
struct StrI32 { // Inner test struct
str_value string
int_value i32
}
ok := [
StrI32{'0', 0}, // All kind of zeroes
StrI32{'+0', 0},
StrI32{'-0', 0},
StrI32{'-0_00', 0},
StrI32{'+0_00', 0},
StrI32{'1', 1},
StrI32{'+1', 1},
StrI32{'-1', -1},
StrI32{'+123', 123},
StrI32{'-1_2_1', -121},
StrI32{'0_0_0_0_0_0_0_6', 6},
StrI32{'2147483647', max_i32},
StrI32{'-2147483648', min_i32},
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atoi32(v.str_value)! == v.int_value
}
// Parsing of these values should fail !
ko := [
'-2147483649', // i32 bits underflow by 1.
'+2147483648', // i32 bit overflow by 1.
'+4294967295', // Large Overflow but equal to u32 max.
'-483_647_909_912_754', // Large i32 underflow.
]
for v in ko {
if r := strconv.atoi32(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} int extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for : "${v}')
assert true
}
}
}
fn test_atoi64() {
struct StrI64 { // Inner test struct
str_value string
int_value i64
}
ok := [
StrI64{'0', 0}, // All kind of zeroes
StrI64{'+0', 0},
StrI64{'-0', 0},
StrI64{'-0_00', 0},
StrI64{'+0_00', 0},
StrI64{'1', 1},
StrI64{'+1', 1},
StrI64{'-1', -1},
StrI64{'+123', 123},
StrI64{'-1_2_1', -121},
StrI64{'0_0_0_0_0_0_0_6', 6},
StrI64{'9223372036854775807', max_i64},
StrI64{'-9223372036854775808', min_i64},
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atoi64(v.str_value)! == v.int_value
}
// Parsing of these values should fail !
ko := [
'-9223372036854775809', // i64 bits underflow by 1.
'+9223372036854775808', // i64 bit overflow by 1.
'+18446744073709551615', // Large Overflow but equal to u64 max.
'-483647909912754123456789', // Large i64 underflow.
]
for v in ko {
if r := strconv.atoi64(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} int extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for : "${v}')
assert true
}
}
}
fn test_parse_int() {
// symbols coverage
assert strconv.parse_int('1234567890', 10, 32)! == 1234567890
assert strconv.parse_int('19aAbBcCdDeEfF', 16, 64)! == 0x19aAbBcCdDeEfF
// Different bases
assert strconv.parse_int('16', 16, 0)! == 0x16
assert strconv.parse_int('16', 8, 0)! == 0o16
assert strconv.parse_int('11', 2, 0)! == 3
// Different bit sizes
assert strconv.parse_int('127', 10, 8)! == 127
assert strconv.parse_int('128', 10, 8)! == 127
assert strconv.parse_int('32767', 10, 16)! == 32767
assert strconv.parse_int('32768', 10, 16)! == 32767
assert strconv.parse_int('2147483647', 10, 32)! == 2147483647
assert strconv.parse_int('2147483648', 10, 32)! == 2147483647
assert strconv.parse_int('9223372036854775807', 10, 64)! == 9223372036854775807
assert strconv.parse_int('9223372036854775808', 10, 64)! == 9223372036854775807
assert strconv.parse_int('baobab', 36, 64)! == 683058467
// Invalid bit sizes
if x := strconv.parse_int('123', 10, -1) {
println(x)
assert false
} else {
assert true
}
if x := strconv.parse_int('123', 10, 65) {
println(x)
assert false
} else {
assert true
}
}
fn test_common_parse_int_error_on_high_digit() {
assert strconv.common_parse_int('9223372036854775808', 0, 64, false, true) or { 1 } == 1
assert strconv.common_parse_int('-9223372036854775809', 0, 64, false, true) or { 1 } == 1
assert strconv.common_parse_int('9223372036854775808', 0, 64, false, false) or { 1 } == max_i64
assert strconv.common_parse_int('-9223372036854775809', 0, 64, false, false) or { 1 } == min_i64
}
fn test_common_parse_uint2() {
mut result, mut error := strconv.common_parse_uint2('1', 10, 8)
assert result == 1
assert error == 0
result, error = strconv.common_parse_uint2('123', 10, 8)
assert result == 123
assert error == 0
result, error = strconv.common_parse_uint2('123', 10, 65)
assert result == 0
assert error == -2
result, error = strconv.common_parse_uint2('123', 10, -1)
assert result == 0
assert error == -2
result, error = strconv.common_parse_uint2('', 10, 8)
assert result == 0
assert error == 1
result, error = strconv.common_parse_uint2('1a', 10, 8)
assert result == 1
assert error == 2
result, error = strconv.common_parse_uint2('12a', 10, 8)
assert result == 12
assert error == 3
result, error = strconv.common_parse_uint2('123a', 10, 8)
assert result == 123
assert error == 4
}
fn test_common_parse_uint2_fail() {
mut ascii_characters := [' ', '!', '"', '#', '\$', '%', '&', "'", '(', ')', '*', '+', ',',
'-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|',
'}', '~']
mut special_characters := [':', ';', '<', '=', '>', '?', '@', 'X', 'Y', 'Z', '[', '\\', ']',
'^', '_', '`']
num0, err0 := strconv.common_parse_uint2('1Ab', 16, 32)
assert num0 == 427
assert err0 == 0
for ch in ascii_characters {
// println("ch: [${ch}]")
txt_str := '${ch[0]:c}12Ab'
num, err := strconv.common_parse_uint2(txt_str, 16, 32)
assert err != 0
}
for ch in special_characters {
// println("ch: [${ch}]")
txt_str := '${ch[0]:c}12Ab'
num, err := strconv.common_parse_uint2(txt_str, 16, 32)
assert err != 0
}
}
fn test_common_parse_uint2_compatibility() {
test_list := [
'1234,1234',
'1_234,1234',
'1_2_34,1234',
'_12__34,1',
'12__34,1',
'_1234,1',
'1234_,1',
'0x1234,4660',
'0x_1234,4660',
'0x1_234,4660',
'0x1_2_3_4,4660',
'0_x1234,1',
'0x1234_,1',
'0o1234,668',
'0o_1234,668',
'0o1_234,668',
'0o1_2_3_4,668',
'0_o1234,1',
'0o1234_,1',
'0b111,7',
'0b_111,7',
'0b1_11,7',
'0b1_1_1,7',
'0_b111,1',
'0b111_,1',
'0xa,10',
'0xA,10',
'0xf,15',
'0xf,15',
'0_xf,1',
'0x_0_0_f_,1',
'0x_0_0__f,1',
'0x_0_0_f,15',
]
for tst in test_list {
query := tst.split(',')
mut a0 := strconv.common_parse_uint(query[0], 0, 32, true, true) or { 1 }
// println("${a0} => ${query[1]}")
assert a0.str() == query[1]
}
}
+98
View File
@@ -0,0 +1,98 @@
// Copyright (c) 2019-2024 V language community. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module strconv
// atou_common_check perform basics check on unsigned string to parse.
// Test emptiness, + sign presence, absence of minus sign, presence of digit after
// signs and no underscore as first character.
// returns s first digit index or an error.
@[direct_array_access]
fn atou_common_check(s string) !int {
if s == '' {
return error('strconv.atou: parsing "": empty string')
}
mut start_idx := 0
if s[0] == `-` {
return error('strconv.atou: parsing "{s}" : negative value')
}
if s[0] == `+` {
start_idx++
}
if s.len - start_idx < 1 {
return error('strconv.atou: parsing "${s}": no number after sign')
}
if s[start_idx] == `_` || s[s.len - 1] == `_` {
return error('strconv.atou: parsing "${s}": values cannot start or end with underscores')
}
return start_idx
}
// atou_common performs computation for all u8, u16 and u32 type, excluding i64.
// Parse values, and returns consistent error message over differents types.
// s is string to parse, max is respective types max value.
@[direct_array_access]
fn atou_common(s string, type_max u64) !u64 {
mut start_idx := int(atou_common_check(s)!)
mut x := u64(0)
mut underscored := false
for i in start_idx .. s.len {
c := s[i] - `0`
if c == 47 { // 47 = Ascii(`_`) - ascii(`0`) = 95 - 48.
if underscored == true { // Two consecutives underscore
return error('strconv.atou: parsing "${s}": consecutives underscores are not allowed')
}
underscored = true
continue // Skip underscore
} else {
if c > 9 {
return error('strconv.atou: parsing "${s}": invalid radix 10 character')
}
underscored = false
if x > type_max / 10 {
return error('strconv.atou: parsing "${s}": integer overflow')
}
x *= 10
if x > type_max - u64(c) {
return error('strconv.atou: parsing "${s}": integer overflow')
}
x += u64(c)
}
}
return x
}
// atou8 is equivalent to parse_uint(s, 10, 0), converted to type u8.
// It returns u8 in range [0..255] or an Error.
pub fn atou8(s string) !u8 {
return u8(atou_common(s, max_u8)!)
}
// atou16 is equivalent to parse_uint(s, 10, 0), converted to type u16.
// It returns u16 in range [0..65535] or an Error.
pub fn atou16(s string) !u16 {
return u16(atou_common(s, max_u16)!)
}
// atou is equivalent to parse_uint(s, 10, 0), converted to type u32.
pub fn atou(s string) !u32 {
return u32(atou_common(s, max_u32)!)
}
// atou32 is identical to atou. Here to provide a symmetrical API with atoi/atoi32
// It returns u32 in range [0..4294967295] or an Error.
pub fn atou32(s string) !u32 {
return u32(atou_common(s, max_u32)!)
}
// atou64 is equivalent to parse_uint(s, 10, 0), converted to type u64.
// It returns u64 in range [0..18446744073709551615] or an Error.
pub fn atou64(s string) !u64 {
return u64(atou_common(s, max_u64)!)
}
+296
View File
@@ -0,0 +1,296 @@
// Copyright (c) 2019-2024 V language community. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
import strconv
// Perform tests against basic check done on fn test_atou_common_check()
// used from atou_common.
fn test_atou_common_check() {
// Parsing of these strings should fail on all types.
ko := [
'', // Empty string
'+', // Only sign
'-10', // - sign
'_', // Only Underscore
'_10', // Start with underscore
'+_10', // Start with underscore after sign.
'-_16', // Start with underscore after sign.
'123_', // End with underscore
'+12_3_', // Sign with trailing underscore
]
for v in ko {
if r := strconv.atou(v) {
// These conversions should fail so force assertion !
assert false, 'The string "${v}" should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for : "${v}')
assert true
}
}
}
// Performs tests on possible errors from atou_common function.
// Function called from atou_common are tested above.
fn test_atou_common() {
struct StrUint {
str_value string
uint_value u64
}
ok := [
StrUint{'0', 0},
StrUint{'+0', 0},
StrUint{'1', 1},
StrUint{'+3_14159', 314159},
StrUint{'1_00_1', 1001},
StrUint{'+1_024', 1024},
StrUint{'123_456_789', 123456789},
StrUint{'00000006', 6},
StrUint{'+0_0_0_0_0_0_0_6', 6},
StrUint{'2147483647', 2147483647},
StrUint{'+4294967295', 4294967295}, // max u32 bits
StrUint{'+18446744073709551615', 18446744073709551615}, // max u64 bits
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atou_common(v.str_value, max_u64)! == v.uint_value
}
// Parsing of these values should fail !
ko := [
'+1_2A', // Non radix 10 character.
'++A', // double sign.
'1__0', // 2 consecutive underscore
'+18446744073709551616', // u64 overflow by 1.
]
for v in ko {
if r := strconv.atou_common(v, max_u64) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for: "${v} -> ${err}')
assert true
}
}
}
// performs numeric (bounds) tests over u8 type.
fn test_atou8() {
struct StrU8 {
str_value string
uint_value u8
}
ok := [
StrU8{'0', 0},
StrU8{'+0', 0},
StrU8{'1', 1},
StrU8{'+39', 39},
StrU8{'1_23', 123},
StrU8{'00000006', 6},
StrU8{'+0_0_0_0_0_0_0_6', 6},
StrU8{'255', 255}, // max u8
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atou8(v.str_value)! == v.uint_value
}
// Parsing of these values should fail !
ko := [
'256', // Overflow by one
'+65535', // overflow of superior type.
]
for v in ko {
if r := strconv.atou8(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for: "${v} -> ${err}')
assert true
}
}
}
// performs numeric (bounds) tests over u16 type.
fn test_atou16() {
struct StrU16 {
str_value string
uint_value u16
}
ok := [
StrU16{'0', 0},
StrU16{'+0', 0},
StrU16{'1', 1},
StrU16{'+16384', 16384},
StrU16{'1_23', 123},
StrU16{'00000006', 6},
StrU16{'+0_0_0_0_0_0_0_6', 6},
StrU16{'+3_2_7_6_8', 32768},
StrU16{'65535', 65535}, // max u16
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atou16(v.str_value)! == v.uint_value
}
// Parsing of these values should fail !
ko := [
'65536', // Overflow by one
'+4294967295', // overflow of superior type.
]
for v in ko {
if r := strconv.atou16(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for: "${v} -> ${err}')
assert true
}
}
}
// atou method acts actually with u32 boundary. In the future int may be mapped on 32/64bits
// depending on machine architecture. That's why we provide atou/atou32 code and tests.
fn test_atou() {
struct StrU32 {
str_value string
uint_value u32
}
ok := [
StrU32{'0', 0},
StrU32{'+0', 0},
StrU32{'1', 1},
StrU32{'+3_14159', 314159},
StrU32{'1_00_1', 1001},
StrU32{'+1_024', 1024},
StrU32{'123_456_789', 123456789},
StrU32{'00000006', 6},
StrU32{'+0_0_0_0_0_0_0_6', 6},
StrU32{'2147483647', 2147483647},
StrU32{'+4294967295', 4294967295}, // max u32 bits
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atou(v.str_value)! == v.uint_value
}
// Parsing of these values should fail !
ko := [
'4294967296', // Overflow by one
'+18446744073709551615', // overflow of superior type.
]
for v in ko {
if r := strconv.atou(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for: "${v} -> ${err}')
assert true
}
}
}
// performs numeric (bounds) tests over u64 type.
fn test_atou32() {
struct StrU32 {
str_value string
uint_value u32
}
ok := [
StrU32{'0', 0},
StrU32{'+0', 0},
StrU32{'1', 1},
StrU32{'+3_14159', 314159},
StrU32{'1_00_1', 1001},
StrU32{'+1_024', 1024},
StrU32{'123_456_789', 123456789},
StrU32{'00000006', 6},
StrU32{'+0_0_0_0_0_0_0_6', 6},
StrU32{'2147483647', 2147483647},
StrU32{'+4294967295', 4294967295}, // max u32 bits
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atou32(v.str_value)! == v.uint_value
}
// Parsing of these values should fail !
ko := [
'4294967296', // Overflow by one
'+18446744073709551615', // overflow of superior type.
]
for v in ko {
if r := strconv.atou32(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for: "${v} -> ${err}')
assert true
}
}
}
// performs numeric (bounds) tests over u64 type.
fn test_atou64() {
struct StrU64 {
str_value string
uint_value u64
}
ok := [
StrU64{'0', 0},
StrU64{'+0', 0},
StrU64{'1', 1},
StrU64{'+3_14159', 314159},
StrU64{'1_00_1', 1001},
StrU64{'+1_024', 1024},
StrU64{'123_456_789', 123456789},
StrU64{'00000006', 6},
StrU64{'+0_0_0_0_0_0_0_6', 6},
StrU64{'2147483647', 2147483647},
StrU64{'+18446744073709551615', 18446744073709551615}, // max u64 bits
]
// Check that extracted int value matches its string.
for v in ok {
// println('Parsing ${v.str_value} should equals ${v.int_value}')
assert strconv.atou64(v.str_value)! == v.uint_value
}
// Parsing of these values should fail !
ko := [
'18446744073709551616', // Overflow by one
'+184467440214748364773709551615', // Large overflow .
'430943843908439083411', // Overflow that wraps without falling under oldx (issue #27102).
]
for v in ko {
if r := strconv.atou64(v) {
// These conversions should fail so force assertion !
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
} else {
// println('Parsing fails as it should for: "${v} -> ${err}')
assert true
}
}
}
@@ -0,0 +1,7 @@
fn main() {
mut x := []int{cap: 100}
x << 42
x << 41
x << 40
println(x)
}
+53
View File
@@ -0,0 +1,53 @@
module main
fn test_issue_16448() {
float_prec := 1.9999999999999999 + 0.0000000000000000005
assert float_prec > 1.99
assert float_prec < 2.01
}
fn test_atof_converter() {
mut float_prec := 1.9999999999999999 + 0.0000000000000000005
assert float_prec == 2.0
float_prec = 3.9999999999999999 + 0.0000000000000000005
assert float_prec == 4.0
float_prec = -3.9999999999999999 - 0.0000000000000000005
assert float_prec == -4.0
float_prec = -1.9999999999999999 - 0.0000000000000000005
assert float_prec == -2.0
float_prec = 0.025 + 0.0000000000000000005
assert float_prec == 0.025
float_prec = 0.0625 + 0.0000000000000000005
assert float_prec == 0.0625
float_prec = 0.0009765625 + 0.0000000000000000005
assert float_prec > 0.00097656250 && float_prec < 0.00097656251
float_prec = -0.025 - 0.0000000000000000005
assert float_prec == -0.025
float_prec = -0.0625 - 0.0000000000000000005
assert float_prec == -0.0625
float_prec = -0.0009765625 - 0.0000000000000000005
assert float_prec < -0.00097656250 && float_prec > -0.00097656251
}
// Testing all the numbers between -100E6..100E6 takes over a minute with clang on i3
// while for just -1E5..1E5, it takes less than a second.
// const r = i64(100_000_001)
// const r = i64(17_000_001)
const r = i64(100_001)
fn test_renormalisation_inside_converter() {
for i in -r .. r {
s := '${i}.9999999999999999999'
fs := s.f64()
ifs := i64(fs)
if i % 10000 == 0 {
eprintln('>>> s: ${s:30s} | fs: ${fs:20.6f} | ifs: ${ifs:20}')
}
if i < 0 && ifs != i - 1 {
panic('invalid negative f64 value, i: ${i} | s: ${s} | fs: ${fs} | ifs: ${ifs}')
}
if i > 0 && ifs != i + 1 {
panic('invalid positive f64 value, i: ${i} | s: ${s} | fs: ${fs} | ifs: ${ifs}')
}
}
}
@@ -0,0 +1,206 @@
/**********************************************************************
*
* Float to string Test
*
**********************************************************************/
import strconv
import math
union Ufloat32 {
mut:
f f32 = f32(0)
b u32
}
union Ufloat64 {
mut:
f f64 = f64(0)
b u64
}
fn f64_from_bits1(b u64) f64 {
mut x := Ufloat64{}
x.b = b
// C.printf("bin: %016llx\n",x.f)
return unsafe { x.f }
}
fn f32_from_bits1(b u32) f32 {
mut x := Ufloat32{}
x.b = b
// C.printf("bin: %08x\n",x.f)
return unsafe { x.f }
}
fn test_float_to_str() {
test_cases_f32 := [
f32_from_bits1(0x0000_0000), // +0
f32_from_bits1(0x8000_0000), // -0
f32_from_bits1(0xFFC0_0001), // sNan
f32_from_bits1(0xFF80_0001), // qNan
f32_from_bits1(0x7F80_0000), // +inf
f32_from_bits1(0xFF80_0000), // -inf
1,
-1,
10,
-10,
0.3,
-0.3,
1000000,
123456.7,
123e35,
-123.45,
1e23,
f32_from_bits1(0x0080_0000), // smallest float32
math.max_f32,
383260575764816448.0,
]
exp_result_f32 := [
'0e+00',
'-0e+00',
'nan',
'nan',
'+inf',
'-inf',
'1e+00',
'-1e+00',
'1e+01',
'-1e+01',
'3e-01',
'-3e-01',
'1e+06',
'1.234567e+05',
'1.23e+37',
'-1.2345e+02',
'1e+23',
'1.1754944e-38', // aprox from 1.1754943508 × 10−38,
'3.4028235e+38',
'3.8326058e+17',
]
test_cases_f64 := [
f64_from_bits1(0x0000_0000_0000_0000), // +0
f64_from_bits1(0x8000_0000_0000_0000), // -0
f64_from_bits1(0x7FF0_0000_0000_0001), // sNan
f64_from_bits1(0x7FF8_0000_0000_0001), // qNan
f64_from_bits1(0x7FF0_0000_0000_0000), // +inf
f64_from_bits1(0xFFF0_0000_0000_0000), // -inf
1,
-1,
10,
-10,
0.3,
-0.3,
1000000,
123456.7,
123e45,
-123.45,
1e23,
f64_from_bits1(0x0010_0000_0000_0000), // smallest float64
math.max_f32,
383260575764816448,
383260575764816448,
// C failing cases
123e300,
123e-300,
5.e-324,
-5.e-324,
]
exp_result_f64 := [
'0e+00',
'-0e+00',
'nan',
'nan',
'+inf',
'-inf',
'1e+00',
'-1e+00',
'1e+01',
'-1e+01',
'3e-01',
'-3e-01',
'1e+06',
'1.234567e+05',
'1.23e+47',
'-1.2345e+02',
'1e+23',
'2.2250738585072014e-308',
'3.4028234663852886e+38',
'3.8326057576481645e+17',
'3.8326057576481645e+17',
'1.23e+302', // this test is failed from C sprintf!!
'1.23e-298',
'5e-324',
'-5e-324',
]
// test f32
for c, x in test_cases_f32 {
// println(x)
s := strconv.f32_to_str(x, 8)
s1 := exp_result_f32[c]
// println("${s1} ${s}")
assert s == s1
}
// test f64
for c, x in test_cases_f64 {
s := strconv.f64_to_str(x, 17)
s1 := exp_result_f64[c]
// println("${s1} ${s}")
assert s == s1
}
// test long format
assert strconv.f64_to_str_l('1e1'.f64()).len == 4 // '10.0'
assert strconv.f64_to_str_l('1e-1'.f64()).len == 3 // '0.1'
for exp := 2; exp < 120; exp++ {
a := strconv.f64_to_str_l(('1e' + exp.str()).f64())
// println(a)
assert a.len == exp + 3
b := strconv.f64_to_str_l(('1e-' + exp.str()).f64())
// println(b)
assert b.len == exp + 2
}
// test rounding str conversion
// println( ftoa.f64_to_str(0.3456789123456, 4) )
// assert ftoa.f64_to_str(0.3456789123456, 4)=="3.4568e-01"
// assert ftoa.f32_to_str(0.345678, 3)=="3.457e-01"
}
fn test_issue_25141_1() {
assert strconv.ftoa_long_32(0.1234567901) == '0.12345679'
}
fn test_issue_25141_2() {
assert strconv.f32_to_str_l(math.f32_from_bits(1055100472)) == '0.44444442'
}
fn test_issue_25141_3() {
assert strconv.f32_to_str_l(math.f32_from_bits(1055100473)) == '0.44444445'
}
fn test_issue_25141_4() {
assert strconv.f32_to_str_l(math.f32_from_bits(1055100474)) == '0.44444448'
}
fn test_issue_25141_5() {
assert strconv.f32_to_str_l(math.f32_from_bits(1055100475)) == '0.4444445'
}
fn test_issue_25141_0001() {
assert strconv.f32_to_str_l(math.f32_from_bits(953267991)) == '0.0001'
assert strconv.f32_to_str_l(math.f32_from_bits(953267992)) == '0.000100000005'
assert strconv.f32_to_str_l(math.f32_from_bits(953267993)) == '0.00010000001'
assert strconv.f32_to_str_l(math.f32_from_bits(953267994)) == '0.00010000002'
assert strconv.f32_to_str_l(math.f32_from_bits(953267995)) == '0.00010000003'
assert strconv.f32_to_str_l(math.f32_from_bits(953267996)) == '0.000100000034'
assert strconv.f32_to_str_l(math.f32_from_bits(953267997)) == '0.00010000004'
assert strconv.f32_to_str_l(math.f32_from_bits(953267998)) == '0.00010000005'
assert strconv.f32_to_str_l(math.f32_from_bits(953267999)) == '0.000100000056'
}
+372
View File
@@ -0,0 +1,372 @@
module strconv
/*=============================================================================
f32 to string
Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
Use of this source code is governed by an MIT license
that can be found in the LICENSE file.
This file contains the f32 to string functions
These functions are based on the work of:
Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN
Conference on Programming Language Design and ImplementationJune 2018
Pages 270–282 https://doi.org/10.1145/3192366.3192369
inspired by the Go version here:
https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea
=============================================================================*/
// pow of ten table used by n_digit reduction
const ten_pow_table_32 = [
u32(1),
u32(10),
u32(100),
u32(1000),
u32(10000),
u32(100000),
u32(1000000),
u32(10000000),
u32(100000000),
u32(1000000000),
]!
//=============================================================================
// Conversion Functions
//=============================================================================
const mantbits32 = u32(23)
const expbits32 = u32(8)
const bias32 = 127 // f32 exponent bias
const maxexp32 = 255
// max 46 char
// -3.40282346638528859811704183484516925440e+38
@[direct_array_access]
pub fn (d Dec32) get_string_32(neg bool, i_n_digit int, i_pad_digit int) string {
n_digit := i_n_digit + 1
pad_digit := i_pad_digit + 1
mut out := d.m
// mut out_len := decimal_len_32(out)
mut out_len := dec_digits(out)
out_len_original := out_len
mut fw_zeros := 0
if pad_digit > out_len {
fw_zeros = pad_digit - out_len
}
mut buf := []u8{len: int(out_len + 5 + 1 + 1)} // sign + mant_len + . + e + e_sign + exp_len(2) + \0}
mut i := 0
if neg {
if buf.data != 0 {
// The buf.data != 0 check here, is needed for clean compilation
// with `-cc gcc -cstrict -prod`. Without it, gcc produces:
// error: potential null pointer dereference
buf[i] = `-`
}
i++
}
mut disp := 0
if out_len <= 1 {
disp = 1
}
if n_digit < out_len {
// println("orig: ${out_len_original}")
out += ten_pow_table_32[out_len - n_digit - 1] * 5 // round to up
out /= ten_pow_table_32[out_len - n_digit]
out_len = n_digit
}
y := i + out_len
mut x := 0
for x < (out_len - disp - 1) {
buf[y - x] = `0` + u8(out % 10)
out /= 10
i++
x++
}
// no decimal digits needed, end here
if i_n_digit == 0 {
unsafe {
buf[i] = 0
return tos(memdup(&buf[0], i + 1), i)
}
}
if out_len > 1 || fw_zeros > 0 {
buf[y - x] = `.`
i++
}
x++
if y - x >= 0 {
buf[y - x] = `0` + u8(out % 10)
i++
}
for fw_zeros > 0 {
buf[i] = `0`
i++
fw_zeros--
}
buf[i] = `e`
i++
mut exp := d.e + out_len_original - 1
if exp < 0 {
buf[i] = `-`
i++
exp = -exp
} else {
buf[i] = `+`
i++
}
// Always print two digits to match strconv's formatting.
d1 := exp % 10
d0 := exp / 10
buf[i] = `0` + u8(d0)
i++
buf[i] = `0` + u8(d1)
i++
buf[i] = 0
return unsafe {
tos(memdup(&buf[0], i + 1), i)
}
}
fn f32_to_decimal_exact_int(i_mant u32, exp u32) (Dec32, bool) {
mut d := Dec32{}
e := exp - bias32
if e > mantbits32 {
return d, false
}
shift := mantbits32 - e
mant := i_mant | 0x0080_0000 // implicit 1
// mant := i_mant | (1 << mantbits32) // implicit 1
d.m = mant >> shift
if (d.m << shift) != mant {
return d, false
}
for (d.m % 10) == 0 {
d.m /= 10
d.e++
}
return d, true
}
fn f32_to_decimal(mant u32, exp u32) Dec32 {
mut e2 := 0
mut m2 := u32(0)
if exp == 0 {
// We subtract 2 so that the bounds computation has
// 2 additional bits.
e2 = 1 - bias32 - int(mantbits32) - 2
m2 = mant
} else {
e2 = int(exp) - bias32 - int(mantbits32) - 2
m2 = (u32(1) << mantbits32) | mant
}
even := (m2 & 1) == 0
accept_bounds := even
// Step 2: Determine the interval of valid decimal representations.
mv := u32(4 * m2)
mp := u32(4 * m2 + 2)
mm_shift := bool_to_u32(mant != 0 || exp <= 1)
mm := u32(4 * m2 - 1 - mm_shift)
mut vr := u32(0)
mut vp := u32(0)
mut vm := u32(0)
mut e10 := 0
mut vm_is_trailing_zeros := false
mut vr_is_trailing_zeros := false
mut last_removed_digit := u8(0)
if e2 >= 0 {
q := log10_pow2(e2)
e10 = int(q)
k := pow5_inv_num_bits_32 + pow5_bits(int(q)) - 1
i := -e2 + int(q) + k
vr = mul_pow5_invdiv_pow2(mv, q, i)
vp = mul_pow5_invdiv_pow2(mp, q, i)
vm = mul_pow5_invdiv_pow2(mm, q, i)
if q != 0 && (vp - 1) / 10 <= vm / 10 {
// We need to know one removed digit even if we are not
// going to loop below. We could use q = X - 1 above,
// except that would require 33 bits for the result, and
// we've found that 32-bit arithmetic is faster even on
// 64-bit machines.
l := pow5_inv_num_bits_32 + pow5_bits(int(q - 1)) - 1
last_removed_digit = u8(mul_pow5_invdiv_pow2(mv, q - 1, -e2 + int(q - 1) + l) % 10)
}
if q <= 9 {
// The largest power of 5 that fits in 24 bits is 5^10,
// but q <= 9 seems to be safe as well. Only one of mp,
// mv, and mm can be a multiple of 5, if any.
if mv % 5 == 0 {
vr_is_trailing_zeros = multiple_of_power_of_five_32(mv, q)
} else if accept_bounds {
vm_is_trailing_zeros = multiple_of_power_of_five_32(mm, q)
} else if multiple_of_power_of_five_32(mp, q) {
vp--
}
}
} else {
q := log10_pow5(-e2)
e10 = int(q) + e2
i := -e2 - int(q)
k := pow5_bits(i) - pow5_num_bits_32
mut j := int(q) - k
vr = mul_pow5_div_pow2(mv, u32(i), j)
vp = mul_pow5_div_pow2(mp, u32(i), j)
vm = mul_pow5_div_pow2(mm, u32(i), j)
if q != 0 && ((vp - 1) / 10) <= vm / 10 {
j = int(q) - 1 - (pow5_bits(i + 1) - pow5_num_bits_32)
last_removed_digit = u8(mul_pow5_div_pow2(mv, u32(i + 1), j) % 10)
}
if q <= 1 {
// {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at
// least q trailing 0 bits. mv = 4 * m2, so it always
// has at least two trailing 0 bits.
vr_is_trailing_zeros = true
if accept_bounds {
// mm = mv - 1 - mm_shift, so it has 1 trailing 0 bit
// if mm_shift == 1.
vm_is_trailing_zeros = mm_shift == 1
} else {
// mp = mv + 2, so it always has at least one
// trailing 0 bit.
vp--
}
} else if q < 31 {
vr_is_trailing_zeros = multiple_of_power_of_two_32(mv, q - 1)
}
}
// Step 4: Find the shortest decimal representation
// in the interval of valid representations.
mut removed := 0
mut out := u32(0)
if vm_is_trailing_zeros || vr_is_trailing_zeros {
// General case, which happens rarely (~4.0%).
for vp / 10 > vm / 10 {
vm_is_trailing_zeros = vm_is_trailing_zeros && (vm % 10) == 0
vr_is_trailing_zeros = vr_is_trailing_zeros && last_removed_digit == 0
last_removed_digit = u8(vr % 10)
vr /= 10
vp /= 10
vm /= 10
removed++
}
if vm_is_trailing_zeros {
for vm % 10 == 0 {
vr_is_trailing_zeros = vr_is_trailing_zeros && last_removed_digit == 0
last_removed_digit = u8(vr % 10)
vr /= 10
vp /= 10
vm /= 10
removed++
}
}
if vr_is_trailing_zeros && last_removed_digit == 5 && (vr % 2) == 0 {
// Round even if the exact number is .....50..0.
last_removed_digit = 4
}
out = vr
// We need to take vr + 1 if vr is outside bounds
// or we need to round up.
if (vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5 {
out++
}
} else {
// Specialized for the common case (~96.0%). Percentages below
// are relative to this. Loop iterations below (approximately):
// 0: 13.6%, 1: 70.7%, 2: 14.1%, 3: 1.39%, 4: 0.14%, 5+: 0.01%
for vp / 10 > vm / 10 {
last_removed_digit = u8(vr % 10)
vr /= 10
vp /= 10
vm /= 10
removed++
}
// We need to take vr + 1 if vr is outside bounds
// or we need to round up.
out = vr + bool_to_u32(vr == vm || last_removed_digit >= 5)
}
return Dec32{
m: out
e: e10 + removed
}
}
//=============================================================================
// String Functions
//=============================================================================
// f32_to_str returns a `string` in scientific notation with max `n_digit` after the dot.
pub fn f32_to_str(f f32, n_digit int) string {
mut u1 := Uf32{}
u1.f = f
u := unsafe { u1.u }
neg := (u >> (mantbits32 + expbits32)) != 0
mant := u & ((u32(1) << mantbits32) - u32(1))
exp := (u >> mantbits32) & ((u32(1) << expbits32) - u32(1))
// println("${neg} ${mant} e ${exp-bias32}")
// Exit early for easy cases.
if exp == maxexp32 || (exp == 0 && mant == 0) {
return get_string_special(neg, exp == 0, mant == 0)
}
mut d, ok := f32_to_decimal_exact_int(mant, exp)
if !ok {
// println("with exp form")
d = f32_to_decimal(mant, exp)
}
// println("${d.m} ${d.e}")
return d.get_string_32(neg, n_digit, 0)
}
// f32_to_str_pad returns a `string` in scientific notation with max `n_digit` after the dot.
pub fn f32_to_str_pad(f f32, n_digit int) string {
mut u1 := Uf32{}
u1.f = f
u := unsafe { u1.u }
neg := (u >> (mantbits32 + expbits32)) != 0
mant := u & ((u32(1) << mantbits32) - u32(1))
exp := (u >> mantbits32) & ((u32(1) << expbits32) - u32(1))
// println("${neg} ${mant} e ${exp-bias32}")
// Exit early for easy cases.
if exp == maxexp32 || (exp == 0 && mant == 0) {
return get_string_special(neg, exp == 0, mant == 0)
}
mut d, ok := f32_to_decimal_exact_int(mant, exp)
if !ok {
// println("with exp form")
d = f32_to_decimal(mant, exp)
}
// println("${d.m} ${d.e}")
return d.get_string_32(neg, n_digit, n_digit)
}
+1
View File
@@ -0,0 +1 @@
module strconv
@@ -0,0 +1,35 @@
import math
fn test_around() {
// do not pass 0
for f in [f32(1e-12), 1e-9, 1e-6, 1e-4, 0.1, 0.2, 0.5, 1, 2, 5, 10, 1e6, 1e7, 1e9, 1e12] {
check_around(f, 1000)
}
}
fn check_around(fval f32, total int) {
check(fval, total)
check(-fval, total)
}
fn check(fval f32, total int) {
middle_bits := math.f32_bits(fval)
start_bits := middle_bits - u32(total / 2)
end_bits := middle_bits + u32(total / 2)
min, max := if start_bits < end_bits { start_bits, end_bits } else { end_bits, start_bits }
println('> check_around ${total} f32 values around fval: ${fval:26.12f}, middle_bits for fval: ${middle_bits:12} | min: ${min:12} | max: ${max:12}')
for ux in min .. max {
x := math.f32_from_bits(ux)
if math.is_nan(x) {
continue
}
uy := ux - 1
y := math.f32_from_bits(uy)
if x == y {
continue
}
sx := x.str()
sy := y.str()
assert sx != sy, 'math.f32_from_bits(${ux})'
}
}
@@ -0,0 +1,6 @@
module strconv
fn test_issue_20877_ten_pow_table_32_stays_in_u32_range() {
assert ten_pow_table_32.len == 10
assert ten_pow_table_32[ten_pow_table_32.len - 1] == u32(1_000_000_000)
}
+386
View File
@@ -0,0 +1,386 @@
module strconv
/*=============================================================================
f64 to string
Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
Use of this source code is governed by an MIT license
that can be found in the LICENSE file.
This file contains the f64 to string functions
These functions are based on the work of:
Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN
Conference on Programming Language Design and ImplementationJune 2018
Pages 270–282 https://doi.org/10.1145/3192366.3192369
inspired by the Go version here:
https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea
=============================================================================*/
@[direct_array_access]
fn (d Dec64) get_string_64(neg bool, i_n_digit int, i_pad_digit int) string {
mut n_digit := if i_n_digit < 1 { 1 } else { i_n_digit + 1 }
pad_digit := i_pad_digit + 1
mut out := d.m
mut d_exp := d.e
// mut out_len := decimal_len_64(out)
mut out_len := dec_digits(out)
out_len_original := out_len
mut fw_zeros := 0
if pad_digit > out_len {
fw_zeros = pad_digit - out_len
}
mut buf := []u8{len: (out_len + 6 + 1 + 1 + fw_zeros)} // sign + mant_len + . + e + e_sign + exp_len(2) + \0}
mut i := 0
if neg {
buf[i] = `-`
i++
}
mut disp := 0
if out_len <= 1 {
disp = 1
}
// rounding last used digit
if n_digit < out_len {
// println("out:[${out}]")
out += ten_pow_table_64[out_len - n_digit - 1] * 5 // round to up
out /= ten_pow_table_64[out_len - n_digit]
// println("out1:[${out}] ${d.m / ten_pow_table_64[out_len - n_digit ]}")
// fix issue #22424
out_div := d.m / ten_pow_table_64[out_len - n_digit]
if out_div < out && dec_digits(out_div) < dec_digits(out) {
// from `99` to `100`, will need d_exp+1
d_exp++
n_digit++
}
// println("cmp: ${d.m/ten_pow_table_64[out_len - n_digit ]} ${out/ten_pow_table_64[out_len - n_digit ]}")
out_len = n_digit
// println("orig: ${out_len_original} new len: ${out_len} out:[${out}]")
}
y := i + out_len
mut x := 0
for x < (out_len - disp - 1) {
buf[y - x] = `0` + u8(out % 10)
out /= 10
i++
x++
}
// fix issue #22424
// no decimal digits needed, end here
// if i_n_digit == 0 {
// unsafe {
// buf[i] = 0
// return tos(&u8(&buf[0]), i)
// }
//}
if out_len > 1 || fw_zeros > 0 {
buf[y - x] = `.`
i++
}
x++
if y - x >= 0 {
buf[y - x] = `0` + u8(out % 10)
i++
}
for fw_zeros > 0 {
buf[i] = `0`
i++
fw_zeros--
}
buf[i] = `e`
i++
mut exp := d_exp + out_len_original - 1
if exp < 0 {
buf[i] = `-`
i++
exp = -exp
} else {
buf[i] = `+`
i++
}
// Always print at least two digits to match strconv's formatting.
d2 := exp % 10
exp /= 10
d1 := exp % 10
d0 := exp / 10
if d0 > 0 {
buf[i] = `0` + u8(d0)
i++
}
buf[i] = `0` + u8(d1)
i++
buf[i] = `0` + u8(d2)
i++
buf[i] = 0
return unsafe {
tos(memdup(&buf[0], i + 1), i)
}
}
@[ignore_overflow]
fn f64_to_decimal_exact_int(i_mant u64, exp u64) (Dec64, bool) {
mut d := Dec64{}
e := exp - bias64
if e > mantbits64 {
return d, false
}
shift := mantbits64 - e
mant := i_mant | u64(0x0010_0000_0000_0000) // implicit 1
// mant := i_mant | (1 << mantbits64) // implicit 1
d.m = mant >> shift
if (d.m << shift) != mant {
return d, false
}
for (d.m % 10) == 0 {
d.m /= 10
d.e++
}
return d, true
}
fn f64_to_decimal(mant u64, exp u64) Dec64 {
mut e2 := 0
mut m2 := u64(0)
if exp == 0 {
// We subtract 2 so that the bounds computation has
// 2 additional bits.
e2 = 1 - bias64 - int(mantbits64) - 2
m2 = mant
} else {
e2 = int(exp) - bias64 - int(mantbits64) - 2
m2 = (u64(1) << mantbits64) | mant
}
even := (m2 & 1) == 0
accept_bounds := even
// Step 2: Determine the interval of valid decimal representations.
mv := u64(4 * m2)
mm_shift := bool_to_u64(mant != 0 || exp <= 1)
// Step 3: Convert to a decimal power base uing 128-bit arithmetic.
mut vr := u64(0)
mut vp := u64(0)
mut vm := u64(0)
mut e10 := 0
mut vm_is_trailing_zeros := false
mut vr_is_trailing_zeros := false
if e2 >= 0 {
// This expression is slightly faster than max(0, log10Pow2(e2) - 1).
q := log10_pow2(e2) - bool_to_u32(e2 > 3)
e10 = int(q)
k := pow5_inv_num_bits_64 + pow5_bits(int(q)) - 1
i := -e2 + int(q) + k
mul := *(&Uint128(&pow5_inv_split_64_x[q * 2]))
vr = mul_shift_64(u64(4) * m2, mul, i)
vp = mul_shift_64(u64(4) * m2 + u64(2), mul, i)
vm = mul_shift_64(u64(4) * m2 - u64(1) - mm_shift, mul, i)
if q <= 21 {
// This should use q <= 22, but I think 21 is also safe.
// Smaller values may still be safe, but it's more
// difficult to reason about them. Only one of mp, mv,
// and mm can be a multiple of 5, if any.
if mv % 5 == 0 {
vr_is_trailing_zeros = multiple_of_power_of_five_64(mv, q)
} else if accept_bounds {
// Same as min(e2 + (^mm & 1), pow5Factor64(mm)) >= q
// <=> e2 + (^mm & 1) >= q && pow5Factor64(mm) >= q
// <=> true && pow5Factor64(mm) >= q, since e2 >= q.
vm_is_trailing_zeros = multiple_of_power_of_five_64(mv - 1 - mm_shift, q)
} else if multiple_of_power_of_five_64(mv + 2, q) {
vp--
}
}
} else {
// This expression is slightly faster than max(0, log10Pow5(-e2) - 1).
q := log10_pow5(-e2) - bool_to_u32(-e2 > 1)
e10 = int(q) + e2
i := -e2 - int(q)
k := pow5_bits(i) - pow5_num_bits_64
j := int(q) - k
mul := *(&Uint128(&pow5_split_64_x[i * 2]))
vr = mul_shift_64(u64(4) * m2, mul, j)
vp = mul_shift_64(u64(4) * m2 + u64(2), mul, j)
vm = mul_shift_64(u64(4) * m2 - u64(1) - mm_shift, mul, j)
if q <= 1 {
// {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits.
// mv = 4 * m2, so it always has at least two trailing 0 bits.
vr_is_trailing_zeros = true
if accept_bounds {
// mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1.
vm_is_trailing_zeros = (mm_shift == 1)
} else {
// mp = mv + 2, so it always has at least one trailing 0 bit.
vp--
}
} else if q < 63 { // TODO(ulfjack/cespare): Use a tighter bound here.
// We need to compute min(ntz(mv), pow5Factor64(mv) - e2) >= q - 1
// <=> ntz(mv) >= q - 1 && pow5Factor64(mv) - e2 >= q - 1
// <=> ntz(mv) >= q - 1 (e2 is negative and -e2 >= q)
// <=> (mv & ((1 << (q - 1)) - 1)) == 0
// We also need to make sure that the left shift does not overflow.
vr_is_trailing_zeros = multiple_of_power_of_two_64(mv, q - 1)
}
}
// Step 4: Find the shortest decimal representation
// in the interval of valid representations.
mut removed := 0
mut last_removed_digit := u8(0)
mut out := u64(0)
// On average, we remove ~2 digits.
if vm_is_trailing_zeros || vr_is_trailing_zeros {
// General case, which happens rarely (~0.7%).
for {
vp_div_10 := vp / 10
vm_div_10 := vm / 10
if vp_div_10 <= vm_div_10 {
break
}
vm_mod_10 := vm % 10
vr_div_10 := vr / 10
vr_mod_10 := vr % 10
vm_is_trailing_zeros = vm_is_trailing_zeros && vm_mod_10 == 0
vr_is_trailing_zeros = vr_is_trailing_zeros && last_removed_digit == 0
last_removed_digit = u8(vr_mod_10)
vr = vr_div_10
vp = vp_div_10
vm = vm_div_10
removed++
}
if vm_is_trailing_zeros {
for {
vm_div_10 := vm / 10
vm_mod_10 := vm % 10
if vm_mod_10 != 0 {
break
}
vp_div_10 := vp / 10
vr_div_10 := vr / 10
vr_mod_10 := vr % 10
vr_is_trailing_zeros = vr_is_trailing_zeros && last_removed_digit == 0
last_removed_digit = u8(vr_mod_10)
vr = vr_div_10
vp = vp_div_10
vm = vm_div_10
removed++
}
}
if vr_is_trailing_zeros && last_removed_digit == 5 && (vr % 2) == 0 {
// Round even if the exact number is .....50..0.
last_removed_digit = 4
}
out = vr
// We need to take vr + 1 if vr is outside bounds
// or we need to round up.
if (vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5 {
out++
}
} else {
// Specialized for the common case (~99.3%).
// Percentages below are relative to this.
mut round_up := false
for vp / 100 > vm / 100 {
// Optimization: remove two digits at a time (~86.2%).
round_up = (vr % 100) >= 50
vr /= 100
vp /= 100
vm /= 100
removed += 2
}
// Loop iterations below (approximately), without optimization above:
// 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02%
// Loop iterations below (approximately), with optimization above:
// 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02%
for vp / 10 > vm / 10 {
round_up = (vr % 10) >= 5
vr /= 10
vp /= 10
vm /= 10
removed++
}
// We need to take vr + 1 if vr is outside bounds
// or we need to round up.
out = vr + bool_to_u64(vr == vm || round_up)
}
return Dec64{
m: out
e: e10 + removed
}
}
//=============================================================================
// String Functions
//=============================================================================
// f64_to_str returns `f` as a `string` in scientific notation with max `n_digit` digits after the dot.
pub fn f64_to_str(f f64, n_digit int) string {
mut u1 := Uf64{}
u1.f = f
u := unsafe { u1.u }
neg := (u >> (mantbits64 + expbits64)) != 0
mant := u & ((u64(1) << mantbits64) - u64(1))
exp := (u >> mantbits64) & ((u64(1) << expbits64) - u64(1))
// println("s:${neg} mant:${mant} exp:${exp} float:${f} byte:${u1.u:016lx}")
// Exit early for easy cases.
if exp == maxexp64 || (exp == 0 && mant == 0) {
return get_string_special(neg, exp == 0, mant == 0)
}
mut d, ok := f64_to_decimal_exact_int(mant, exp)
if !ok {
// println("to_decimal")
d = f64_to_decimal(mant, exp)
}
// println("${d.m} ${d.e}")
return d.get_string_64(neg, n_digit, 0)
}
// f64_to_str returns `f` as a `string` in scientific notation with max `n_digit` digits after the dot.
pub fn f64_to_str_pad(f f64, n_digit int) string {
mut u1 := Uf64{}
u1.f = f
u := unsafe { u1.u }
neg := (u >> (mantbits64 + expbits64)) != 0
mant := u & ((u64(1) << mantbits64) - u64(1))
exp := (u >> mantbits64) & ((u64(1) << expbits64) - u64(1))
// unsafe { println("s:${neg} mant:${mant} exp:${exp} float:${f} byte:${u1.u:016x}") }
// Exit early for easy cases.
if exp == maxexp64 || (exp == 0 && mant == 0) {
return get_string_special(neg, exp == 0, mant == 0)
}
mut d, ok := f64_to_decimal_exact_int(mant, exp)
if !ok {
// println("to_decimal")
d = f64_to_decimal(mant, exp)
}
// println("DEBUG: ${d.m} ${d.e}")
return d.get_string_64(neg, n_digit, n_digit)
}
+338
View File
@@ -0,0 +1,338 @@
module strconv
import math
fn (d Dec64) get_string_64(neg bool, i_n_digit int, i_pad_digit int) string {
mut n_digit := i_n_digit + 1
pad_digit := i_pad_digit + 1
mut out := d.m
mut d_exp := d.e
// mut out_len := decimal_len_64(out)
mut out_len := dec_digits(out)
out_len_original := out_len
mut fw_zeros := 0
if pad_digit > out_len {
fw_zeros = pad_digit - out_len
}
mut buf := []u8{len: (out_len + 6 + 1 + 1 + fw_zeros)} // sign + mant_len + . + e + e_sign + exp_len(2) + \0}
mut i := 0
if neg {
#buf.arr.arr[i.val] = '-'.charCodeAt()
i++
}
mut disp := 0
if out_len <= 1 {
disp = 1
}
// rounding last used digit
if n_digit < out_len {
// println("out:[${out}]")
out += ten_pow_table_64[out_len - n_digit - 1] * 5 // round to up
out /= ten_pow_table_64[out_len - n_digit]
// println("out1:[${out}] ${d.m / ten_pow_table_64[out_len - n_digit ]}")
if d.m / ten_pow_table_64[out_len - n_digit] < out {
d_exp++
n_digit++
}
// println("cmp: ${d.m/ten_pow_table_64[out_len - n_digit ]} ${out/ten_pow_table_64[out_len - n_digit ]}")
out_len = n_digit
// println("orig: ${out_len_original} new len: ${out_len} out:[${out}]")
}
y := i + out_len
mut x := 0
for x < (out_len - disp - 1) {
#buf.arr.arr[y.val - x.val].val = '0'.charCodeAt() + Number(out.valueOf() % 10n)
out /= 10
i++
x++
}
// no decimal digits needed, end here
if i_n_digit == 0 {
res := ''
#buf.arr.arr.forEach((it) => it.val == 0 ? res.str : res.str += String.fromCharCode(it.val))
return res
}
if out_len >= 1 {
buf[y - x] = `.`
x++
i++
}
if y - x >= 0 {
#buf.arr.arr[y.val - x.val].val = '0'.charCodeAt() + Number(out.valueOf() % 10n)
i++
}
for fw_zeros > 0 {
#buf.arr.arr[i.val].val = '0'.charCodeAt()
i++
fw_zeros--
}
#buf.arr.arr[i.val].val = 'e'.charCodeAt()
i++
mut exp := d_exp + out_len_original - 1
if exp < 0 {
#buf.arr.arr[i.val].val = '-'.charCodeAt()
i++
exp = -exp
} else {
#buf.arr.arr[i.val].val = '+'.charCodeAt()
i++
}
// Always print at least two digits to match strconv's formatting.
d2 := exp % 10
exp /= 10
d1 := exp % 10
_ := d1
_ := d2
d0 := exp / 10
if d0 > 0 {
#buf.arr.arr[i].val = '0'.charCodeAt() + d0.val
i++
}
#buf.arr.arr[i].val = '0'.charCodeAt() + d1.val
i++
#buf.arr.arr[i].val = '0' + d2.val
i++
#buf.arr.arr[i].val = 0
res := ''
#buf.arr.arr.forEach((it) => it.val == 0 ? res.str : res.str += String.fromCharCode(it.val))
return res
}
fn f64_to_decimal_exact_int(i_mant u64, exp u64) (Dec64, bool) {
mut d := Dec64{}
e := exp - bias64
if e > mantbits64 {
return d, false
}
shift := mantbits64 - e
mant := i_mant | u64(0x0010_0000_0000_0000) // implicit 1
// mant := i_mant | (1 << mantbits64) // implicit 1
d.m = mant >> shift
if (d.m << shift) != mant {
return d, false
}
for (d.m % 10) == 0 {
d.m /= 10
d.e++
}
return d, true
}
fn f64_to_decimal(mant u64, exp u64) Dec64 {
mut e2 := 0
mut m2 := u64(0)
if exp == 0 {
// We subtract 2 so that the bounds computation has
// 2 additional bits.
e2 = 1 - bias64 - int(mantbits64) - 2
m2 = mant
} else {
e2 = int(exp) - bias64 - int(mantbits64) - 2
m2 = (u64(1) << mantbits64) | mant
}
even := (m2 & 1) == 0
accept_bounds := even
// Step 2: Determine the interval of valid decimal representations.
mv := u64(4 * m2)
mm_shift := bool_to_u64(mant != 0 || exp <= 1)
// Step 3: Convert to a decimal power base uing 128-bit arithmetic.
mut vr := u64(0)
mut vp := u64(0)
mut vm := u64(0)
mut e10 := 0
mut vm_is_trailing_zeros := false
mut vr_is_trailing_zeros := false
if e2 >= 0 {
// This expression is slightly faster than max(0, log10Pow2(e2) - 1).
q := log10_pow2(e2) - bool_to_u32(e2 > 3)
e10 = int(q)
k := pow5_inv_num_bits_64 + pow5_bits(int(q)) - 1
i := -e2 + int(q) + k
mul := pow5_inv_split_64[q]
vr = mul_shift_64(u64(4) * m2, mul, i)
vp = mul_shift_64(u64(4) * m2 + u64(2), mul, i)
vm = mul_shift_64(u64(4) * m2 - u64(1) - mm_shift, mul, i)
if q <= 21 {
// This should use q <= 22, but I think 21 is also safe.
// Smaller values may still be safe, but it's more
// difficult to reason about them. Only one of mp, mv,
// and mm can be a multiple of 5, if any.
if mv % 5 == 0 {
vr_is_trailing_zeros = multiple_of_power_of_five_64(mv, q)
} else if accept_bounds {
// Same as min(e2 + (^mm & 1), pow5Factor64(mm)) >= q
// <=> e2 + (^mm & 1) >= q && pow5Factor64(mm) >= q
// <=> true && pow5Factor64(mm) >= q, since e2 >= q.
vm_is_trailing_zeros = multiple_of_power_of_five_64(mv - 1 - mm_shift, q)
} else if multiple_of_power_of_five_64(mv + 2, q) {
vp--
}
}
} else {
// This expression is slightly faster than max(0, log10Pow5(-e2) - 1).
q := log10_pow5(-e2) - bool_to_u32(-e2 > 1)
e10 = int(q) + e2
i := -e2 - int(q)
k := pow5_bits(i) - pow5_num_bits_64
j := int(q) - k
mul := pow5_split_64[i]
vr = mul_shift_64(u64(4) * m2, mul, j)
vp = mul_shift_64(u64(4) * m2 + u64(2), mul, j)
vm = mul_shift_64(u64(4) * m2 - u64(1) - mm_shift, mul, j)
if q <= 1 {
// {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits.
// mv = 4 * m2, so it always has at least two trailing 0 bits.
vr_is_trailing_zeros = true
if accept_bounds {
// mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1.
vm_is_trailing_zeros = (mm_shift == 1)
} else {
// mp = mv + 2, so it always has at least one trailing 0 bit.
vp--
}
} else if q < 63 { // TODO(ulfjack/cespare): Use a tighter bound here.
// We need to compute min(ntz(mv), pow5Factor64(mv) - e2) >= q - 1
// <=> ntz(mv) >= q - 1 && pow5Factor64(mv) - e2 >= q - 1
// <=> ntz(mv) >= q - 1 (e2 is negative and -e2 >= q)
// <=> (mv & ((1 << (q - 1)) - 1)) == 0
// We also need to make sure that the left shift does not overflow.
vr_is_trailing_zeros = multiple_of_power_of_two_64(mv, q - 1)
}
}
// Step 4: Find the shortest decimal representation
// in the interval of valid representations.
mut removed := 0
mut last_removed_digit := u8(0)
mut out := u64(0)
// On average, we remove ~2 digits.
if vm_is_trailing_zeros || vr_is_trailing_zeros {
// General case, which happens rarely (~0.7%).
for {
vp_div_10 := vp / 10
vm_div_10 := vm / 10
if vp_div_10 <= vm_div_10 {
break
}
vm_mod_10 := vm % 10
vr_div_10 := vr / 10
vr_mod_10 := vr % 10
vm_is_trailing_zeros = vm_is_trailing_zeros && vm_mod_10 == 0
vr_is_trailing_zeros = vr_is_trailing_zeros && last_removed_digit == 0
last_removed_digit = u8(vr_mod_10)
vr = vr_div_10
vp = vp_div_10
vm = vm_div_10
removed++
}
if vm_is_trailing_zeros {
for {
vm_div_10 := vm / 10
vm_mod_10 := vm % 10
if vm_mod_10 != 0 {
break
}
vp_div_10 := vp / 10
vr_div_10 := vr / 10
vr_mod_10 := vr % 10
vr_is_trailing_zeros = vr_is_trailing_zeros && last_removed_digit == 0
last_removed_digit = u8(vr_mod_10)
vr = vr_div_10
vp = vp_div_10
vm = vm_div_10
removed++
}
}
if vr_is_trailing_zeros && last_removed_digit == 5 && (vr % 2) == 0 {
// Round even if the exact number is .....50..0.
last_removed_digit = 4
}
out = vr
// We need to take vr + 1 if vr is outside bounds
// or we need to round up.
if (vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5 {
out++
}
} else {
// Specialized for the common case (~99.3%).
// Percentages below are relative to this.
mut round_up := false
for vp / 100 > vm / 100 {
// Optimization: remove two digits at a time (~86.2%).
round_up = (vr % 100) >= 50
vr /= 100
vp /= 100
vm /= 100
removed += 2
}
// Loop iterations below (approximately), without optimization above:
// 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02%
// Loop iterations below (approximately), with optimization above:
// 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02%
for vp / 10 > vm / 10 {
round_up = (vr % 10) >= 5
vr /= 10
vp /= 10
vm /= 10
removed++
}
// We need to take vr + 1 if vr is outside bounds
// or we need to round up.
out = vr + bool_to_u64(vr == vm || round_up)
}
return Dec64{
m: out
e: e10 + removed
}
}
//=============================================================================
// String Functions
//=============================================================================
// f64_to_str return a string in scientific notation with max n_digit after the dot
pub fn f64_to_str(f f64, n_digit int) string {
u := math.f64_bits(f)
neg := (u >> (mantbits64 + expbits64)) != 0
mant := u & ((u64(1) << mantbits64) - u64(1))
exp := (u >> mantbits64) & ((u64(1) << expbits64) - u64(1))
// println("s:${neg} mant:${mant} exp:${exp} float:${f} byte:${u1.u:016lx}")
// Exit early for easy cases.
if exp == maxexp64 || (exp == 0 && mant == 0) {
return get_string_special(neg, exp == 0, mant == 0)
}
mut d, ok := f64_to_decimal_exact_int(mant, exp)
if !ok {
// println("to_decimal")
d = f64_to_decimal(mant, exp)
}
// println("${d.m} ${d.e}")
return d.get_string_64(neg, n_digit, 0)
}
+34
View File
@@ -0,0 +1,34 @@
module strconv
// pow of ten table used by n_digit reduction
const ten_pow_table_64 = [
u64(1),
u64(10),
u64(100),
u64(1000),
u64(10000),
u64(100000),
u64(1000000),
u64(10000000),
u64(100000000),
u64(1000000000),
u64(10000000000),
u64(100000000000),
u64(1000000000000),
u64(10000000000000),
u64(100000000000000),
u64(1000000000000000),
u64(10000000000000000),
u64(100000000000000000),
u64(1000000000000000000),
u64(10000000000000000000),
]!
//=============================================================================
// Conversion Functions
//=============================================================================
const mantbits64 = u32(52)
const expbits64 = u32(11)
const bias64 = 1023 // f64 exponent bias
const maxexp64 = 2047
+265
View File
@@ -0,0 +1,265 @@
# v_printf/v_sprintf
These are v implementations of the C language `printf` and `sprintf` functions.
> **Note**
> These functions are platform dependent in C, but in V they are platform independent.
### v_sprintf
`v_sprintf` has a variable number of parameters.
The first is a format string to control the appearance of the final string.
Each format specifier (%s, %d, etc.) in the format string
is replaced by the textual version of the following parameters.
```v
import strconv
fn main() {
a := 'World'
s := strconv.v_sprintf('Hello %s!', a)
println(s)
}
```
```
Hello World!
```
### v_printf
`v_printf` creates the same modified string as `v_sprintf`, using the same format specifiers,
but it will immediately print the modified string to stdout instead of returning a string.
### Syntax
The syntax for a format specifier is:
```
%[parameter][flags][width][.precision][length]type
```
#### Flags field
The Flags field may be zero or more (in any order) of:
| Character | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `-` (minus) | Left-align the output of this specifier. (The default is to right-align the output.) |
| `+` (plus) | Prepends a plus for positive signed-numeric types. positive = `+`, negative = `-`. (The default doesn't prepend anything to positive numbers.) |
| `0` (zero) | When the 'width' option is specified, prepends zeros for numeric types. (The default prepends spaces.) For example, `printf("%4X",3)` produces ` 3`, while `printf("%04X",3)` produces `0003`. |
#### Width field
The Width field specifies a _maximum_ number of characters to output,
and is typically used to pad fixed-width fields in tabulated output,
it causes truncation of oversized fields.
The width field may be omitted, or it may be a numeric integer value,
or may also be specified by a parameter when indicated by an asterisk `*`.
For example, `v_printf("%*.s", 5, my_string)` will result in ` mystring` being printed,
with a total width of 5 characters.
#### Length field
The Length field can be omitted or be any of:
| Character | Description |
| --------- | -------------------------------------------------------------------------- |
| `hh` | For integer types, causes `printf` to expect an `i8` or `u8` argument. |
| `h` | For integer types, causes `printf` to expect an `int16` or `u16` argument. |
| `l` | For integer types, causes `printf` to expect an `i64` or `u64` argument. |
| `ll` | For integer types, causes `printf` to expect an `i64` or `u64` argument. |
| | |
| | |
#### Type field
The Type field can be any of:
| Character | Description |
| --------- | --------------------------------------------------------------------------------------------------------------------------- |
| `%` | Prints a literal `%` character (this type doesn't accept any flags, width, precision, length fields). |
| `d`, `i` | `int` as a signed `int` `%d` and `%i` are synonymous for output. The size of the argument is specified by the length field. |
| `u` | `unsigned int`. The size of the argument is specified by the length field. |
| `f`, `F` | `double` in normal notation. `f` and `F` only differs in how the strings are printed: lowercase or uppercase. |
| `e`, `E` | `double` in scientific notation.`e` and `E` only differs in how the strings are printed: lowercase or uppercase. |
| `g`, `G` | `double` in automatic notation.`g` and `G` only differs in how the strings are printed: lowercase or uppercase. |
| `x`, `X` | `unsigned int` as a hexadecimal number. `x` uses lower-case letters and `X` uses upper-case. |
| `s` | string |
| `p` | `void *` (pointer to void) in an implementation-defined format. |
| `c` | `char` (character). |
## Examples
various types
```v
import strconv
a0 := u32(10)
b0 := 200
c0 := u8(12)
s0 := 'ciAo'
ch0 := `B`
f0 := 0.312345
f1 := 200000.0
sc0 := 'ciao: [%-08u] %d %hhd [%8s] [%08X] [%-20.4f] [%-20.4f] [%c]'
temp_s := strconv.v_sprintf(sc0, a0, b0, c0, s0, b0, f0, f1, ch0)
println(temp_s)
```
```
ciao: [10 ] 200 12 [ ciAo] [000000C8] [0.3123 ] [200000.0000 ] [B]
```
integer
```v
import strconv
a := u8(12)
b := i16(13)
c := 14
d := i64(15)
sc1 := '==>%hhd %hd %d %ld'
temp_s := strconv.v_sprintf(sc1, a, b, c, d)
println(temp_s)
```
```
==>12 13 14 15
```
unsigned integer
```v
import strconv
a1 := u8(0xff)
b1 := u16(0xffff)
c1 := u32(0xffffffff)
d1 := u64(-1)
sc2 := '%hhu %hu %u %lu'
temp_s := strconv.v_sprintf(sc2, a1, b1, c1, d1)
println(temp_s)
```
```
255 65535 4294967295 18446744073709551615
```
hexadecimal
```v
import strconv
a1 := u8(0xff)
b1 := i16(u16(0xffff))
c1 := u32(0xffffffff)
d1 := u64(-1)
sc3 := '%hhx %hx %x %lx'
temp_s := strconv.v_sprintf(sc3, a1, b1, c1, d1)
println(temp_s)
```
```
ff ffff ffffffff ffffffffffffffff
```
hexadecimal
```v
import strconv
a2 := 125
sc7 := '[%9x] [%9X] [%-9x] [%-9X] [%09x] [%09X]'
temp_s := strconv.v_sprintf(sc7, a2, a2, a2, a2, a2, a2)
println(temp_s)
```
```
[ 7d] [ 7D] [7d ] [7D ] [00000007d] [00000007D]
```
floating points
```v
import strconv
f0 := 0.312345
f1 := 200000.0
f2 := -1234.300e6
f3 := 1234.300e-6
sc4 := '[%-20.3e] [%20.3e] [%-020.3e] [%-020.3E] [%-020.3e] [%-020.3e]'
temp_s := strconv.v_sprintf(sc4, f0, f1, f1, f1, f2, f3)
println(temp_s)
```
```
[3.123e-01 ] [ 2.000e+05] [2.000e+05 ] [2.000E+05 ] [-1.234e+09 ] [1.234e-03 ]
```
float automatic notations
```v
import strconv
mut ft := -1e-7
mut x := 0
sc8 := '[%20g][%20G]|'
for x < 12 {
temp_s := strconv.v_sprintf(sc8, ft, ft)
println('${temp_s}\n')
ft = ft * 10.0
x++
}
```
```
[ -1e-07][ -1E-07]|
[ -1e-06][ -1E-06]|
[ -1e-05][ -1E-05]|
[ -0.0001][ -0.0001]|
[ -0.001][ -0.001]|
[ -0.01][ -0.01]|
[ -0.1][ -0.1]|
[ -1][ -1]|
[ -10][ -10]|
[ -100][ -100]|
[ -1000][ -1000]|
[ -10000][ -10000]|
```
## Utility functions
The format module also has some utility functions:
```v oksyntax nofmt
// calling struct
struct BF_param {
pad_ch u8 = ` ` // padding char
len0 int = -1 // default len for whole the number or string
len1 int = 6 // number of decimal digits, if needed
positive bool = true // mandatory: the sign of the number passed
sign_flag bool = false // flag for print sign as prefix in padding
align Align_text = .right // alignment of the string
rm_tail_zero bool = false // remove the tail zeros from floats
}
// utilities
fn format_dec(d u64, p BF_param) string
fn format_fl(f f64, p BF_param) string
fn format_es(f f64, p BF_param) string
fn remove_tail_zeros(s string) string
```
`format_dec` format the integer number using the parameters in the `BF_param` struct.
`format_fl` format a float number in normal notation using the parameters in the `BF_param` struct.
`format_es format a float number in scientific notation using the parameters in the BF_param`
struct.
`remove_tail_zeros` removes the tailing zeros from a floating point number as string.
+104
View File
@@ -0,0 +1,104 @@
module strconv
import strings
/*
printf/sprintf V implementation
Copyright (c) 2020 Dario Deledda. All rights reserved.
Use of this source code is governed by an MIT license
that can be found in the LICENSE file.
This file contains the printf/sprintf functions
*/
// Align_text is used to describe the different ways to align a text - left, right and center
pub enum Align_text {
right = 0
left
center
}
// Float conversion utility
// rounding value
const dec_round = [
f64(0.5),
0.05,
0.005,
0.0005,
0.00005,
0.000005,
0.0000005,
0.00000005,
0.000000005,
0.0000000005,
0.00000000005,
0.000000000005,
0.0000000000005,
0.00000000000005,
0.000000000000005,
0.0000000000000005,
0.00000000000000005,
0.000000000000000005,
0.0000000000000000005,
0.00000000000000000005,
0.000000000000000000005,
0.0000000000000000000005,
0.00000000000000000000005,
0.000000000000000000000005,
0.0000000000000000000000005,
0.00000000000000000000000005,
0.000000000000000000000000005,
0.0000000000000000000000000005,
0.00000000000000000000000000005,
0.000000000000000000000000000005,
0.0000000000000000000000000000005,
0.00000000000000000000000000000005,
0.000000000000000000000000000000005,
0.0000000000000000000000000000000005,
0.00000000000000000000000000000000005,
0.000000000000000000000000000000000005,
]!
// Single format functions
// BF_param is used for describing the formatting options for a single interpolated value
pub struct BF_param {
pub mut:
pad_ch u8 = u8(` `) // padding char
len0 int = -1 // default len for whole the number or string
len1 int = 6 // number of decimal digits, if needed
positive bool = true // mandatory: the sign of the number passed
sign_flag bool // flag for print sign as prefix in padding
align Align_text = .right // alignment of the string
rm_tail_zero bool // remove the tail zeros from floats
}
// format_str returns the `s` formatted, according to the options set in `p`.
@[manualfree]
pub fn format_str(s string, p BF_param) string {
if p.len0 <= 0 {
return s.clone()
}
dif := p.len0 - utf8_str_visible_length(s)
if dif <= 0 {
return s.clone()
}
mut res := strings.new_builder(s.len + dif)
defer {
unsafe { res.free() }
}
if p.align == .right {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
res.write_string(s)
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
return res.str()
}
+530
View File
@@ -0,0 +1,530 @@
/*=============================================================================
Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
Use of this source code is governed by an MIT license
that can be found in the LICENSE file.
This file contains string interpolation V functions
=============================================================================*/
module strconv
import strings
// format_str_sb is a `strings.Builder` version of `format_str`.
pub fn format_str_sb(s string, p BF_param, mut sb strings.Builder) {
if p.len0 <= 0 {
sb.write_string(s)
return
}
dif := p.len0 - utf8_str_visible_length(s)
if dif <= 0 {
sb.write_string(s)
return
}
if p.align == .right {
for i1 := 0; i1 < dif; i1++ {
sb.write_u8(p.pad_ch)
}
}
sb.write_string(s)
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
sb.write_u8(p.pad_ch)
}
}
}
const max_size_f64_char = 512 // the f64 max representation is -36,028,797,018,963,968e1023, 21 chars, but alignment padding requires more
// digit pairs in reverse order
const digit_pairs = '00102030405060708090011121314151617181910212223242526272829203132333435363738393041424344454647484940515253545556575859506162636465666768696071727374757677787970818283848586878889809192939495969798999'
// format_dec_sb formats an u64 using a `strings.Builder`.
@[direct_array_access]
pub fn format_dec_sb(d u64, p BF_param, mut res strings.Builder) {
mut n_char := dec_digits(d)
sign_len := if !p.positive || p.sign_flag { 1 } else { 0 }
number_len := sign_len + n_char
dif := p.len0 - number_len
mut sign_written := false
if p.align == .right {
if p.pad_ch == `0` {
if p.positive {
if p.sign_flag {
res.write_u8(`+`)
sign_written = true
}
} else {
res.write_u8(`-`)
sign_written = true
}
}
// write the pad chars
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
if !sign_written {
// no pad char, write the sign before the number
if p.positive {
if p.sign_flag {
res.write_u8(`+`)
}
} else {
res.write_u8(`-`)
}
}
/*
// Legacy version
// max u64 18446744073709551615 => 20 byte
mut buf := [32]u8{}
mut i := 20
mut d1 := d
for i >= (21 - n_char) {
buf[i] = u8(d1 % 10) + `0`
d1 = d1 / 10
i--
}
i++
*/
//===========================================
// Speed version
// max u64 18446744073709551615 => 20 byte
mut buf := [32]u8{}
mut i := 20
mut n := d
mut d_i := u64(0)
if n > 0 {
for n > 0 {
n1 := n / 100
// calculate the digit_pairs start index
d_i = (n - (n1 * 100)) << 1
n = n1
unsafe {
buf[i] = digit_pairs.str[d_i]
}
i--
d_i++
unsafe {
buf[i] = digit_pairs.str[d_i]
}
i--
}
i++
// remove head zero
if d_i < 20 {
i++
}
unsafe { res.write_ptr(&buf[i], n_char) }
} else {
// we have a zero no need of more code!
res.write_u8(`0`)
}
//===========================================
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
return
}
// f64_to_str_lnd1 formats a f64 to a `string` with `dec_digit` digits after the dot.
@[direct_array_access; manualfree]
pub fn f64_to_str_lnd1(f f64, dec_digit int) string {
unsafe {
// we add the rounding value
clamped_dec := if dec_digit >= dec_round.len { dec_round.len - 1 } else { dec_digit }
s := f64_to_str(f + dec_round[clamped_dec], 18)
// check for +inf -inf Nan
if s.len > 2 && (s[0] == `n` || s[1] == `i`) {
return s
}
m_sgn_flag := false
mut sgn := 1
mut b := [26]u8{}
mut d_pos := 1
mut i := 0
mut i1 := 0
mut exp := 0
mut exp_sgn := 1
mut dot_res_sp := -1
// get sign and decimal parts
for c in s {
match c {
`-` {
sgn = -1
i++
}
`+` {
sgn = 1
i++
}
`0`...`9` {
b[i1] = c
i1++
i++
}
`.` {
if sgn > 0 {
d_pos = i
} else {
d_pos = i - 1
}
i++
}
`e` {
i++
break
}
else {
s.free()
return '[Float conversion error!!]'
}
}
}
b[i1] = 0
// get exponent
if s[i] == `-` {
exp_sgn = -1
i++
} else if s[i] == `+` {
exp_sgn = 1
i++
}
mut c := i
for c < s.len {
exp = exp * 10 + int(s[c] - `0`)
c++
}
// Reserve enough space for the current digits, exponent-driven zeros,
// requested fractional padding, and the trailing NUL.
extra_frac_digits := if dec_digit > 0 { dec_digit } else { 0 }
sign_len := if sgn < 0 { 1 } else { 0 }
mut res := []u8{len: sign_len + i1 + exp + extra_frac_digits + 4, init: 0}
mut r_i := 0 // result string buffer index
// println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}")
// s no more needed
s.free()
if sgn == 1 {
if m_sgn_flag {
res[r_i] = `+`
r_i++
}
} else {
res[r_i] = `-`
r_i++
}
i = 0
if exp_sgn >= 0 {
for b[i] != 0 {
res[r_i] = b[i]
r_i++
i++
if i >= d_pos && exp >= 0 {
if exp == 0 {
dot_res_sp = r_i
res[r_i] = `.`
r_i++
}
exp--
}
}
for exp >= 0 {
res[r_i] = `0`
r_i++
exp--
}
// println("exp: ${exp} ${r_i} ${dot_res_sp}")
} else {
mut dot_p := true
for exp > 0 {
res[r_i] = `0`
r_i++
exp--
if dot_p {
dot_res_sp = r_i
res[r_i] = `.`
r_i++
dot_p = false
}
}
for b[i] != 0 {
res[r_i] = b[i]
r_i++
i++
}
}
// no more digits needed, stop here
if dec_digit <= 0 {
// C.printf(c'f: %f, i: %d, res.data: %p | dot_res_sp: %d | *(res.data): %s \n', f, i, res.data, dot_res_sp, res.data)
if dot_res_sp < 0 {
dot_res_sp = i + 1
}
tmp_res := tos(res.data, dot_res_sp).clone()
res.free()
return tmp_res
}
// println("r_i-d_pos: ${r_i - d_pos}")
if dot_res_sp >= 0 {
r_i = dot_res_sp + dec_digit + 1
res[r_i] = 0
for c1 in 1 .. dec_digit + 1 {
if res[r_i - c1] == 0 {
res[r_i - c1] = `0`
}
}
// println("result: [${tos(&res[0],r_i)}]")
tmp_res := tos(res.data, r_i).clone()
res.free()
return tmp_res
} else {
if dec_digit > 0 {
mut c1 := 0
res[r_i] = `.`
r_i++
for c1 < dec_digit {
res[r_i] = `0`
r_i++
c1++
}
res[r_i] = 0
}
tmp_res := tos(res.data, r_i).clone()
res.free()
return tmp_res
}
}
}
// format_fl is a `strings.Builder` version of format_fl.
@[direct_array_access; manualfree]
pub fn format_fl(f f64, p BF_param) string {
unsafe {
mut fs := f64_to_str_lnd1(if f >= 0.0 { f } else { -f }, p.len1)
// error!!
if fs[0] == `[` {
return fs
}
if p.rm_tail_zero {
tmp := fs
fs = remove_tail_zeros(fs)
tmp.free()
}
mut buf := [max_size_f64_char]u8{} // write temp float buffer in stack
mut out := [max_size_f64_char]u8{} // out buffer
mut buf_i := 0 // index temporary string
mut out_i := 0 // index output string
mut sign_len_diff := 0
if p.pad_ch == `0` {
if p.positive {
if p.sign_flag {
out[out_i] = `+`
out_i++
sign_len_diff = -1
}
} else {
out[out_i] = `-`
out_i++
sign_len_diff = -1
}
} else {
if p.positive {
if p.sign_flag {
buf[buf_i] = `+`
buf_i++
}
} else {
buf[buf_i] = `-`
buf_i++
}
}
// copy the float
vmemcpy(&buf[buf_i], fs.str, fs.len)
buf_i += fs.len
// make the padding if needed
dif := p.len0 - buf_i + sign_len_diff
if p.align == .right {
for i1 := 0; i1 < dif; i1++ {
out[out_i] = p.pad_ch
out_i++
}
}
vmemcpy(&out[out_i], &buf[0], buf_i)
out_i += buf_i
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
out[out_i] = p.pad_ch
out_i++
}
}
out[out_i] = 0
// return and free
tmp := fs
fs = tos_clone(&out[0])
tmp.free()
return fs
}
}
// format_es returns a f64 as a `string` formatted according to the options set in `p`.
@[direct_array_access; manualfree]
pub fn format_es(f f64, p BF_param) string {
unsafe {
mut fs := f64_to_str_pad(if f > 0 { f } else { -f }, p.len1)
if p.rm_tail_zero {
tmp := fs
fs = remove_tail_zeros(fs)
tmp.free()
}
mut buf := [max_size_f64_char]u8{} // write temp float buffer in stack
mut out := [max_size_f64_char]u8{} // out buffer
mut buf_i := 0 // index temporary string
mut out_i := 0 // index output string
mut sign_len_diff := 0
if p.pad_ch == `0` {
if p.positive {
if p.sign_flag {
out[out_i] = `+`
out_i++
sign_len_diff = -1
}
} else {
out[out_i] = `-`
out_i++
sign_len_diff = -1
}
} else {
if p.positive {
if p.sign_flag {
buf[buf_i] = `+`
buf_i++
}
} else {
buf[buf_i] = `-`
buf_i++
}
}
// copy the float
vmemcpy(&buf[buf_i], fs.str, fs.len)
buf_i += fs.len
// make the padding if needed
dif := p.len0 - buf_i + sign_len_diff
if p.align == .right {
for i1 := 0; i1 < dif; i1++ {
out[out_i] = p.pad_ch
out_i++
}
}
vmemcpy(&out[out_i], &buf[0], buf_i)
out_i += buf_i
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
out[out_i] = p.pad_ch
out_i++
}
}
out[out_i] = 0
// return and free
tmp := fs
fs = tos_clone(&out[0])
tmp.free()
return fs
}
}
// remove_tail_zeros strips trailing zeros from `s` and return the resulting `string`.
@[direct_array_access]
pub fn remove_tail_zeros(s string) string {
unsafe {
mut buf := malloc_noscan(s.len + 1)
mut i_d := 0
mut i_s := 0
// skip spaces
for i_s < s.len && s[i_s] !in [`-`, `+`] && (s[i_s] > `9` || s[i_s] < `0`) {
buf[i_d] = s[i_s]
i_s++
i_d++
}
// sign
if i_s < s.len && s[i_s] in [`-`, `+`] {
buf[i_d] = s[i_s]
i_s++
i_d++
}
// integer part
for i_s < s.len && s[i_s] >= `0` && s[i_s] <= `9` {
buf[i_d] = s[i_s]
i_s++
i_d++
}
// check decimals
if i_s < s.len && s[i_s] == `.` {
mut i_s1 := i_s + 1
mut sum := 0
mut i_s2 := i_s1 // last non-zero index after `.`
for i_s1 < s.len && s[i_s1] >= `0` && s[i_s1] <= `9` {
sum += s[i_s1] - u8(`0`)
if s[i_s1] != `0` {
i_s2 = i_s1
}
i_s1++
}
// decimal part must be copied
if sum > 0 {
for c_i in i_s .. i_s2 + 1 {
buf[i_d] = s[c_i]
i_d++
}
}
i_s = i_s1
}
if i_s < s.len && s[i_s] != `.` {
// check exponent
for {
buf[i_d] = s[i_s]
i_s++
i_d++
if i_s >= s.len {
break
}
}
}
buf[i_d] = 0
return tos(buf, i_d)
}
}
+100
View File
@@ -0,0 +1,100 @@
module strconv
import strings
pub fn format_str_sb(s string, p BF_param, mut sb strings.Builder) {
if p.len0 <= 0 {
sb.write_string(s)
return
}
dif := p.len0 - utf8_str_visible_length(s)
if dif <= 0 {
sb.write_string(s)
return
}
if p.align == .right {
for i1 := 0; i1 < dif; i1++ {
sb.write_u8(p.pad_ch)
}
}
sb.write_string(s)
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
sb.write_u8(p.pad_ch)
}
}
}
// digit pairs in reverse order
const digit_pairs = '00102030405060708090011121314151617181910212223242526272829203132333435363738393041424344454647484940515253545556575859506162636465666768696071727374757677787970818283848586878889809192939495969798999'
// format_dec_sb format a u64
@[direct_array_access]
pub fn format_dec_sb(d u64, p BF_param, mut res strings.Builder) {
mut n_char := dec_digits(d)
sign_len := if !p.positive || p.sign_flag { 1 } else { 0 }
number_len := sign_len + n_char
dif := p.len0 - number_len
mut sign_written := false
if p.align == .right {
if p.pad_ch == `0` {
if p.positive {
if p.sign_flag {
res.write_u8(`+`)
sign_written = true
}
} else {
res.write_u8(`-`)
sign_written = true
}
}
// write the pad chars
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
if !sign_written {
// no pad char, write the sign before the number
if p.positive {
if p.sign_flag {
res.write_u8(`+`)
}
} else {
res.write_u8(`-`)
}
}
// Legacy version
// max u64 18446744073709551615 => 20 byte
mut buf := [32]u8{}
mut i := 20
mut d1 := d
for i >= (21 - n_char) {
buf[i] = u8(d1 % 10) + `0`
d1 = d1 / 10
i--
}
for _ in 0 .. n_char {
i++
res.write_u8(buf[i])
}
i++
//===========================================
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
return
}
+162
View File
@@ -0,0 +1,162 @@
import strconv
fn test_format() {
mut temp_s := ''
mut tmp_str := ''
a0 := u32(10)
b0 := 200
c0 := u8(12)
s0 := 'ciAo'
ch0 := `B`
f0 := 0.312345
f1 := 200000.0
f2 := -1234.300e6
f3 := 1234.300e-6
sc0 := 'ciao: [%-08u] %d %hhd [%8s] [%08X] [%-20.4f] [%-20.4f] [%c]'
temp_s = unsafe { strconv.v_sprintf(sc0, a0, b0, c0, s0, b0, f0, f1, ch0) }
tmp_str = 'ciao: [10 ] 200 12 [ ciAo] [000000C8] [0.3123 ] [200000.0000 ] [B]'
// C.printf(sc0.str,a0 ,b0 ,c0 ,s0.str ,b0 ,f0, f1, ch0)
// println("\n${temp_s}")
assert tmp_str == temp_s
a := u8(12)
b := i16(13)
c := 14
d := i64(15)
sc1 := '==>%hhd %hd %d %ld'
temp_s = unsafe { strconv.v_sprintf(sc1, a, b, c, d) }
tmp_str = '==>12 13 14 15'
// C.printf(sc1.str, a ,b ,c, d)
// println("\n${temp_s}")
assert tmp_str == temp_s
a1 := u8(0xff)
b1 := i16(u16(0xffff))
c1 := u32(0xffff_ffff)
d1 := u64(-1)
sc2 := '%hhu %hu %u %lu'
temp_s = unsafe { strconv.v_sprintf(sc2, a1, b1, c1, d1) }
tmp_str = '255 65535 4294967295 18446744073709551615'
// C.printf(sc2.str, a1 ,b1 ,c1, d1)
// println("\n${temp_s}")
assert tmp_str == temp_s
sc3 := '%hhx %hx %x %lx'
temp_s = unsafe { strconv.v_sprintf(sc3, a1, b1, c1, d1) }
tmp_str = 'ff ffff ffffffff ffffffffffffffff'
// C.printf(sc3.str, a1 ,b1 ,c1, d1)
// println("\n${temp_s}")
assert tmp_str == temp_s
sc4 := '[%-20.3e] [%20.3e] [%-020.3e] [%-020.3E] [%-020.3e] [%-020.3e]'
temp_s = unsafe { strconv.v_sprintf(sc4, f0, f1, f1, f1, f2, f3) }
tmp_str = '[3.123e-01 ] [ 2.000e+05] [2.000e+05 ] [2.000E+05 ] [-1.234e+09 ] [1.234e-03 ]'
// C.printf(sc4.str, f0, f1, f1, f1, f2, f3)
// println("\n${temp_s}")
assert tmp_str == temp_s
sc5 := '[%.3f] [%0.3f] [%0.3F] [%0.3f] [%0.3F]'
temp_s = unsafe { strconv.v_sprintf(sc5, f0, f1, f1, f2, f3) }
tmp_str = '[0.312] [200000.000] [200000.000] [-1234300000.000] [0.001]'
// C.printf(sc5.str, f0, f1, f1, f2, f3, f3)
// println("\n${temp_s}")
assert tmp_str == temp_s
ml := 3
sc6 := '%.*s [%05hhX]'
temp_s = unsafe { strconv.v_sprintf(sc6, ml, s0, a) }
tmp_str = 'ciA [0000C]'
// C.printf(sc6.str, ml, s0.str, a)
// println("\n${temp_s}")
assert tmp_str == temp_s
a2 := 125
sc7 := '[%9x] [%9X] [%-9x] [%-9X] [%09x] [%09X]'
temp_s = unsafe { strconv.v_sprintf(sc7, a2, a2, a2, a2, a2, a2) }
tmp_str = '[ 7d] [ 7D] [7d ] [7D ] [00000007d] [00000007D]'
// C.printf(sc7.str, a2, a2, a2, a2, a2, a2)
// println("\n${temp_s}")
assert tmp_str == temp_s
g_test := [
'[ -1e-07][ -1E-07]|',
'[ -1e-06][ -1E-06]|',
'[ -1e-05][ -1E-05]|',
'[ -0.0001][ -0.0001]|',
'[ -0.001][ -0.001]|',
'[ -0.01][ -0.01]|',
'[ -0.1][ -0.1]|',
'[ -1][ -1]|',
'[ -10][ -10]|',
'[ -100][ -100]|',
'[ -1000][ -1000]|',
'[ -10000][ -10000]|',
]
mut ft := -1e-7
mut x := 0
mut cnt := 0
sc8 := '[%20g][%20G]|'
for x < 12 {
temp_s = unsafe { strconv.v_sprintf(sc8, ft, ft) }
// C.printf(sc8.str, ft, ft)
// println("\n${temp_s}")
assert temp_s == g_test[cnt]
ft = ft * 10.0
x++
cnt++
}
}
fn test_sprintf_does_not_double_free_on_g() {
x := 3.141516
assert unsafe { strconv.v_sprintf('aaa %G', x) } == 'aaa 3.141516'
}
fn test_sprintf_with_escape() {
n := 69
s := unsafe { strconv.v_sprintf('%d is 100%% awesome', n) }
assert s == '69 is 100% awesome'
}
struct SprintfPromotedValue {
mut:
x u8
}
fn test_sprintf_promoted_variadic_values() {
mut value := SprintfPromotedValue{
x: 1
}
assert unsafe { strconv.v_sprintf('x=%02d', value.x) } == 'x=01'
assert unsafe { strconv.v_sprintf('x=%02d', int(value.x)) } == 'x=01'
assert unsafe { strconv.v_sprintf('%s', 'abc') } == 'abc'
assert unsafe { strconv.v_sprintf('%.1f', f32(1.5)) } == '1.5'
}
fn test_remove_tail_zeros() {
assert strconv.remove_tail_zeros('1.234000000000') == '1.234'
assert strconv.remove_tail_zeros('1.0000000') == '1'
assert strconv.remove_tail_zeros('1234') == '1234'
assert strconv.remove_tail_zeros('1.00000000007') == '1.00000000007'
}
fn test_g_format() {
a := 1234.56789000e10
assert '${a:1.0g}' == '1e+13'
assert '${a:1.1g}' == '1e+13'
assert '${a:1.2g}' == '1.2e+13'
assert '${a:1.3g}' == '1.23e+13'
assert '${a:1.4g}' == '1.235e+13'
assert '${a:1.5g}' == '1.2346e+13'
assert '${a:1.6g}' == '1.23457e+13'
assert '${a:1.7g}' == '1.234568e+13'
assert '${a:1.8g}' == '1.2345679e+13'
assert '${a:1.9g}' == '1.23456789e+13'
assert '${a:1.10g}' == '1.23456789e+13'
assert '${a:1.11g}' == '1.23456789e+13'
assert '${a:1.12g}' == '1.23456789e+13'
// TODO: e format not support due to issue #22429
}
+47
View File
@@ -0,0 +1,47 @@
module strconv
/*
f32/f64 ftoa functions
Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
Use of this source code is governed by an MIT license
that can be found in the LICENSE file.
This file contains the f32/f64 ftoa functions
These functions are based on the work of:
Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN
Conference on Programming Language Design and ImplementationJune 2018
Pages 270–282 https://doi.org/10.1145/3192366.3192369
inspired by the Go version here:
https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea
*/
// ftoa_64 returns a string in scientific notation with max 17 digits after the dot.
// Example: assert strconv.ftoa_64(123.1234567891011121) == '1.2312345678910111e+02'
@[inline]
pub fn ftoa_64(f f64) string {
return f64_to_str(f, 17)
}
// ftoa_long_64 returns `f` as a `string` in decimal notation with a maximum of 17 digits after the dot.
// Example: assert strconv.f64_to_str_l(123.1234567891011121) == '123.12345678910111'
@[inline]
pub fn ftoa_long_64(f f64) string {
return f64_to_str_l(f)
}
// ftoa_32 returns a `string` in scientific notation with max 8 digits after the dot.
// Example: assert strconv.ftoa_32(34.1234567) == '3.4123455e+01'
@[inline]
pub fn ftoa_32(f f32) string {
return f32_to_str(f, 8)
}
// ftoa_long_32 returns `f` as a `string` in decimal notation with a maximum of 8 digits after the dot.
// Example: assert strconv.ftoa_long_32(0.1234567901) == '0.12345679'
@[inline]
pub fn ftoa_long_32(f f32) string {
return f32_to_str_l(f)
}
+67
View File
@@ -0,0 +1,67 @@
module strconv
const base_digits = '0123456789abcdefghijklmnopqrstuvwxyz'
// format_int returns the string representation of the number n in base `radix`
// for digit values > 10, this function uses the small latin leters a-z.
@[direct_array_access; manualfree]
pub fn format_int(n i64, radix int) string {
unsafe {
if radix < 2 || radix > 36 {
panic_n('invalid radix, it should be => 2 and <= 36, actual:', radix)
}
if n == 0 {
return '0'
}
mut n_copy := n
mut have_minus := false
if n < 0 {
have_minus = true
n_copy = -n_copy
}
mut res := ''
for n_copy != 0 {
tmp_0 := res
bdx := int(n_copy % radix)
tmp_1 := base_digits[bdx].ascii_str()
res = tmp_1 + res
tmp_0.free()
tmp_1.free()
// res = base_digits[n_copy % radix].ascii_str() + res
n_copy /= radix
}
if have_minus {
final_res := '-' + res
res.free()
return final_res
}
return res
}
}
// format_uint returns the string representation of the number n in base `radix`
// for digit values > 10, this function uses the small latin leters a-z.
@[direct_array_access; manualfree]
pub fn format_uint(n u64, radix int) string {
unsafe {
if radix < 2 || radix > 36 {
panic_n('invalid radix, it should be => 2 and <= 36, actual:', radix)
}
if n == 0 {
return '0'
}
mut n_copy := n
mut res := ''
uradix := u64(radix)
for n_copy != 0 {
tmp_0 := res
tmp_1 := base_digits[int(n_copy % uradix)].ascii_str()
res = tmp_1 + res
tmp_0.free()
tmp_1.free()
// res = base_digits[n_copy % uradix].ascii_str() + res
n_copy /= uradix
}
return res
}
}
@@ -0,0 +1,38 @@
import strconv
fn test_format_int() {
assert strconv.format_int(0, 2) == '0'
assert strconv.format_int(0, 10) == '0'
assert strconv.format_int(0, 16) == '0'
assert strconv.format_int(0, 36) == '0'
assert strconv.format_int(1, 2) == '1'
assert strconv.format_int(1, 10) == '1'
assert strconv.format_int(1, 16) == '1'
assert strconv.format_int(1, 36) == '1'
assert strconv.format_int(-1, 2) == '-1'
assert strconv.format_int(-1, 10) == '-1'
assert strconv.format_int(-1, 16) == '-1'
assert strconv.format_int(-1, 36) == '-1'
assert strconv.format_int(255, 2) == '11111111'
assert strconv.format_int(255, 8) == '377'
assert strconv.format_int(255, 10) == '255'
assert strconv.format_int(255, 16) == 'ff'
assert strconv.format_int(-255, 2) == '-11111111'
assert strconv.format_int(-255, 8) == '-377'
assert strconv.format_int(-255, 10) == '-255'
assert strconv.format_int(-255, 16) == '-ff'
for i in -256 .. 256 {
assert strconv.format_int(i, 10) == i.str()
}
}
fn test_format_uint() {
assert strconv.format_uint(0, 2) == '0'
assert strconv.format_int(255, 2) == '11111111'
assert strconv.format_int(255, 8) == '377'
assert strconv.format_int(255, 10) == '255'
assert strconv.format_int(255, 16) == 'ff'
assert strconv.format_uint(18446744073709551615, 2) == '1111111111111111111111111111111111111111111111111111111111111111'
assert strconv.format_uint(18446744073709551615, 16) == 'ffffffffffffffff'
assert strconv.format_uint(683058467, 36) == 'baobab'
}
+55
View File
@@ -0,0 +1,55 @@
module strconv
// The structure is filled by parser, then given to converter.
pub struct PrepNumber {
pub mut:
negative bool // 0 if positive number, 1 if negative
exponent int // power of 10 exponent
mantissa u64 // integer mantissa
}
// dec32 is a floating decimal type representing m * 10^e.
struct Dec32 {
mut:
m u32
e int
}
// dec64 is a floating decimal type representing m * 10^e.
struct Dec64 {
mut:
m u64
e int
}
struct Uint128 {
mut:
lo u64
hi u64
}
// support union for convert f32 to u32
union Uf32 {
mut:
f f32
u u32
}
// support union for convert f64 to u64
union Uf64 {
mut:
f f64
u u64
}
pub union Float64u {
pub mut:
f f64
u u64
}
pub union Float32u {
pub mut:
f f32
u u32
}
File diff suppressed because it is too large. Load diff
+341
View File
@@ -0,0 +1,341 @@
module strconv
/*
f32/f64 to string utilities
Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
Use of this source code is governed by an MIT license
that can be found in the LICENSE file.
This file contains the f32/f64 to string utilities functions
These functions are based on the work of:
Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN
Conference on Programming Language Design and ImplementationJune 2018
Pages 270–282 https://doi.org/10.1145/3192366.3192369
inspired by the Go version here:
https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea
*/
/*
f64 to string with string format
*/
// TODO: Investigate precision issues
// f32_to_str_l returns `f` as a `string` in decimal notation with a maximum of 8 digits after the dot.
// Example: assert strconv.f32_to_str_l(0.1234567891) == '0.12345679'
// Example: assert strconv.f32_to_str_l(34.1234567891) == '34.123455'
@[manualfree]
pub fn f32_to_str_l(f f32) string {
s := f32_to_str(f, 8)
res := fxx_to_str_l_parse(s)
unsafe { s.free() }
return res
}
// f32_to_str_l_with_dot returns `f` as a `string` in decimal notation with a maximum of 8 digits after the dot.
// If the decimal digits after the dot are zero, a '.0' is appended for clarity.
//
// Example: assert strconv.f32_to_str_l_with_dot(34.2) == '34.2'
@[manualfree]
pub fn f32_to_str_l_with_dot(f f32) string {
s := f32_to_str(f, 8)
res := fxx_to_str_l_parse_with_dot(s)
unsafe { s.free() }
return res
}
// f64_to_str_l returns `f` as a `string` in decimal notation with a maximum of 18 digits after the dot.
//
// Example: assert strconv.f64_to_str_l(123.1234567891011121) == '123.12345678910111'
@[manualfree]
pub fn f64_to_str_l(f f64) string {
s := f64_to_str(f, 18)
res := fxx_to_str_l_parse(s)
unsafe { s.free() }
return res
}
// f64_to_str_l_with_dot returns `f` as a `string` in decimal notation with a maximum of 18 digits after the dot.
// If the decimal digits after the dot are zero, a '.0' is appended for clarity.
//
// Example: assert strconv.f64_to_str_l_with_dot(34.7) == '34.7'
@[manualfree]
pub fn f64_to_str_l_with_dot(f f64) string {
s := f64_to_str(f, 18)
res := fxx_to_str_l_parse_with_dot(s)
unsafe { s.free() }
return res
}
// fxx_to_str_l_parse returns a `string` in decimal notation converted from a
// floating-point `string` in scientific notation.
//
// Example: assert strconv.fxx_to_str_l_parse('34.22e+00') == '34.22'
@[direct_array_access; manualfree]
pub fn fxx_to_str_l_parse(s string) string {
// check for +inf -inf Nan
if s.len > 2 && (s[0] == `n` || s[1] == `i`) {
return s.clone()
}
m_sgn_flag := false
mut sgn := 1
mut b := [26]u8{}
mut d_pos := 1
mut i := 0
mut i1 := 0
mut exp := 0
mut exp_sgn := 1
// get sign and decimal parts
for c in s {
if c == `-` {
sgn = -1
i++
} else if c == `+` {
sgn = 1
i++
} else if c >= `0` && c <= `9` {
b[i1] = c
i1++
i++
} else if c == `.` {
if sgn > 0 {
d_pos = i
} else {
d_pos = i - 1
}
i++
} else if c == `e` {
i++
break
} else {
return 'Float conversion error!!'
}
}
b[i1] = 0
// get exponent
if s[i] == `-` {
exp_sgn = -1
i++
} else if s[i] == `+` {
exp_sgn = 1
i++
}
mut c := i
for c < s.len {
exp = exp * 10 + int(s[c] - `0`)
c++
}
// allocate exp+32 chars for the return string
mut res := []u8{len: exp + 32, init: 0}
mut r_i := 0 // result string buffer index
// println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}")
if sgn == 1 {
if m_sgn_flag {
res[r_i] = `+`
r_i++
}
} else {
res[r_i] = `-`
r_i++
}
i = 0
if exp_sgn >= 0 {
for b[i] != 0 {
res[r_i] = b[i]
r_i++
i++
if i >= d_pos && exp >= 0 {
if exp == 0 {
res[r_i] = `.`
r_i++
}
exp--
}
}
for exp >= 0 {
res[r_i] = `0`
r_i++
exp--
}
} else {
mut dot_p := true
for exp > 0 {
res[r_i] = `0`
r_i++
exp--
if dot_p {
res[r_i] = `.`
r_i++
dot_p = false
}
}
for b[i] != 0 {
res[r_i] = b[i]
r_i++
i++
}
}
// Add a zero after the dot from the numbers like 2.
if r_i > 1 && res[r_i - 1] == `.` {
res[r_i] = `0`
r_i++
} else if `.` !in res {
// If there is no dot, add it with a zero
res[r_i] = `.`
r_i++
res[r_i] = `0`
r_i++
}
res[r_i] = 0
tmp_res := unsafe { tos(res.data, r_i).clone() }
unsafe { res.free() }
return tmp_res
}
// fxx_to_str_l_parse_with_dot returns a `string` in decimal notation converted from a
// floating-point `string` in scientific notation.
// If the decimal digits after the dot are zero, a '.0' is appended for clarity.
//
// Example: assert strconv.fxx_to_str_l_parse_with_dot ('34.e+01') == '340.0'
@[direct_array_access; manualfree]
pub fn fxx_to_str_l_parse_with_dot(s string) string {
// check for +inf -inf Nan
if s.len > 2 && (s[0] == `n` || s[1] == `i`) {
return s.clone()
}
m_sgn_flag := false
mut sgn := 1
mut b := [26]u8{}
mut d_pos := 1
mut i := 0
mut i1 := 0
mut exp := 0
mut exp_sgn := 1
// get sign and decimal parts
for c in s {
if c == `-` {
sgn = -1
i++
} else if c == `+` {
sgn = 1
i++
} else if c >= `0` && c <= `9` {
b[i1] = c
i1++
i++
} else if c == `.` {
if sgn > 0 {
d_pos = i
} else {
d_pos = i - 1
}
i++
} else if c == `e` {
i++
break
} else {
return 'Float conversion error!!'
}
}
b[i1] = 0
// get exponent
if s[i] == `-` {
exp_sgn = -1
i++
} else if s[i] == `+` {
exp_sgn = 1
i++
}
mut c := i
for c < s.len {
exp = exp * 10 + int(s[c] - `0`)
c++
}
// allocate exp+32 chars for the return string
mut res := []u8{len: exp + 32, init: 0}
mut r_i := 0 // result string buffer index
// println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}")
if sgn == 1 {
if m_sgn_flag {
res[r_i] = `+`
r_i++
}
} else {
res[r_i] = `-`
r_i++
}
i = 0
if exp_sgn >= 0 {
for b[i] != 0 {
res[r_i] = b[i]
r_i++
i++
if i >= d_pos && exp >= 0 {
if exp == 0 {
res[r_i] = `.`
r_i++
}
exp--
}
}
for exp >= 0 {
res[r_i] = `0`
r_i++
exp--
}
} else {
mut dot_p := true
for exp > 0 {
res[r_i] = `0`
r_i++
exp--
if dot_p {
res[r_i] = `.`
r_i++
dot_p = false
}
}
for b[i] != 0 {
res[r_i] = b[i]
r_i++
i++
}
}
// Add a zero after the dot from the numbers like 2.
if r_i > 1 && res[r_i - 1] == `.` {
res[r_i] = `0`
r_i++
} else if `.` !in res {
// If there is no dot, add it with a zero
res[r_i] = `.`
r_i++
res[r_i] = `0`
r_i++
}
res[r_i] = 0
tmp_res := unsafe { tos(res.data, r_i).clone() }
unsafe { res.free() }
return tmp_res
}
+259
View File
@@ -0,0 +1,259 @@
module strconv
import math.bits
// general utilities
// General Utilities
@[if debug_strconv ?]
fn assert1(t bool, msg string) {
if !t {
panic(msg)
}
}
@[inline]
fn bool_to_int(b bool) int {
if b {
return 1
}
return 0
}
@[inline]
fn bool_to_u32(b bool) u32 {
if b {
return u32(1)
}
return u32(0)
}
@[inline]
fn bool_to_u64(b bool) u64 {
if b {
return u64(1)
}
return u64(0)
}
fn get_string_special(neg bool, expZero bool, mantZero bool) string {
if !mantZero {
return 'nan'
}
if !expZero {
if neg {
return '-inf'
} else {
return '+inf'
}
}
if neg {
return '-0e+00'
}
return '0e+00'
}
/*
32 bit functions
*/
fn mul_shift_32(m u32, mul u64, ishift int) u32 {
// QTODO
// assert ishift > 32
hi, lo := bits.mul_64(u64(m), mul)
shifted_sum := (lo >> u64(ishift)) + (hi << u64(64 - ishift))
assert1(shifted_sum <= 2147483647, 'shiftedSum <= math.max_u32')
return u32(shifted_sum)
}
@[direct_array_access; inline]
fn mul_pow5_invdiv_pow2(m u32, q u32, j int) u32 {
assert1(q < pow5_inv_split_32.len, 'q < pow5_inv_split_32.len')
return mul_shift_32(m, pow5_inv_split_32[q], j)
}
@[direct_array_access; inline]
fn mul_pow5_div_pow2(m u32, i u32, j int) u32 {
assert1(i < pow5_split_32.len, 'i < pow5_split_32.len')
return mul_shift_32(m, pow5_split_32[i], j)
}
fn pow5_factor_32(i_v u32) u32 {
mut v := i_v
for n := u32(0); true; n++ {
q := v / 5
r := v % 5
if r != 0 {
return n
}
v = q
}
return v
}
// multiple_of_power_of_five_32 reports whether v is divisible by 5^p.
fn multiple_of_power_of_five_32(v u32, p u32) bool {
return pow5_factor_32(v) >= p
}
// multiple_of_power_of_two_32 reports whether v is divisible by 2^p.
fn multiple_of_power_of_two_32(v u32, p u32) bool {
return u32(bits.trailing_zeros_32(v)) >= p
}
// log10_pow2 returns floor(log_10(2^e)).
fn log10_pow2(e int) u32 {
// The first value this approximation fails for is 2^1651
// which is just greater than 10^297.
assert1(e >= 0, 'e >= 0')
assert1(e <= 1650, 'e <= 1650')
return (u32(e) * 78913) >> 18
}
// log10_pow5 returns floor(log_10(5^e)).
fn log10_pow5(e int) u32 {
// The first value this approximation fails for is 5^2621
// which is just greater than 10^1832.
assert1(e >= 0, 'e >= 0')
assert1(e <= 2620, 'e <= 2620')
return (u32(e) * 732923) >> 20
}
// pow5_bits returns ceil(log_2(5^e)), or else 1 if e==0.
fn pow5_bits(e int) int {
// This approximation works up to the point that the multiplication
// overflows at e = 3529. If the multiplication were done in 64 bits,
// it would fail at 5^4004 which is just greater than 2^9297.
assert1(e >= 0, 'e >= 0')
assert1(e <= 3528, 'e <= 3528')
return int(((u32(e) * 1217359) >> 19) + 1)
}
/*
64 bit functions
*/
fn shift_right_128(v Uint128, shift int) u64 {
// The shift value is always modulo 64.
// In the current implementation of the 64-bit version
// of Ryu, the shift value is always < 64.
// (It is in the range [2, 59].)
// Check this here in case a future change requires larger shift
// values. In this case this function needs to be adjusted.
assert1(shift < 64, 'shift < 64')
return (v.hi << u64(64 - shift)) | (v.lo >> u32(shift))
}
fn mul_shift_64(m u64, mul Uint128, shift int) u64 {
hihi, hilo := bits.mul_64(m, mul.hi)
lohi, _ := bits.mul_64(m, mul.lo)
mut sum := Uint128{
lo: lohi + hilo
hi: hihi
}
if sum.lo < lohi {
sum.hi++ // overflow
}
return shift_right_128(sum, shift - 64)
}
fn pow5_factor_64(v_i u64) u32 {
mut v := v_i
for n := u32(0); true; n++ {
q := v / 5
r := v % 5
if r != 0 {
return n
}
v = q
}
return u32(0)
}
fn multiple_of_power_of_five_64(v u64, p u32) bool {
return pow5_factor_64(v) >= p
}
fn multiple_of_power_of_two_64(v u64, p u32) bool {
return u32(bits.trailing_zeros_64(v)) >= p
}
// dec_digits return the number of decimal digit of an u64
pub fn dec_digits(n u64) int {
if n <= 9_999_999_999 { // 1-10
if n <= 99_999 { // 5
if n <= 99 { // 2
if n <= 9 { // 1
return 1
} else {
return 2
}
} else {
if n <= 999 { // 3
return 3
} else {
if n <= 9999 { // 4
return 4
} else {
return 5
}
}
}
} else {
if n <= 9_999_999 { // 7
if n <= 999_999 { // 6
return 6
} else {
return 7
}
} else {
if n <= 99_999_999 { // 8
return 8
} else {
if n <= 999_999_999 { // 9
return 9
}
return 10
}
}
}
} else {
if n <= 999_999_999_999_999 { // 5
if n <= 999_999_999_999 { // 2
if n <= 99_999_999_999 { // 1
return 11
} else {
return 12
}
} else {
if n <= 9_999_999_999_999 { // 3
return 13
} else {
if n <= 99_999_999_999_999 { // 4
return 14
} else {
return 15
}
}
}
} else {
if n <= 99_999_999_999_999_999 { // 7
if n <= 9_999_999_999_999_999 { // 6
return 16
} else {
return 17
}
} else {
if n <= 999_999_999_999_999_999 { // 8
return 18
} else {
if n <= 9_999_999_999_999_999_999 { // 9
return 19
}
return 20
}
}
}
}
}
+815
View File
@@ -0,0 +1,815 @@
/*=============================================================================
Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
Use of this source code is governed by an MIT license
that can be found in the LICENSE file.
This file contains string interpolation V functions
=============================================================================*/
module strconv
import strings
enum Char_parse_state {
start
norm_char
field_char
pad_ch
len_set_start
len_set_in
check_type
check_float
check_float_in
reset_params
}
// v_printf prints a sprintf-like formatted `string` to the terminal.
// The format string `str` can be constructed at runtime.
// Note, that this function is unsafe.
// In most cases, you are better off using V's string interpolation,
// when your format string is known at compile time.
@[unsafe]
pub fn v_printf(str string, pt ...voidptr) {
print(unsafe { v_sprintf(str, ...pt) })
}
// v_sprintf returns a sprintf-like formatted `string`.
// The format string `str` can be constructed at runtime.
// Note, that this function is unsafe.
// In most cases, you are better off using V's string interpolation,
// when your format string is known at compile time.
// Small integer and `f32` arguments follow C-style default promotions.
// Example:
// ```v
// x := 3.141516
// assert unsafe{strconv.v_sprintf('aaa %G', x)} == 'aaa 3.141516'
// ```
@[direct_array_access; manualfree; unsafe]
pub fn v_sprintf(str string, pt ...voidptr) string {
mut res := strings.new_builder(pt.len * 16)
defer {
unsafe { res.free() }
}
mut i := 0 // main string index
mut p_index := 0 // parameter index
mut sign := false // sign flag
mut align := Align_text.right
mut len0 := -1 // forced length, if -1 free length
mut len1 := -1 // decimal part for floats
def_len1 := 6 // default value for len1
mut pad_ch := u8(` `) // pad char
// prefix chars for Length field
mut ch1 := `0` // +1 char if present else `0`
mut ch2 := `0` // +2 char if present else `0`
mut status := Char_parse_state.norm_char
for i < str.len {
if status == .reset_params {
sign = false
align = .right
len0 = -1
len1 = -1
pad_ch = ` `
status = .norm_char
ch1 = `0`
ch2 = `0`
continue
}
ch := str[i]
if ch != `%` && status == .norm_char {
res.write_u8(ch)
i++
continue
}
if ch == `%` && status == .field_char {
status = .norm_char
res.write_u8(ch)
i++
continue
}
if ch == `%` && status == .norm_char {
status = .field_char
i++
continue
}
// single char, manage it here
if ch == `c` && status == .field_char {
v_sprintf_panic(p_index, pt.len)
d1 := u8(unsafe { *(&int(pt[p_index])) })
res.write_u8(d1)
status = .reset_params
p_index++
i++
continue
}
// pointer, manage it here
if ch == `p` && status == .field_char {
v_sprintf_panic(p_index, pt.len)
res.write_string('0x')
res.write_string(ptr_str(unsafe { pt[p_index] }))
status = .reset_params
p_index++
i++
continue
}
if status == .field_char {
mut fc_ch1 := `0`
mut fc_ch2 := `0`
if (i + 1) < str.len {
fc_ch1 = str[i + 1]
if (i + 2) < str.len {
fc_ch2 = str[i + 2]
}
}
if ch == `+` {
sign = true
i++
continue
} else if ch == `-` {
align = .left
i++
continue
} else if ch in [`0`, ` `] {
if align == .right {
pad_ch = ch
}
i++
continue
} else if ch == `'` {
i++
continue
} else if ch == `.` && fc_ch1 >= `1` && fc_ch1 <= `9` {
status = .check_float
i++
continue
}
// manage "%.*s" precision field
else if ch == `.` && fc_ch1 == `*` && fc_ch2 == `s` {
v_sprintf_panic(p_index, pt.len)
len := unsafe { *(&int(pt[p_index])) }
p_index++
v_sprintf_panic(p_index, pt.len)
mut s := unsafe { *(&string(pt[p_index])) }
s = s[..len]
p_index++
res.write_string(s)
status = .reset_params
i += 3
continue
}
status = .len_set_start
continue
}
if status == .len_set_start {
if ch >= `1` && ch <= `9` {
len0 = int(ch - `0`)
status = .len_set_in
i++
continue
}
if ch == `.` {
status = .check_float
i++
continue
}
status = .check_type
continue
}
if status == .len_set_in {
if ch >= `0` && ch <= `9` {
len0 *= 10
len0 += int(ch - `0`)
i++
continue
}
if ch == `.` {
status = .check_float
i++
continue
}
status = .check_type
continue
}
if status == .check_float {
if ch >= `0` && ch <= `9` {
len1 = int(ch - `0`)
status = .check_float_in
i++
continue
}
status = .check_type
continue
}
if status == .check_float_in {
if ch >= `0` && ch <= `9` {
len1 *= 10
len1 += int(ch - `0`)
i++
continue
}
status = .check_type
continue
}
if status == .check_type {
if ch == `l` {
if ch1 == `0` {
ch1 = `l`
i++
continue
} else {
ch2 = `l`
i++
continue
}
} else if ch == `h` {
if ch1 == `0` {
ch1 = `h`
i++
continue
} else {
ch2 = `h`
i++
continue
}
}
// signed integer
else if ch in [`d`, `i`] {
mut d1 := u64(0)
mut positive := true
// println("${ch1} ${ch2}")
match ch1 {
// h for 16 bit int
// hh for 8 bit int
`h` {
v_sprintf_panic(p_index, pt.len)
x := unsafe { *(&int(pt[p_index])) }
if ch2 == `h` {
sx := i8(x)
positive = if sx >= 0 { true } else { false }
d1 = if positive { u64(sx) } else { u64(-sx) }
} else {
sx := i16(x)
positive = if sx >= 0 { true } else { false }
d1 = if positive { u64(sx) } else { u64(-sx) }
}
}
// l i64
// ll i64 for now
`l` {
// placeholder for future 128bit integer code
/*
if ch2 == `l` {
v_sprintf_panic(p_index, pt.len)
x := *(&i128(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u128(x) } else { u128(-x) }
} else {
v_sprintf_panic(p_index, pt.len)
x := *(&i64(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
}
*/
v_sprintf_panic(p_index, pt.len)
x := unsafe { *(&i64(pt[p_index])) }
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
}
// default int
else {
v_sprintf_panic(p_index, pt.len)
x := unsafe { *(&int(pt[p_index])) }
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
}
}
tmp := format_dec_old(d1,
pad_ch: pad_ch
len0: len0
len1: 0
positive: positive
sign_flag: sign
align: align
)
res.write_string(tmp)
unsafe { tmp.free() }
status = .reset_params
p_index++
i++
ch1 = `0`
ch2 = `0`
continue
}
// unsigned integer
else if ch == `u` {
mut d1 := u64(0)
positive := true
v_sprintf_panic(p_index, pt.len)
match ch1 {
// h for 16 bit unsigned int
// hh for 8 bit unsigned int
`h` {
x := unsafe { *(&int(pt[p_index])) }
if ch2 == `h` {
d1 = u64(u8(x))
} else {
d1 = u64(u16(x))
}
}
// l u64
// ll u64 for now
`l` {
// placeholder for future 128bit integer code
/*
if ch2 == `l` {
d1 = u128(*(&u128(pt[p_index])))
} else {
d1 = u64(*(&u64(pt[p_index])))
}
*/
d1 = u64(unsafe { *(&u64(pt[p_index])) })
}
// default int
else {
d1 = u64(u32(unsafe { *(&int(pt[p_index])) }))
}
}
tmp := format_dec_old(d1,
pad_ch: pad_ch
len0: len0
len1: 0
positive: positive
sign_flag: sign
align: align
)
res.write_string(tmp)
unsafe { tmp.free() }
status = .reset_params
p_index++
i++
continue
}
// hex
else if ch in [`x`, `X`] {
v_sprintf_panic(p_index, pt.len)
mut s := ''
match ch1 {
// h for 16 bit int
// hh fot 8 bit int
`h` {
x := unsafe { *(&int(pt[p_index])) }
if ch2 == `h` {
s = i8(x).hex()
} else {
s = i16(x).hex()
}
}
// l i64
// ll i64 for now
`l` {
// placeholder for future 128bit integer code
/*
if ch2 == `l` {
x := *(&i128(pt[p_index]))
s = x.hex()
} else {
x := *(&i64(pt[p_index]))
s = x.hex()
}
*/
x := unsafe { *(&i64(pt[p_index])) }
s = x.hex()
}
else {
x := unsafe { *(&int(pt[p_index])) }
s = x.hex()
}
}
if ch == `X` {
tmp := s
s = s.to_upper()
unsafe { tmp.free() }
}
tmp := format_str(s,
pad_ch: pad_ch
len0: len0
len1: 0
positive: true
sign_flag: false
align: align
)
res.write_string(tmp)
unsafe { tmp.free() }
unsafe { s.free() }
status = .reset_params
p_index++
i++
continue
}
// float and double
if ch in [`f`, `F`] {
$if !nofloat ? {
v_sprintf_panic(p_index, pt.len)
x := unsafe { *(&f64(pt[p_index])) }
positive := x >= f64(0.0)
len1 = if len1 >= 0 { len1 } else { def_len1 }
s := format_fl_old(f64(x),
pad_ch: pad_ch
len0: len0
len1: len1
positive: positive
sign_flag: sign
align: align
)
if ch == `F` {
tmp := s.to_upper()
res.write_string(tmp)
unsafe { tmp.free() }
} else {
res.write_string(s)
}
unsafe { s.free() }
}
status = .reset_params
p_index++
i++
continue
} else if ch in [`e`, `E`] {
$if !nofloat ? {
v_sprintf_panic(p_index, pt.len)
x := unsafe { *(&f64(pt[p_index])) }
positive := x >= f64(0.0)
len1 = if len1 >= 0 { len1 } else { def_len1 }
s := format_es_old(f64(x),
pad_ch: pad_ch
len0: len0
len1: len1
positive: positive
sign_flag: sign
align: align
)
if ch == `E` {
tmp := s.to_upper()
res.write_string(tmp)
unsafe { tmp.free() }
} else {
res.write_string(s)
}
unsafe { s.free() }
}
status = .reset_params
p_index++
i++
continue
} else if ch in [`g`, `G`] {
$if !nofloat ? {
v_sprintf_panic(p_index, pt.len)
x := unsafe { *(&f64(pt[p_index])) }
positive := x >= f64(0.0)
mut s := ''
tx := fabs(x)
if tx < 999_999.0 && tx >= 0.00001 {
// println("Here g format_fl [${tx}]")
len1 = if len1 >= 0 { len1 + 1 } else { def_len1 }
tmp := s
s = format_fl_old(x,
pad_ch: pad_ch
len0: len0
len1: len1
positive: positive
sign_flag: sign
align: align
rm_tail_zero: true
)
unsafe { tmp.free() }
} else {
len1 = if len1 >= 0 { len1 + 1 } else { def_len1 }
tmp := s
s = format_es_old(x,
pad_ch: pad_ch
len0: len0
len1: len1
positive: positive
sign_flag: sign
align: align
rm_tail_zero: true
)
unsafe { tmp.free() }
}
if ch == `G` {
tmp := s.to_upper()
res.write_string(tmp)
unsafe { tmp.free() }
} else {
res.write_string(s)
}
unsafe { s.free() }
}
status = .reset_params
p_index++
i++
continue
}
// string
else if ch == `s` {
v_sprintf_panic(p_index, pt.len)
s1 := unsafe { *(&string(pt[p_index])) }
pad_ch = ` `
tmp := format_str(s1,
pad_ch: pad_ch
len0: len0
len1: 0
positive: true
sign_flag: false
align: align
)
res.write_string(tmp)
unsafe { tmp.free() }
status = .reset_params
p_index++
i++
continue
}
}
status = .reset_params
p_index++
i++
}
if p_index != pt.len {
panic_n2('% conversion specifiers number mismatch (expected %, given args)', p_index,
pt.len)
}
return res.str()
}
@[inline]
fn v_sprintf_panic(idx int, len int) {
if idx >= len {
panic_n2('% conversion specifiers number mismatch (expected %, given args)', idx + 1, len)
}
}
fn fabs(x f64) f64 {
if x < 0.0 {
return -x
}
return x
}
// strings.Builder version of format_fl
@[direct_array_access; manualfree]
pub fn format_fl_old(f f64, p BF_param) string {
unsafe {
mut s := ''
// mut fs := "1.2343"
mut fs := f64_to_str_lnd1(if f >= 0.0 { f } else { -f }, p.len1)
// println("Dario")
// println(fs)
// error!!
if fs[0] == `[` {
s.free()
return fs
}
if p.rm_tail_zero {
tmp := fs
fs = remove_tail_zeros_old(fs)
tmp.free()
}
mut res := strings.new_builder(if p.len0 > fs.len { p.len0 } else { fs.len })
defer {
res.free()
}
mut sign_len_diff := 0
if p.pad_ch == `0` {
if p.positive {
if p.sign_flag {
res.write_u8(`+`)
sign_len_diff = -1
}
} else {
res.write_u8(`-`)
sign_len_diff = -1
}
tmp := s
s = fs.clone()
tmp.free()
} else {
if p.positive {
if p.sign_flag {
tmp := s
s = '+' + fs
tmp.free()
} else {
tmp := s
s = fs.clone()
tmp.free()
}
} else {
tmp := s
s = '-' + fs
tmp.free()
}
}
dif := p.len0 - s.len + sign_len_diff
if p.align == .right {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
res.write_string(s)
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
s.free()
fs.free()
return res.str()
}
}
@[manualfree]
fn format_es_old(f f64, p BF_param) string {
unsafe {
mut s := ''
mut fs := f64_to_str_pad(if f > 0 { f } else { -f }, p.len1)
if p.rm_tail_zero {
tmp := fs
fs = remove_tail_zeros_old(fs)
tmp.free()
}
mut res := strings.new_builder(if p.len0 > fs.len { p.len0 } else { fs.len })
defer {
res.free()
fs.free()
s.free()
}
mut sign_len_diff := 0
if p.pad_ch == `0` {
if p.positive {
if p.sign_flag {
res.write_u8(`+`)
sign_len_diff = -1
}
} else {
res.write_u8(`-`)
sign_len_diff = -1
}
tmp := s
s = fs.clone()
tmp.free()
} else {
if p.positive {
if p.sign_flag {
tmp := s
s = '+' + fs
tmp.free()
} else {
tmp := s
s = fs.clone()
tmp.free()
}
} else {
tmp := s
s = '-' + fs
tmp.free()
}
}
dif := p.len0 - s.len + sign_len_diff
if p.align == .right {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
res.write_string(s)
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
return res.str()
}
}
fn remove_tail_zeros_old(s string) string {
mut i := 0
mut last_zero_start := -1
mut dot_pos := -1
mut in_decimal := false
mut prev_ch := u8(0)
for i < s.len {
ch := unsafe { s.str[i] }
if ch == `.` {
in_decimal = true
dot_pos = i
} else if in_decimal {
if ch == `0` && prev_ch != `0` {
last_zero_start = i
} else if ch >= `1` && ch <= `9` {
last_zero_start = -1
} else if ch == `e` {
break
}
}
prev_ch = ch
i++
}
mut tmp := ''
if last_zero_start > 0 {
if last_zero_start == dot_pos + 1 {
tmp = s[..dot_pos] + s[i..]
} else {
tmp = s[..last_zero_start] + s[i..]
}
} else {
tmp = s.clone()
}
if unsafe { tmp.str[tmp.len - 1] } == `.` {
return tmp[..tmp.len - 1]
}
return tmp
}
// max int64 9223372036854775807
@[manualfree]
pub fn format_dec_old(d u64, p BF_param) string {
mut s := ''
mut res := strings.new_builder(20)
defer {
unsafe { res.free() }
unsafe { s.free() }
}
mut sign_len_diff := 0
if p.pad_ch == `0` {
if p.positive {
if p.sign_flag {
res.write_u8(`+`)
sign_len_diff = -1
}
} else {
res.write_u8(`-`)
sign_len_diff = -1
}
tmp := s
s = d.str()
unsafe { tmp.free() }
} else {
if p.positive {
if p.sign_flag {
tmp := s
s = '+' + d.str()
unsafe { tmp.free() }
} else {
tmp := s
s = d.str()
unsafe { tmp.free() }
}
} else {
tmp := s
s = '-' + d.str()
unsafe { tmp.free() }
}
}
dif := p.len0 - s.len + sign_len_diff
if p.align == .right {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
res.write_string(s)
if p.align == .left {
for i1 := 0; i1 < dif; i1++ {
res.write_u8(p.pad_ch)
}
}
return res.str()
}