Skip to content

Commit 8a79300

Browse files
authored
strings: add Builder.write_u_decimal and write_decimal JS-backend parity (#27522)
1 parent cc39afe commit 8a79300

3 files changed

Lines changed: 87 additions & 11 deletions

File tree

vlib/strings/builder.c.v

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,32 +74,44 @@ pub fn (mut b Builder) write_byte(data u8) {
7474
// write_decimal appends a decimal representation of the number `n` into the builder `b`,
7575
// without dynamic allocation. The higher order digits come first, i.e. 6123 will be written
7676
// with the digit `6` first, then `1`, then `2` and `3` last.
77-
@[direct_array_access]
7877
pub fn (mut b Builder) write_decimal(n i64) {
7978
if n == 0 {
8079
b.write_u8(0x30)
8180
return
8281
}
83-
if n == min_i64 {
84-
b.write_string(n.str())
82+
mut mag := u64(n)
83+
if n < 0 {
84+
b.write_u8(`-`)
85+
// Wrapping unsigned negation yields the correct magnitude even for `min_i64`,
86+
// whose absolute value does not fit in an i64, so this stays allocation-free for
87+
// every input without a special case for the signed 64-bit minimum.
88+
mag = u64(0) - mag
89+
}
90+
b.write_u_decimal(mag)
91+
}
92+
93+
// write_u_decimal appends a decimal representation of the unsigned number `n` into the
94+
// builder `b`, without dynamic allocation. Unlike `write_decimal`, it covers the entire
95+
// `u64` range (values above `max_i64`). The higher order digits come first, i.e. 6123
96+
// will be written with the digit `6` first, then `1`, then `2` and `3` last.
97+
@[direct_array_access]
98+
pub fn (mut b Builder) write_u_decimal(n u64) {
99+
if n == 0 {
100+
b.write_u8(0x30)
85101
return
86102
}
87103

88-
mut buf := [25]u8{}
89-
mut x := if n < 0 { -n } else { n }
90-
mut i := 24
104+
mut buf := [20]u8{} // max_u64 == 18446744073709551615, i.e. 20 digits
105+
mut x := n
106+
mut i := 19
91107
for x != 0 {
92108
nextx := x / 10
93109
r := x % 10
94110
buf[i] = u8(r) + 0x30
95111
x = nextx
96112
i--
97113
}
98-
if n < 0 {
99-
buf[i] = `-`
100-
i--
101-
}
102-
unsafe { b.write_ptr(&buf[i + 1], 24 - i) }
114+
unsafe { b.write_ptr(&buf[i + 1], 19 - i) }
103115
}
104116

105117
// write implements the io.Writer interface, that is why it returns how many bytes were written to the string builder.

vlib/strings/builder.js.v

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,47 @@ pub fn (mut b Builder) write_u8(data u8) {
2626
b << data
2727
}
2828

29+
// write_decimal appends a decimal representation of the number `n` into the builder `b`.
30+
// The higher order digits come first, i.e. 6123 will be written with the digit `6` first,
31+
// then `1`, then `2` and `3` last.
32+
pub fn (mut b Builder) write_decimal(n i64) {
33+
if n == 0 {
34+
b.write_u8(0x30)
35+
return
36+
}
37+
mut mag := u64(n)
38+
if n < 0 {
39+
b.write_u8(`-`)
40+
// Wrapping unsigned negation yields the correct magnitude even for `min_i64`,
41+
// whose absolute value does not fit in an i64. It also avoids depending on the
42+
// `min_i64` constant, which the JS backend currently lowers incorrectly, so a
43+
// runtime `min_i64` (e.g. from `'-9223372036854775808'.i64()`) still formats right.
44+
mag = u64(0) - mag
45+
}
46+
b.write_u_decimal(mag)
47+
}
48+
49+
// write_u_decimal appends a decimal representation of the unsigned number `n` into the
50+
// builder `b`. Unlike `write_decimal`, it covers the entire `u64` range (values above
51+
// `max_i64`). The higher order digits come first.
52+
pub fn (mut b Builder) write_u_decimal(n u64) {
53+
if n == 0 {
54+
b.write_u8(0x30)
55+
return
56+
}
57+
mut buf := [20]u8{} // max_u64 == 18446744073709551615, i.e. 20 digits
58+
mut x := n
59+
mut i := 19
60+
for x != 0 {
61+
buf[i] = u8(x % 10) + 0x30
62+
x = x / 10
63+
i--
64+
}
65+
for j := i + 1; j <= 19; j++ {
66+
b.write_u8(buf[j])
67+
}
68+
}
69+
2970
pub fn (mut b Builder) write(data []u8) ?int {
3071
if data.len == 0 {
3172
return 0

vlib/strings/builder_test.v

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,29 @@ fn test_write_decimal() {
168168
assert sb_i64_str(9223372036854775807) == '9223372036854775807'
169169
assert sb_i64_str(-9223372036854775807) == '-9223372036854775807'
170170
assert sb_i64_str(min_i64) == '-9223372036854775808'
171+
// runtime `min_i64` (parsed, not the constant), whose negation overflows i64:
172+
assert sb_i64_str('-9223372036854775808'.i64()) == '-9223372036854775808'
173+
}
174+
175+
@[manualfree]
176+
fn sb_u64_str(n u64) string {
177+
mut sb := strings.new_builder(24)
178+
defer {
179+
unsafe { sb.free() }
180+
}
181+
sb.write_u_decimal(n)
182+
return sb.str()
183+
}
184+
185+
fn test_write_u_decimal() {
186+
assert sb_u64_str(0) == '0'
187+
assert sb_u64_str(1) == '1'
188+
assert sb_u64_str(1001) == '1001'
189+
assert sb_u64_str(1234567890) == '1234567890'
190+
assert sb_u64_str(u64(9223372036854775807)) == '9223372036854775807'
191+
// values above max_i64, which write_decimal(i64) cannot represent:
192+
assert sb_u64_str(u64(9223372036854775807) + 1) == '9223372036854775808'
193+
assert sb_u64_str(max_u64) == '18446744073709551615'
171194
}
172195

173196
fn test_grow_len() {

0 commit comments

Comments
 (0)