Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,3 @@
|
||||
## Description
|
||||
|
||||
`strconv` provides functions for converting strings to numbers and numbers to strings.
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
@@ -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)]!
|
||||
@@ -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
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -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)!)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in new issue
Block a user