v / vlib / strconv / f32_str.c.v
372 lines · 326 sloc · 9.3 KB · 09cc0c5133d075ee6dcff29286dce7eb7d4421da
Raw
1module strconv
2
3/*=============================================================================
4
5f32 to string
6
7Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
8Use of this source code is governed by an MIT license
9that can be found in the LICENSE file.
10
11This file contains the f32 to string functions
12
13These functions are based on the work of:
14Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN
15Conference on Programming Language Design and ImplementationJune 2018
16Pages 270–282 https://doi.org/10.1145/3192366.3192369
17
18inspired by the Go version here:
19https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea
20
21=============================================================================*/
22
23// pow of ten table used by n_digit reduction
24const ten_pow_table_32 = [
25 u32(1),
26 u32(10),
27 u32(100),
28 u32(1000),
29 u32(10000),
30 u32(100000),
31 u32(1000000),
32 u32(10000000),
33 u32(100000000),
34 u32(1000000000),
35]!
36
37//=============================================================================
38// Conversion Functions
39//=============================================================================
40const mantbits32 = u32(23)
41const expbits32 = u32(8)
42const bias32 = 127 // f32 exponent bias
43
44const maxexp32 = 255
45
46// max 46 char
47// -3.40282346638528859811704183484516925440e+38
48@[direct_array_access]
49pub fn (d Dec32) get_string_32(neg bool, i_n_digit int, i_pad_digit int) string {
50 n_digit := i_n_digit + 1
51 pad_digit := i_pad_digit + 1
52 mut out := d.m
53 // mut out_len := decimal_len_32(out)
54 mut out_len := dec_digits(out)
55 out_len_original := out_len
56
57 mut fw_zeros := 0
58 if pad_digit > out_len {
59 fw_zeros = pad_digit - out_len
60 }
61
62 mut buf := []u8{len: int(out_len + 5 + 1 + 1)} // sign + mant_len + . + e + e_sign + exp_len(2) + \0}
63 mut i := 0
64
65 if neg {
66 if buf.data != 0 {
67 // The buf.data != 0 check here, is needed for clean compilation
68 // with `-cc gcc -cstrict -prod`. Without it, gcc produces:
69 // error: potential null pointer dereference
70 buf[i] = `-`
71 }
72 i++
73 }
74
75 mut disp := 0
76 if out_len <= 1 {
77 disp = 1
78 }
79
80 if n_digit < out_len {
81 // println("orig: ${out_len_original}")
82 out += ten_pow_table_32[out_len - n_digit - 1] * 5 // round to up
83 out /= ten_pow_table_32[out_len - n_digit]
84 out_len = n_digit
85 }
86
87 y := i + out_len
88 mut x := 0
89 for x < (out_len - disp - 1) {
90 buf[y - x] = `0` + u8(out % 10)
91 out /= 10
92 i++
93 x++
94 }
95
96 // no decimal digits needed, end here
97 if i_n_digit == 0 {
98 unsafe {
99 buf[i] = 0
100 return tos(memdup(&buf[0], i + 1), i)
101 }
102 }
103
104 if out_len > 1 || fw_zeros > 0 {
105 buf[y - x] = `.`
106 i++
107 }
108 x++
109
110 if y - x >= 0 {
111 buf[y - x] = `0` + u8(out % 10)
112 i++
113 }
114
115 for fw_zeros > 0 {
116 buf[i] = `0`
117 i++
118 fw_zeros--
119 }
120
121 buf[i] = `e`
122 i++
123
124 mut exp := d.e + out_len_original - 1
125 if exp < 0 {
126 buf[i] = `-`
127 i++
128 exp = -exp
129 } else {
130 buf[i] = `+`
131 i++
132 }
133
134 // Always print two digits to match strconv's formatting.
135 d1 := exp % 10
136 d0 := exp / 10
137 buf[i] = `0` + u8(d0)
138 i++
139 buf[i] = `0` + u8(d1)
140 i++
141 buf[i] = 0
142
143 return unsafe {
144 tos(memdup(&buf[0], i + 1), i)
145 }
146}
147
148fn f32_to_decimal_exact_int(i_mant u32, exp u32) (Dec32, bool) {
149 mut d := Dec32{}
150 e := exp - bias32
151 if e > mantbits32 {
152 return d, false
153 }
154 shift := mantbits32 - e
155 mant := i_mant | 0x0080_0000 // implicit 1
156 // mant := i_mant | (1 << mantbits32) // implicit 1
157 d.m = mant >> shift
158 if (d.m << shift) != mant {
159 return d, false
160 }
161 for (d.m % 10) == 0 {
162 d.m /= 10
163 d.e++
164 }
165 return d, true
166}
167
168fn f32_to_decimal(mant u32, exp u32) Dec32 {
169 mut e2 := 0
170 mut m2 := u32(0)
171 if exp == 0 {
172 // We subtract 2 so that the bounds computation has
173 // 2 additional bits.
174 e2 = 1 - bias32 - int(mantbits32) - 2
175 m2 = mant
176 } else {
177 e2 = int(exp) - bias32 - int(mantbits32) - 2
178 m2 = (u32(1) << mantbits32) | mant
179 }
180 even := (m2 & 1) == 0
181 accept_bounds := even
182
183 // Step 2: Determine the interval of valid decimal representations.
184 mv := u32(4 * m2)
185 mp := u32(4 * m2 + 2)
186 mm_shift := bool_to_u32(mant != 0 || exp <= 1)
187 mm := u32(4 * m2 - 1 - mm_shift)
188
189 mut vr := u32(0)
190 mut vp := u32(0)
191 mut vm := u32(0)
192 mut e10 := 0
193 mut vm_is_trailing_zeros := false
194 mut vr_is_trailing_zeros := false
195 mut last_removed_digit := u8(0)
196
197 if e2 >= 0 {
198 q := log10_pow2(e2)
199 e10 = int(q)
200 k := pow5_inv_num_bits_32 + pow5_bits(int(q)) - 1
201 i := -e2 + int(q) + k
202
203 vr = mul_pow5_invdiv_pow2(mv, q, i)
204 vp = mul_pow5_invdiv_pow2(mp, q, i)
205 vm = mul_pow5_invdiv_pow2(mm, q, i)
206 if q != 0 && (vp - 1) / 10 <= vm / 10 {
207 // We need to know one removed digit even if we are not
208 // going to loop below. We could use q = X - 1 above,
209 // except that would require 33 bits for the result, and
210 // we've found that 32-bit arithmetic is faster even on
211 // 64-bit machines.
212 l := pow5_inv_num_bits_32 + pow5_bits(int(q - 1)) - 1
213 last_removed_digit = u8(mul_pow5_invdiv_pow2(mv, q - 1, -e2 + int(q - 1) + l) % 10)
214 }
215 if q <= 9 {
216 // The largest power of 5 that fits in 24 bits is 5^10,
217 // but q <= 9 seems to be safe as well. Only one of mp,
218 // mv, and mm can be a multiple of 5, if any.
219 if mv % 5 == 0 {
220 vr_is_trailing_zeros = multiple_of_power_of_five_32(mv, q)
221 } else if accept_bounds {
222 vm_is_trailing_zeros = multiple_of_power_of_five_32(mm, q)
223 } else if multiple_of_power_of_five_32(mp, q) {
224 vp--
225 }
226 }
227 } else {
228 q := log10_pow5(-e2)
229 e10 = int(q) + e2
230 i := -e2 - int(q)
231 k := pow5_bits(i) - pow5_num_bits_32
232 mut j := int(q) - k
233 vr = mul_pow5_div_pow2(mv, u32(i), j)
234 vp = mul_pow5_div_pow2(mp, u32(i), j)
235 vm = mul_pow5_div_pow2(mm, u32(i), j)
236 if q != 0 && ((vp - 1) / 10) <= vm / 10 {
237 j = int(q) - 1 - (pow5_bits(i + 1) - pow5_num_bits_32)
238 last_removed_digit = u8(mul_pow5_div_pow2(mv, u32(i + 1), j) % 10)
239 }
240 if q <= 1 {
241 // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at
242 // least q trailing 0 bits. mv = 4 * m2, so it always
243 // has at least two trailing 0 bits.
244 vr_is_trailing_zeros = true
245 if accept_bounds {
246 // mm = mv - 1 - mm_shift, so it has 1 trailing 0 bit
247 // if mm_shift == 1.
248 vm_is_trailing_zeros = mm_shift == 1
249 } else {
250 // mp = mv + 2, so it always has at least one
251 // trailing 0 bit.
252 vp--
253 }
254 } else if q < 31 {
255 vr_is_trailing_zeros = multiple_of_power_of_two_32(mv, q - 1)
256 }
257 }
258
259 // Step 4: Find the shortest decimal representation
260 // in the interval of valid representations.
261 mut removed := 0
262 mut out := u32(0)
263 if vm_is_trailing_zeros || vr_is_trailing_zeros {
264 // General case, which happens rarely (~4.0%).
265 for vp / 10 > vm / 10 {
266 vm_is_trailing_zeros = vm_is_trailing_zeros && (vm % 10) == 0
267 vr_is_trailing_zeros = vr_is_trailing_zeros && last_removed_digit == 0
268 last_removed_digit = u8(vr % 10)
269 vr /= 10
270 vp /= 10
271 vm /= 10
272 removed++
273 }
274 if vm_is_trailing_zeros {
275 for vm % 10 == 0 {
276 vr_is_trailing_zeros = vr_is_trailing_zeros && last_removed_digit == 0
277 last_removed_digit = u8(vr % 10)
278 vr /= 10
279 vp /= 10
280 vm /= 10
281 removed++
282 }
283 }
284 if vr_is_trailing_zeros && last_removed_digit == 5 && (vr % 2) == 0 {
285 // Round even if the exact number is .....50..0.
286 last_removed_digit = 4
287 }
288 out = vr
289 // We need to take vr + 1 if vr is outside bounds
290 // or we need to round up.
291 if (vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5 {
292 out++
293 }
294 } else {
295 // Specialized for the common case (~96.0%). Percentages below
296 // are relative to this. Loop iterations below (approximately):
297 // 0: 13.6%, 1: 70.7%, 2: 14.1%, 3: 1.39%, 4: 0.14%, 5+: 0.01%
298 for vp / 10 > vm / 10 {
299 last_removed_digit = u8(vr % 10)
300 vr /= 10
301 vp /= 10
302 vm /= 10
303 removed++
304 }
305 // We need to take vr + 1 if vr is outside bounds
306 // or we need to round up.
307 out = vr + bool_to_u32(vr == vm || last_removed_digit >= 5)
308 }
309
310 return Dec32{
311 m: out
312 e: e10 + removed
313 }
314}
315
316//=============================================================================
317// String Functions
318//=============================================================================
319
320// f32_to_str returns a `string` in scientific notation with max `n_digit` after the dot.
321pub fn f32_to_str(f f32, n_digit int) string {
322 mut u1 := Uf32{}
323 u1.f = f
324 u := unsafe { u1.u }
325
326 neg := (u >> (mantbits32 + expbits32)) != 0
327 mant := u & ((u32(1) << mantbits32) - u32(1))
328 exp := (u >> mantbits32) & ((u32(1) << expbits32) - u32(1))
329
330 // println("${neg} ${mant} e ${exp-bias32}")
331
332 // Exit early for easy cases.
333 if exp == maxexp32 || (exp == 0 && mant == 0) {
334 return get_string_special(neg, exp == 0, mant == 0)
335 }
336
337 mut d, ok := f32_to_decimal_exact_int(mant, exp)
338 if !ok {
339 // println("with exp form")
340 d = f32_to_decimal(mant, exp)
341 }
342
343 // println("${d.m} ${d.e}")
344 return d.get_string_32(neg, n_digit, 0)
345}
346
347// f32_to_str_pad returns a `string` in scientific notation with max `n_digit` after the dot.
348pub fn f32_to_str_pad(f f32, n_digit int) string {
349 mut u1 := Uf32{}
350 u1.f = f
351 u := unsafe { u1.u }
352
353 neg := (u >> (mantbits32 + expbits32)) != 0
354 mant := u & ((u32(1) << mantbits32) - u32(1))
355 exp := (u >> mantbits32) & ((u32(1) << expbits32) - u32(1))
356
357 // println("${neg} ${mant} e ${exp-bias32}")
358
359 // Exit early for easy cases.
360 if exp == maxexp32 || (exp == 0 && mant == 0) {
361 return get_string_special(neg, exp == 0, mant == 0)
362 }
363
364 mut d, ok := f32_to_decimal_exact_int(mant, exp)
365 if !ok {
366 // println("with exp form")
367 d = f32_to_decimal(mant, exp)
368 }
369
370 // println("${d.m} ${d.e}")
371 return d.get_string_32(neg, n_digit, n_digit)
372}
373