/rust/registry/src/index.crates.io-1949cf8c6b5b557f/zmij-1.0.23/src/lib.rs
Line | Count | Source |
1 | | //! [![github]](https://github.com/dtolnay/zmij) [![crates-io]](https://crates.io/crates/zmij) [![docs-rs]](https://docs.rs/zmij) |
2 | | //! |
3 | | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github |
4 | | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust |
5 | | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs |
6 | | //! |
7 | | //! <br> |
8 | | //! |
9 | | //! A double-to-string conversion algorithm based on [Schubfach] and [yy]. |
10 | | //! |
11 | | //! This Rust implementation is a line-by-line port of Victor Zverovich's |
12 | | //! implementation in C++, <https://github.com/vitaut/zmij>. |
13 | | //! |
14 | | //! [Schubfach]: https://fmt.dev/papers/Schubfach4.pdf |
15 | | //! [yy]: https://github.com/ibireme/c_numconv_benchmark/blob/master/vendor/yy_double/yy_double.c |
16 | | //! |
17 | | //! <br> |
18 | | //! |
19 | | //! # Example |
20 | | //! |
21 | | //! ``` |
22 | | //! fn main() { |
23 | | //! let mut buffer = zmij::Buffer::new(); |
24 | | //! let printed = buffer.format(1.234); |
25 | | //! assert_eq!(printed, "1.234"); |
26 | | //! } |
27 | | //! ``` |
28 | | //! |
29 | | //! <br> |
30 | | //! |
31 | | //! ## Performance |
32 | | //! |
33 | | //! The [dtoa-benchmark] compares this library and other Rust floating point |
34 | | //! formatting implementations across a range of precisions. The vertical axis |
35 | | //! in this chart shows nanoseconds taken by a single execution of |
36 | | //! `zmij::Buffer::new().format_finite(value)` so a lower result indicates a |
37 | | //! faster library. |
38 | | //! |
39 | | //! [dtoa-benchmark]: https://github.com/dtolnay/dtoa-benchmark |
40 | | //! |
41 | | //!  |
42 | | |
43 | | #![no_std] |
44 | | #![doc(html_root_url = "https://docs.rs/zmij/1.0.23")] |
45 | | #![deny(unsafe_op_in_unsafe_fn)] |
46 | | #![allow(non_camel_case_types, non_snake_case)] |
47 | | #![allow( |
48 | | clippy::blocks_in_conditions, |
49 | | clippy::cast_possible_truncation, |
50 | | clippy::cast_possible_wrap, |
51 | | clippy::cast_ptr_alignment, |
52 | | clippy::cast_sign_loss, |
53 | | clippy::doc_markdown, |
54 | | clippy::incompatible_msrv, |
55 | | clippy::items_after_statements, |
56 | | clippy::manual_ilog2, |
57 | | clippy::many_single_char_names, |
58 | | clippy::modulo_one, |
59 | | clippy::must_use_candidate, |
60 | | clippy::needless_doctest_main, |
61 | | clippy::needless_late_init, |
62 | | clippy::never_loop, |
63 | | clippy::redundant_else, |
64 | | clippy::similar_names, |
65 | | clippy::too_many_arguments, |
66 | | clippy::too_many_lines, |
67 | | clippy::unreadable_literal, |
68 | | clippy::used_underscore_items, |
69 | | clippy::while_immutable_condition, |
70 | | clippy::wildcard_imports |
71 | | )] |
72 | | |
73 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
74 | | mod stdarch_x86; |
75 | | #[cfg(test)] |
76 | | mod tests; |
77 | | mod traits; |
78 | | |
79 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
80 | | use crate::stdarch_x86::{ |
81 | | __m128i, _mm_add_epi64, _mm_cmpgt_epi8, _mm_cvtsi128_si64, _mm_load_si128, _mm_movemask_epi8, |
82 | | _mm_mul_epu32, _mm_mulhi_epu16, _mm_mullo_epi16, _mm_or_si128, _mm_set_epi64x, |
83 | | _mm_setzero_si128, _mm_srli_epi64, |
84 | | }; |
85 | | #[cfg(all( |
86 | | target_arch = "x86_64", |
87 | | target_feature = "sse2", |
88 | | target_feature = "sse4.1", |
89 | | not(miri) |
90 | | ))] |
91 | | use crate::stdarch_x86::{ |
92 | | _mm_insert_epi64, _mm_mullo_epi32, _mm_shuffle_epi8, _mm_srli_epi32, _mm_storeu_si128, |
93 | | }; |
94 | | #[cfg(all( |
95 | | target_arch = "x86_64", |
96 | | target_feature = "sse2", |
97 | | not(target_feature = "sse4.1"), |
98 | | not(miri) |
99 | | ))] |
100 | | use crate::stdarch_x86::{ |
101 | | _mm_shuffle_epi32, _mm_slli_epi16, _mm_slli_epi32, _mm_srli_epi16, _mm_sub_epi16, _MM_SHUFFLE, |
102 | | }; |
103 | | use crate::traits::Float as _; |
104 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
105 | | use core::arch::aarch64::{ |
106 | | int16x8_t, int32x2_t, int32x4_t, uint16x8_t, uint64x1_t, uint8x16_t, vaddq_u16, vcgtzq_s8, |
107 | | vcombine_s32, vcreate_u64, vdup_n_s32, vdupq_n_s8, vdupq_n_u8, vget_lane_u64, vget_low_u8, |
108 | | vld1q_u8, vmla_n_s32, vmlaq_n_s16, vmlaq_n_s32, vorrq_u8, vqdmulh_n_s32, vqdmulhq_n_s16, |
109 | | vqdmulhq_n_s32, vqtbl1q_u8, vreinterpret_s32_u32, vreinterpret_s32_u64, vreinterpret_u16_s32, |
110 | | vreinterpret_u32_s32, vreinterpret_u64_u8, vreinterpretq_s16_s32, vreinterpretq_s32_u32, |
111 | | vreinterpretq_s8_u8, vreinterpretq_u16_s8, vreinterpretq_u16_u8, vreinterpretq_u64_u8, |
112 | | vreinterpretq_u8_s16, vreinterpretq_u8_u64, vrev64q_u8, vsetq_lane_u64, vshll_n_u16, |
113 | | vshr_n_u32, vshrn_n_u16, vst1q_u8, |
114 | | }; |
115 | | #[cfg(all(any(target_arch = "aarch64", target_arch = "x86_64"), not(miri)))] |
116 | | use core::arch::asm; |
117 | | use core::mem::{self, MaybeUninit}; |
118 | | use core::ops::RangeInclusive; |
119 | | use core::ptr; |
120 | | use core::slice; |
121 | | use core::str; |
122 | | #[cfg(feature = "no-panic")] |
123 | | use no_panic::no_panic; |
124 | | |
125 | | const BUFFER_SIZE: usize = 24; |
126 | | const NAN: &str = "NaN"; |
127 | | const INFINITY: &str = "inf"; |
128 | | const NEG_INFINITY: &str = "-inf"; |
129 | | |
130 | | // Declares struct members that must live in memory on ARM64 but are encoded as |
131 | | // immediates in the x64 assembly. |
132 | | struct AArch64Mem<const VALUE: u64> { |
133 | | #[cfg(target_arch = "aarch64")] |
134 | | value: u64, |
135 | | } |
136 | | |
137 | | impl<const VALUE: u64> AArch64Mem<VALUE> { |
138 | 0 | const fn new() -> Self { |
139 | 0 | AArch64Mem { |
140 | 0 | #[cfg(target_arch = "aarch64")] |
141 | 0 | value: VALUE, |
142 | 0 | } |
143 | 0 | } |
144 | | |
145 | | #[cfg_attr(not(target_arch = "aarch64"), allow(clippy::unused_self))] |
146 | 0 | const fn get(&self) -> u64 { |
147 | | #[cfg(target_arch = "aarch64")] |
148 | | { |
149 | | self.value |
150 | | } |
151 | | |
152 | | #[cfg(not(target_arch = "aarch64"))] |
153 | | { |
154 | 0 | VALUE |
155 | | } |
156 | 0 | } Unexecuted instantiation: <zmij::AArch64Mem<1000000000000000>>::get Unexecuted instantiation: <zmij::AArch64Mem<9223372036854775814>>::get |
157 | | } |
158 | | |
159 | | #[derive(Copy, Clone)] |
160 | | #[cfg_attr(test, derive(Debug, PartialEq))] |
161 | | struct uint128 { |
162 | | hi: u64, |
163 | | lo: u64, |
164 | | } |
165 | | |
166 | | // Use umul128_hi64 for division. |
167 | | const USE_UMUL128_HI64: bool = cfg!(target_vendor = "apple"); |
168 | | |
169 | | // Computes 128-bit result of multiplication of two 64-bit unsigned integers. |
170 | 0 | const fn umul128(x: u64, y: u64) -> u128 { |
171 | 0 | x as u128 * y as u128 |
172 | 0 | } |
173 | | |
174 | | #[inline] |
175 | 0 | const fn umul128_hi64(x: u64, y: u64) -> u64 { |
176 | 0 | (umul128(x, y) >> 64) as u64 |
177 | 0 | } |
178 | | |
179 | | // Returns (x * y + c) >> 64. |
180 | | #[cfg_attr(feature = "no-panic", no_panic)] |
181 | 0 | fn umul128_add_hi64(x: u64, y: u64, c: u64) -> u64 { |
182 | 0 | ((u128::from(x) * u128::from(y) + u128::from(c)) >> 64) as u64 |
183 | 0 | } |
184 | | |
185 | | #[cfg_attr(feature = "no-panic", no_panic)] |
186 | 0 | fn umul192_hi128(x_hi: u64, x_lo: u64, y: u64) -> uint128 { |
187 | 0 | let p = umul128(x_hi, y); |
188 | 0 | let lo = (p as u64).wrapping_add((umul128(x_lo, y) >> 64) as u64); |
189 | 0 | uint128 { |
190 | 0 | hi: (p >> 64) as u64 + u64::from(lo < p as u64), |
191 | 0 | lo, |
192 | 0 | } |
193 | 0 | } |
194 | | |
195 | | // Returns x / 10 for x <= 2**62. |
196 | | #[cfg_attr(feature = "no-panic", no_panic)] |
197 | 0 | fn div10(x: u64) -> u64 { |
198 | 0 | debug_assert!(x < (1 << 62)); |
199 | | // ceil(2**64 / 10) computed as (1 << 63) / 5 + 1 to avoid int128. |
200 | | const DIV10_SIG64: u64 = (1 << 63) / 5 + 1; |
201 | 0 | umul128_hi64(x, DIV10_SIG64) |
202 | 0 | } |
203 | | |
204 | | // Computes the decimal exponent as floor(log10(2**bin_exp)) if regular or |
205 | | // floor(log10(3/4 * 2**bin_exp)) otherwise, without branching. |
206 | 0 | const fn compute_dec_exp(bin_exp: i32, regular: bool) -> i32 { |
207 | 0 | debug_assert!(bin_exp >= -1334 && bin_exp <= 2620); |
208 | | // log10_3_over_4_sig = -log10(3/4) * 2**log10_2_exp rounded to a power of 2 |
209 | | const LOG10_3_OVER_4_SIG: i32 = 131_072; |
210 | | // log10_2_sig = round(log10(2) * 2**log10_2_exp) |
211 | | const LOG10_2_SIG: i32 = 315_653; |
212 | | const LOG10_2_EXP: i32 = 20; |
213 | 0 | (bin_exp * LOG10_2_SIG - !regular as i32 * LOG10_3_OVER_4_SIG) >> LOG10_2_EXP |
214 | 0 | } |
215 | | |
216 | | trait FloatTraits: traits::Float { |
217 | | // Note: Rust port uses wider fixed-notation ranges than upstream. |
218 | | const FIXED_DEC_EXP: RangeInclusive<i32>; |
219 | | |
220 | | const NUM_BITS: i32; |
221 | | const NUM_SIG_BITS: i32 = Self::MANTISSA_DIGITS as i32 - 1; |
222 | | const NUM_EXP_BITS: i32 = Self::NUM_BITS - Self::NUM_SIG_BITS - 1; |
223 | | const EXP_MASK: i32 = (1 << Self::NUM_EXP_BITS) - 1; |
224 | | const EXP_BIAS: i32 = (1 << (Self::NUM_EXP_BITS - 1)) - 1; |
225 | | const EXP_OFFSET: i32 = Self::EXP_BIAS + Self::NUM_SIG_BITS; |
226 | | |
227 | | type SigType: traits::UInt; |
228 | | const IMPLICIT_BIT: Self::SigType; |
229 | | |
230 | | type DecDigitsType: Copy; |
231 | | |
232 | | #[cfg(any( |
233 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
234 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
235 | | ))] |
236 | | type DecUnshuffledType; |
237 | | |
238 | | fn to_bits(self) -> Self::SigType; |
239 | | |
240 | 0 | fn is_negative(bits: Self::SigType) -> bool { |
241 | 0 | (bits >> (Self::NUM_BITS - 1)) != Self::SigType::from(0) |
242 | 0 | } Unexecuted instantiation: <f64 as zmij::FloatTraits>::is_negative Unexecuted instantiation: <f32 as zmij::FloatTraits>::is_negative |
243 | | |
244 | 0 | fn get_sig(bits: Self::SigType) -> Self::SigType { |
245 | 0 | bits & (Self::IMPLICIT_BIT - Self::SigType::from(1)) |
246 | 0 | } Unexecuted instantiation: <f64 as zmij::FloatTraits>::get_sig Unexecuted instantiation: <f32 as zmij::FloatTraits>::get_sig |
247 | | |
248 | 0 | fn get_exp(bits: Self::SigType) -> i64 { |
249 | 0 | (bits << 1u8 >> (Self::NUM_SIG_BITS + 1)).into() as i64 |
250 | 0 | } Unexecuted instantiation: <f64 as zmij::FloatTraits>::get_exp Unexecuted instantiation: <f32 as zmij::FloatTraits>::get_exp |
251 | | |
252 | | // Converts a significand to a string, removing trailing zeros. value has up |
253 | | // to 17 decimal digits (16-17 for normals) for f64 and up to 9 digits (8-9 |
254 | | // for normals) for f32. |
255 | | fn to_digits(value: u64, d: &Data) -> DecDigits<Self>; |
256 | | |
257 | | unsafe fn write_exp_float_simd( |
258 | | buffer: *mut u8, |
259 | | dig: &DecDigits<Self>, |
260 | | last_digit: i32, |
261 | | has_last_digit: bool, |
262 | | has_extra_digit: bool, |
263 | | exp_data: u64, |
264 | | d: &Data, |
265 | | ) -> *mut u8; |
266 | | } |
267 | | |
268 | | impl FloatTraits for f32 { |
269 | | // Upstream uses -4..=6. |
270 | | const FIXED_DEC_EXP: RangeInclusive<i32> = -6..=12; |
271 | | |
272 | | const NUM_BITS: i32 = 32; |
273 | | const IMPLICIT_BIT: u32 = 1 << Self::NUM_SIG_BITS; |
274 | | |
275 | | type SigType = u32; |
276 | | |
277 | | type DecDigitsType = u64; |
278 | | |
279 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
280 | | type DecUnshuffledType = uint8x16_t; |
281 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
282 | | type DecUnshuffledType = __m128i; |
283 | | |
284 | | #[inline] |
285 | 0 | fn to_bits(self) -> Self::SigType { |
286 | 0 | self.to_bits() |
287 | 0 | } |
288 | | |
289 | | #[inline] |
290 | 0 | fn to_digits(value: u64, d: &Data) -> DecDigits<Self> { |
291 | 0 | to_digits_32(value, d) |
292 | 0 | } |
293 | | |
294 | | #[inline] |
295 | 0 | unsafe fn write_exp_float_simd( |
296 | 0 | buffer: *mut u8, |
297 | 0 | dig: &DecDigits<Self>, |
298 | 0 | last_digit: i32, |
299 | 0 | has_last_digit: bool, |
300 | 0 | has_extra_digit: bool, |
301 | 0 | exp_data: u64, |
302 | 0 | d: &Data, |
303 | 0 | ) -> *mut u8 { |
304 | | unsafe { |
305 | 0 | write_exp_float_simd_32( |
306 | 0 | buffer, |
307 | 0 | dig, |
308 | 0 | last_digit, |
309 | 0 | has_last_digit, |
310 | 0 | has_extra_digit, |
311 | 0 | exp_data, |
312 | 0 | d, |
313 | | ) |
314 | | } |
315 | 0 | } |
316 | | } |
317 | | |
318 | | impl FloatTraits for f64 { |
319 | | // Upstream uses -4..=15. |
320 | | const FIXED_DEC_EXP: RangeInclusive<i32> = -5..=15; |
321 | | |
322 | | const NUM_BITS: i32 = 64; |
323 | | const IMPLICIT_BIT: u64 = 1 << Self::NUM_SIG_BITS; |
324 | | |
325 | | type SigType = u64; |
326 | | |
327 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
328 | | type DecDigitsType = uint16x8_t; |
329 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
330 | | type DecDigitsType = __m128i; |
331 | | #[cfg(not(any( |
332 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
333 | | all(target_arch = "x86_64", target_feature = "sse2", not(miri)), |
334 | | )))] |
335 | | type DecDigitsType = [u64; 2]; |
336 | | |
337 | | #[cfg(any( |
338 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
339 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
340 | | ))] |
341 | | type DecUnshuffledType = (); |
342 | | |
343 | | #[inline] |
344 | 0 | fn to_bits(self) -> Self::SigType { |
345 | 0 | self.to_bits() |
346 | 0 | } |
347 | | |
348 | | #[inline] |
349 | 0 | fn to_digits(value: u64, d: &Data) -> DecDigits<Self> { |
350 | 0 | to_digits_64(value, d) |
351 | 0 | } |
352 | | |
353 | | #[inline] |
354 | 0 | unsafe fn write_exp_float_simd( |
355 | 0 | _buffer: *mut u8, |
356 | 0 | _dig: &DecDigits<Self>, |
357 | 0 | _last_digit: i32, |
358 | 0 | _has_last_digit: bool, |
359 | 0 | _has_extra_digit: bool, |
360 | 0 | _exp_data: u64, |
361 | 0 | _d: &Data, |
362 | 0 | ) -> *mut u8 { |
363 | 0 | ptr::null_mut() |
364 | 0 | } |
365 | | } |
366 | | |
367 | | #[rustfmt::skip] |
368 | | const POW10_MINOR: [u64; 28] = [ |
369 | | 0x8000000000000000, 0xa000000000000000, 0xc800000000000000, |
370 | | 0xfa00000000000000, 0x9c40000000000000, 0xc350000000000000, |
371 | | 0xf424000000000000, 0x9896800000000000, 0xbebc200000000000, |
372 | | 0xee6b280000000000, 0x9502f90000000000, 0xba43b74000000000, |
373 | | 0xe8d4a51000000000, 0x9184e72a00000000, 0xb5e620f480000000, |
374 | | 0xe35fa931a0000000, 0x8e1bc9bf04000000, 0xb1a2bc2ec5000000, |
375 | | 0xde0b6b3a76400000, 0x8ac7230489e80000, 0xad78ebc5ac620000, |
376 | | 0xd8d726b7177a8000, 0x878678326eac9000, 0xa968163f0a57b400, |
377 | | 0xd3c21bcecceda100, 0x84595161401484a0, 0xa56fa5b99019a5c8, |
378 | | 0xcecb8f27f4200f3a, |
379 | | ]; |
380 | | |
381 | | #[rustfmt::skip] |
382 | | const POW10_MAJOR: [uint128; 23] = [ |
383 | | uint128 { hi: 0xaf8e5410288e1b6f, lo: 0x07ecf0ae5ee44dda }, // -303 |
384 | | uint128 { hi: 0xb1442798f49ffb4a, lo: 0x99cd11cfdf41779d }, // -275 |
385 | | uint128 { hi: 0xb2fe3f0b8599ef07, lo: 0x861fa7e6dcb4aa15 }, // -247 |
386 | | uint128 { hi: 0xb4bca50b065abe63, lo: 0x0fed077a756b53aa }, // -219 |
387 | | uint128 { hi: 0xb67f6455292cbf08, lo: 0x1a3bc84c17b1d543 }, // -191 |
388 | | uint128 { hi: 0xb84687c269ef3bfb, lo: 0x3d5d514f40eea742 }, // -163 |
389 | | uint128 { hi: 0xba121a4650e4ddeb, lo: 0x92f34d62616ce413 }, // -135 |
390 | | uint128 { hi: 0xbbe226efb628afea, lo: 0x890489f70a55368c }, // -107 |
391 | | uint128 { hi: 0xbdb6b8e905cb600f, lo: 0x5400e987bbc1c921 }, // -79 |
392 | | uint128 { hi: 0xbf8fdb78849a5f96, lo: 0xde98520472bdd034 }, // -51 |
393 | | uint128 { hi: 0xc16d9a0095928a27, lo: 0x75b7053c0f178294 }, // -23 |
394 | | uint128 { hi: 0xc350000000000000, lo: 0x0000000000000000 }, // 5 |
395 | | uint128 { hi: 0xc5371912364ce305, lo: 0x6c28000000000000 }, // 33 |
396 | | uint128 { hi: 0xc722f0ef9d80aad6, lo: 0x424d3ad2b7b97ef6 }, // 61 |
397 | | uint128 { hi: 0xc913936dd571c84c, lo: 0x03bc3a19cd1e38ea }, // 89 |
398 | | uint128 { hi: 0xcb090c8001ab551c, lo: 0x5cadf5bfd3072cc6 }, // 117 |
399 | | uint128 { hi: 0xcd036837130890a1, lo: 0x36dba887c37a8c10 }, // 145 |
400 | | uint128 { hi: 0xcf02b2c21207ef2e, lo: 0x94f967e45e03f4bc }, // 173 |
401 | | uint128 { hi: 0xd106f86e69d785c7, lo: 0xe13336d701beba52 }, // 201 |
402 | | uint128 { hi: 0xd31045a8341ca07c, lo: 0x1ede48111209a051 }, // 229 |
403 | | uint128 { hi: 0xd51ea6fa85785631, lo: 0x552a74227f3ea566 }, // 257 |
404 | | uint128 { hi: 0xd732290fbacaf133, lo: 0xa97c177947ad4096 }, // 285 |
405 | | uint128 { hi: 0xd94ad8b1c7380874, lo: 0x18375281ae7822bc }, // 313 |
406 | | ]; |
407 | | |
408 | | #[rustfmt::skip] |
409 | | const POW10_FIXUPS: [u32; 20] = [ |
410 | | 0x0a4e363f, 0x00001840, 0x00006400, 0x24200040, 0x00000000, |
411 | | 0x0c000000, 0x82c81380, 0x5e4ce01f, 0xd730f60f, 0x0000001b, |
412 | | 0x00000000, 0xcdf7fffc, 0x6e8201d8, 0x40cd3fd1, 0xdb642501, |
413 | | 0x00000d0d, 0x14042400, 0x53713840, 0x11781db4, 0x00000000, |
414 | | ]; |
415 | | |
416 | | // 128-bit significands of powers of 10 rounded down. |
417 | | #[repr(C, align(64))] |
418 | | struct Pow10SignificandTable { |
419 | | data: [u64; if Self::COMPRESS { |
420 | | 0 |
421 | | } else { |
422 | | Self::NUM_POW10S * 2 |
423 | | }], |
424 | | } |
425 | | |
426 | | impl Pow10SignificandTable { |
427 | | const COMPRESS: bool = cfg!(opt_level = "s"); |
428 | | const SPLIT_TABLES: bool = !Self::COMPRESS && cfg!(target_arch = "aarch64"); |
429 | | const NUM_POW10S: usize = 618; |
430 | | |
431 | | // Computes the 128-bit significand of 10**i using method by Dougall Johnson. |
432 | | #[inline] |
433 | 0 | const fn compute(i: u32) -> uint128 { |
434 | | const STRIDE: u32 = POW10_MINOR.len() as u32; |
435 | 0 | let m = unsafe { *POW10_MINOR.as_ptr().add(((i + 10) % STRIDE) as usize) }; |
436 | 0 | let h = unsafe { *POW10_MAJOR.as_ptr().add(((i + 10) / STRIDE) as usize) }; |
437 | | |
438 | 0 | let h1 = umul128_hi64(h.lo, m); |
439 | | |
440 | 0 | let c0 = h.lo.wrapping_mul(m); |
441 | 0 | let c1 = h1.wrapping_add(h.hi.wrapping_mul(m)); |
442 | 0 | let c2 = (c1 < h1) as u64 + umul128_hi64(h.hi, m); |
443 | | |
444 | 0 | let mut result = if (c2 >> 63) != 0 { |
445 | 0 | uint128 { hi: c2, lo: c1 } |
446 | | } else { |
447 | 0 | uint128 { |
448 | 0 | hi: (c2 << 1) | (c1 >> 63), |
449 | 0 | lo: (c1 << 1) | (c0 >> 63), |
450 | 0 | } |
451 | | }; |
452 | 0 | result.lo -= |
453 | 0 | ((unsafe { *POW10_FIXUPS.as_ptr().add((i >> 5) as usize) } >> (i & 31)) & 1) as u64; |
454 | 0 | result |
455 | 0 | } |
456 | | |
457 | 0 | const fn new() -> Self { |
458 | 0 | let mut data = [0; if Self::COMPRESS { |
459 | 0 | 0 |
460 | 0 | } else { |
461 | 0 | Self::NUM_POW10S * 2 |
462 | 0 | }]; |
463 | | |
464 | 0 | let mut i = 0; |
465 | 0 | while i < Self::NUM_POW10S && !Self::COMPRESS { |
466 | 0 | let result = Self::compute(i as u32); |
467 | 0 | if Self::SPLIT_TABLES { |
468 | 0 | data[Self::NUM_POW10S - i - 1] = result.hi; |
469 | 0 | data[Self::NUM_POW10S * 2 - i - 1] = result.lo; |
470 | 0 | } else { |
471 | 0 | data[i * 2] = result.hi; |
472 | 0 | data[i * 2 + 1] = result.lo; |
473 | 0 | } |
474 | 0 | i += 1; |
475 | | } |
476 | | |
477 | 0 | Pow10SignificandTable { data } |
478 | 0 | } |
479 | | |
480 | | #[inline] |
481 | 0 | unsafe fn get_unchecked(&self, dec_exp: i32) -> uint128 { |
482 | | const DEC_EXP_MIN: i32 = -293; |
483 | 0 | let i = dec_exp - DEC_EXP_MIN; |
484 | 0 | if Self::COMPRESS { |
485 | 0 | return Self::compute(i as u32); |
486 | 0 | } |
487 | 0 | if !Self::SPLIT_TABLES { |
488 | 0 | let p = unsafe { self.data.as_ptr().add((i * 2) as usize) }; |
489 | 0 | return uint128 { |
490 | 0 | hi: unsafe { *p }, |
491 | 0 | lo: unsafe { *p.add(1) }, |
492 | 0 | }; |
493 | 0 | } |
494 | | |
495 | | unsafe { |
496 | | // The caller passes -e - 1 as dec_exp, so ~dec_exp recovers e. |
497 | | // Picking the base so that e itself is the index lets both loads |
498 | | // share sxtw addressing. |
499 | | #[cfg_attr( |
500 | | not(all(any(target_arch = "x86_64", target_arch = "aarch64"), not(miri))), |
501 | | allow(unused_mut) |
502 | | )] |
503 | 0 | let mut p = self |
504 | 0 | .data |
505 | 0 | .as_ptr() |
506 | 0 | .offset(Self::NUM_POW10S as isize + DEC_EXP_MIN as isize); |
507 | | #[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64"), not(miri)))] |
508 | 0 | asm!("/*{0}*/", inout(reg) p); |
509 | 0 | uint128 { |
510 | 0 | hi: *p.offset(!(dec_exp as isize)), |
511 | 0 | lo: *p.offset(!(dec_exp as isize) + Self::NUM_POW10S as isize), |
512 | 0 | } |
513 | | } |
514 | 0 | } |
515 | | |
516 | | #[cfg(test)] |
517 | | fn get(&self, dec_exp: i32) -> uint128 { |
518 | | const DEC_EXP_MIN: i32 = -292; |
519 | | assert!((DEC_EXP_MIN..DEC_EXP_MIN + Self::NUM_POW10S as i32).contains(&dec_exp)); |
520 | | unsafe { self.get_unchecked(dec_exp) } |
521 | | } |
522 | | } |
523 | | |
524 | | // Computes a shift so that, after scaling by a power of 10, the intermediate |
525 | | // result always has a fixed 128-bit fractional part (for double). |
526 | | // |
527 | | // Different binary exponents can map to the same decimal exponent, but place |
528 | | // the decimal point at different bit positions. The shift compensates for this. |
529 | | // |
530 | | // For example, both 3 * 2**59 and 3 * 2**60 have dec_exp = 2, but dividing by |
531 | | // 10^dec_exp puts the decimal point in different bit positions: |
532 | | // 3 * 2**59 / 100 = 1.72...e+16 (needs shift = 1 + 1) |
533 | | // 3 * 2**60 / 100 = 3.45...e+16 (needs shift = 2 + 1) |
534 | | #[inline] |
535 | 0 | const fn compute_exp_shift(bin_exp: i32, dec_exp: i32) -> u8 { |
536 | 0 | debug_assert!(dec_exp >= -350 && dec_exp <= 350); |
537 | | // log2_pow10_sig = round(log2(10) * 2**log2_pow10_exp) + 1 |
538 | | const LOG2_POW10_SIG: i32 = 217_707; |
539 | | const LOG2_POW10_EXP: i32 = 16; |
540 | | // pow10_bin_exp = floor(log2(10**-dec_exp)) |
541 | 0 | let pow10_bin_exp = (-dec_exp * LOG2_POW10_SIG) >> LOG2_POW10_EXP; |
542 | | // pow10 = ((pow10_hi << 64) | pow10_lo) * 2**(pow10_bin_exp - 127) |
543 | 0 | (bin_exp + pow10_bin_exp + 1) as u8 |
544 | 0 | } |
545 | | |
546 | | struct ExpShiftTable { |
547 | | data: [u8; if Self::ENABLE { |
548 | | f64::EXP_MASK as usize + 1 |
549 | | } else { |
550 | | 0 |
551 | | }], |
552 | | } |
553 | | |
554 | | impl ExpShiftTable { |
555 | | const ENABLE: bool = cfg!(not(opt_level = "s")); |
556 | | // extra_shift must be >= 3 to keep shift non-negative and <= 11 to fit the |
557 | | // significand into 64 bits after the shift. |
558 | | const EXTRA_SHIFT: usize = 6; |
559 | | |
560 | 0 | const fn new() -> Self { |
561 | 0 | let mut data = [0u8; if Self::ENABLE { |
562 | 0 | f64::EXP_MASK as usize + 1 |
563 | 0 | } else { |
564 | 0 | 0 |
565 | 0 | }]; |
566 | | |
567 | 0 | let mut raw_exp = 0; |
568 | 0 | while raw_exp < data.len() && Self::ENABLE { |
569 | 0 | let mut bin_exp = raw_exp as i32 - f64::EXP_OFFSET; |
570 | 0 | if raw_exp == 0 { |
571 | 0 | bin_exp += 1; |
572 | 0 | } |
573 | 0 | let dec_exp = compute_dec_exp(bin_exp, true); |
574 | 0 | data[raw_exp] = |
575 | 0 | compute_exp_shift(bin_exp, dec_exp + 1).wrapping_add(Self::EXTRA_SHIFT as u8); |
576 | 0 | raw_exp += 1; |
577 | | } |
578 | | |
579 | 0 | ExpShiftTable { data } |
580 | 0 | } |
581 | | } |
582 | | |
583 | | // An optional table of precomputed exponent strings for exponential notation. |
584 | | // Each entry packs "e+dd" or "e+ddd" into a u64 with the length in byte 7. |
585 | | struct ExpStringTable { |
586 | | data: [u64; if Self::ENABLE { |
587 | | (f64::MAX_10_EXP - Self::MIN_DEC_EXP + 1) as usize |
588 | | } else { |
589 | | 0 |
590 | | }], |
591 | | } |
592 | | |
593 | | impl ExpStringTable { |
594 | | const ENABLE: bool = cfg!(not(opt_level = "s")); |
595 | | const MIN_DEC_EXP: i32 = f64::MIN_10_EXP - f64::MAX_DIGITS10 as i32; |
596 | | const OFFSET: i32 = -Self::MIN_DEC_EXP; |
597 | | |
598 | 0 | const fn new() -> Self { |
599 | 0 | let mut data = [0u64; if Self::ENABLE { |
600 | 0 | (f64::MAX_10_EXP - Self::MIN_DEC_EXP + 1) as usize |
601 | 0 | } else { |
602 | 0 | 0 |
603 | 0 | }]; |
604 | | |
605 | 0 | let mut e = Self::MIN_DEC_EXP; |
606 | 0 | while e <= f64::MAX_10_EXP && Self::ENABLE { |
607 | 0 | let abs_e = e.unsigned_abs() as u64; |
608 | 0 | let mut val = abs_e % 10 + b'0' as u64; |
609 | 0 | if abs_e >= 10 { |
610 | 0 | val = (val << 8) | (abs_e / 10 % 10 + b'0' as u64); |
611 | 0 | } |
612 | 0 | if abs_e >= 100 { |
613 | 0 | val = (val << 8) | (abs_e / 100 + b'0' as u64); |
614 | 0 | } |
615 | 0 | let len = 3 + (abs_e >= 10) as u64 + (abs_e >= 100) as u64; |
616 | 0 | data[(e + Self::OFFSET) as usize] = (len << 48) |
617 | 0 | | (val << 16) |
618 | 0 | | (if e >= 0 { b'+' as u64 } else { b'-' as u64 } << 8) |
619 | 0 | | b'e' as u64; |
620 | 0 | e += 1; |
621 | | } |
622 | | |
623 | 0 | ExpStringTable { data } |
624 | 0 | } |
625 | | } |
626 | | |
627 | | // Shuffle vectors to build strings for exponential notation. |
628 | | // |
629 | | // Byte positions in the source register assembled by write_exp_float_simd: |
630 | | // bytes [0, exp_pos): BCD ASCII digits (reversed) |
631 | | // bytes [exp_pos, exp_pos + 4): exponent string "e±NN" |
632 | | // byte last_digit_pos: rounded last digit |
633 | | // byte point_pos: '.' |
634 | | // |
635 | | // The shuffle length (max 14) is stored in byte 15; the corresponding output |
636 | | // byte is past the string and ignored by the caller. |
637 | | #[repr(C, align(16))] |
638 | | struct ExpFloatShuffleTable { |
639 | | data: [u8; if Self::ENABLE { 32 * 16 } else { 0 }], |
640 | | } |
641 | | |
642 | | struct ExpFloatShuffleTableEntry { |
643 | | #[cfg_attr( |
644 | | not(any( |
645 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
646 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
647 | | )), |
648 | | allow(dead_code) |
649 | | )] |
650 | | shuffle: *const u8, |
651 | | length: u8, |
652 | | } |
653 | | |
654 | | impl ExpFloatShuffleTable { |
655 | | const ENABLE: bool = cfg!(any( |
656 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
657 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
658 | | )) && ExpStringTable::ENABLE; |
659 | | |
660 | | const EXP_POS: u8 = 8; |
661 | | const LAST_DIGIT_POS: u8 = 12; |
662 | | const POINT_POS: u8 = 13; |
663 | | |
664 | 0 | unsafe fn get_entry( |
665 | 0 | &self, |
666 | 0 | num_digits: i32, |
667 | 0 | has_last_digit: bool, |
668 | 0 | has_extra_digit: bool, |
669 | 0 | ) -> ExpFloatShuffleTableEntry { |
670 | 0 | let idx = (num_digits - 1) * 4 + i32::from(has_last_digit) * 2 + i32::from(has_extra_digit); |
671 | 0 | ExpFloatShuffleTableEntry { |
672 | 0 | shuffle: unsafe { self.data.as_ptr().add(idx as usize * 16) }, |
673 | 0 | length: *unsafe { self.data.get_unchecked(idx as usize * 16 + 15) }, |
674 | 0 | } |
675 | 0 | } |
676 | | |
677 | 0 | const fn new() -> Self { |
678 | 0 | let mut data = [0u8; if Self::ENABLE { 32 * 16 } else { 0 }]; |
679 | | |
680 | 0 | let mut idx = 0; |
681 | 0 | while idx < 32 && Self::ENABLE { |
682 | 0 | let num_digits = (idx >> 2) + 1; |
683 | 0 | let has_last_digit = ((idx >> 1) & 1) != 0; |
684 | 0 | let has_extra_digit = (idx & 1) != 0; |
685 | | |
686 | 0 | let out = idx * 16; |
687 | 0 | let mut i = 0; |
688 | 0 | while i < 16 { |
689 | 0 | data[out + i] = 0x80; // shuffle high bit: output 0 |
690 | 0 | i += 1; |
691 | 0 | } |
692 | 0 | let leading_digit_pos = if has_extra_digit { 7 } else { 6 }; |
693 | 0 | let mut length = 0; |
694 | 0 | if has_last_digit { |
695 | | // Always 8 BCD chars in the significand plus a last-digit char; |
696 | | // for !has_extra_digit the leading '0' of the 8-digit padded |
697 | | // BCD is shown. |
698 | 0 | data[out + length] = leading_digit_pos; |
699 | 0 | length += 1; |
700 | 0 | data[out + length] = Self::POINT_POS; |
701 | 0 | length += 1; |
702 | 0 | let mut i = leading_digit_pos - 1; |
703 | | loop { |
704 | 0 | data[out + length] = i; |
705 | 0 | length += 1; |
706 | 0 | if i == 0 { |
707 | 0 | break; |
708 | 0 | } |
709 | 0 | i -= 1; |
710 | | } |
711 | 0 | data[out + length] = Self::LAST_DIGIT_POS; |
712 | 0 | length += 1; |
713 | | } else { |
714 | 0 | length = num_digits + has_extra_digit as usize; |
715 | | // Drop the '.' for single-digit output: "5e+02", not "5.0e+02". |
716 | 0 | if length == 2 { |
717 | 0 | length = 1; |
718 | 0 | } |
719 | 0 | data[out] = leading_digit_pos; |
720 | 0 | data[out + 1] = Self::POINT_POS; |
721 | 0 | let mut i = 2; |
722 | 0 | while i < length { |
723 | 0 | data[out + i] = leading_digit_pos + 1 - i as u8; |
724 | 0 | i += 1; |
725 | 0 | } |
726 | | } |
727 | 0 | let mut i = 0; |
728 | 0 | while i < 4 { |
729 | 0 | data[out + length] = Self::EXP_POS + i; |
730 | 0 | length += 1; |
731 | 0 | i += 1; |
732 | 0 | } |
733 | 0 | data[out + 15] = length as u8; |
734 | 0 | idx += 1; |
735 | | } |
736 | | |
737 | 0 | ExpFloatShuffleTable { data } |
738 | 0 | } |
739 | | } |
740 | | |
741 | | #[cfg(any( |
742 | | not(any( |
743 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
744 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
745 | | )), |
746 | | all(test, target_endian = "little"), |
747 | | ))] |
748 | | #[cfg_attr(feature = "no-panic", no_panic)] |
749 | 0 | fn count_trailing_nonzeros(x: u64) -> usize { |
750 | | // We count the number of bytes until there are only zeros left. |
751 | | // The code is equivalent to |
752 | | // 8 - x.leading_zeros() / 8 |
753 | | // but if the BSR instruction is emitted (as gcc on x64 does with default |
754 | | // settings), subtracting the constant before dividing allows the compiler |
755 | | // to combine it with the subtraction which it inserts due to BSR counting |
756 | | // in the opposite direction. |
757 | | // |
758 | | // Additionally, the BSR instruction requires a zero check. Since the high |
759 | | // bit is unused we can avoid the zero check by shifting the datum left by |
760 | | // one and inserting a sentinel bit at the end. This can be faster than the |
761 | | // automatically inserted range check. |
762 | 0 | (70 - ((x.to_le() << 1) | 1).leading_zeros() as usize) / 8 |
763 | 0 | } |
764 | | |
765 | | // Align data since unaligned access may be slower when crossing a |
766 | | // hardware-specific boundary. |
767 | | #[repr(C, align(2))] |
768 | | struct Digits2([u8; 200]); |
769 | | |
770 | | static DIGITS2: Digits2 = Digits2( |
771 | | *b"0001020304050607080910111213141516171819\ |
772 | | 2021222324252627282930313233343536373839\ |
773 | | 4041424344454647484950515253545556575859\ |
774 | | 6061626364656667686970717273747576777879\ |
775 | | 8081828384858687888990919293949596979899", |
776 | | ); |
777 | | |
778 | | // Converts value in the range [0, 100) to a string. GCC generates a bit better |
779 | | // code when value is pointer-size (https://www.godbolt.org/z/5fEPMT1cc). |
780 | | #[cfg_attr(feature = "no-panic", no_panic)] |
781 | 0 | unsafe fn digits2(value: usize) -> &'static u16 { |
782 | 0 | debug_assert!(value < 100); |
783 | | |
784 | | #[allow(clippy::cast_ptr_alignment)] |
785 | | unsafe { |
786 | 0 | &*DIGITS2.0.as_ptr().cast::<u16>().add(value) |
787 | | } |
788 | 0 | } |
789 | | |
790 | | const DIV10K_EXP: i32 = 40; |
791 | | const DIV10K_SIG: u32 = ((1u64 << DIV10K_EXP) / 10000 + 1) as u32; |
792 | | const NEG10K: u32 = ((1u64 << 32) - 10000) as u32; |
793 | | |
794 | | const DIV100_EXP: i32 = 19; |
795 | | const DIV100_SIG: u32 = (1 << DIV100_EXP) / 100 + 1; |
796 | | #[cfg(not(all( |
797 | | target_arch = "x86_64", |
798 | | target_feature = "sse2", |
799 | | not(target_feature = "sse4.1"), |
800 | | not(miri) |
801 | | )))] |
802 | | const NEG100: u32 = (1 << 16) - 100; |
803 | | |
804 | | #[cfg(not(any( |
805 | | all(target_arch = "x86_64", target_feature = "sse2", not(miri)), |
806 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
807 | | )))] |
808 | | const DIV10_EXP: i32 = 10; |
809 | | #[cfg(not(any( |
810 | | all(target_arch = "x86_64", target_feature = "sse2", not(miri)), |
811 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
812 | | )))] |
813 | | const DIV10_SIG: u32 = (1 << DIV10_EXP) / 10 + 1; |
814 | | #[cfg(not(all(target_arch = "x86_64", target_feature = "sse2", not(miri))))] |
815 | | const NEG10: u32 = (1 << 8) - 10; |
816 | | |
817 | | const ZEROS: u64 = 0x0101010101010101 * b'0' as u64; |
818 | | |
819 | | #[repr(C, align(64))] |
820 | | struct Data { |
821 | | threshold: AArch64Mem<1_000_000_000_000_000>, |
822 | | // +6 is needed for boundary cases found by verify.py. |
823 | | biased_half: AArch64Mem<{ (1 << 63) + 6 }>, |
824 | | |
825 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
826 | | mul_const: u64, |
827 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
828 | | hundred_million: u64, |
829 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
830 | | multipliers32: int32x4_t, |
831 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
832 | | multipliers16: int16x8_t, |
833 | | |
834 | | // Ordered so that the values used to format floats fit in a single cache |
835 | | // line. |
836 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
837 | | div100: u128, |
838 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
839 | | div10: u128, |
840 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
841 | | neg100: u128, |
842 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
843 | | neg10: u128, |
844 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
845 | | bswap: u128, |
846 | | #[cfg(all( |
847 | | target_arch = "x86_64", |
848 | | target_feature = "sse2", |
849 | | not(target_feature = "sse4.1"), |
850 | | not(miri) |
851 | | ))] |
852 | | hundred: u128, |
853 | | #[cfg(all( |
854 | | target_arch = "x86_64", |
855 | | target_feature = "sse2", |
856 | | not(target_feature = "sse4.1"), |
857 | | not(miri) |
858 | | ))] |
859 | | moddiv10: u128, |
860 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
861 | | div10k: u128, |
862 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
863 | | neg10k: u128, |
864 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
865 | | zeros: u128, |
866 | | |
867 | | exp_shifts: ExpShiftTable, |
868 | | exp_strings: ExpStringTable, |
869 | | pow10_significands: Pow10SignificandTable, |
870 | | exp_float_shuffles: ExpFloatShuffleTable, |
871 | | } |
872 | | |
873 | | impl Data { |
874 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
875 | 0 | const fn splat64(x: u64) -> u128 { |
876 | 0 | ((x as u128) << 64) | x as u128 |
877 | 0 | } |
878 | | |
879 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
880 | 0 | const fn splat32(x: u32) -> u128 { |
881 | 0 | Self::splat64(((x as u64) << 32) | x as u64) |
882 | 0 | } |
883 | | |
884 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
885 | 0 | const fn splat16(x: u16) -> u128 { |
886 | 0 | Self::splat32(((x as u32) << 16) | x as u32) |
887 | 0 | } |
888 | | |
889 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
890 | | const fn pack8(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) -> u64 { |
891 | | ((h as u64) << 56) |
892 | | | ((g as u64) << 48) |
893 | | | ((f as u64) << 40) |
894 | | | ((e as u64) << 32) |
895 | | | ((d as u64) << 24) |
896 | | | ((c as u64) << 16) |
897 | | | ((b as u64) << 8) |
898 | | | a as u64 |
899 | | } |
900 | | |
901 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
902 | | const NEG10K: i32 = 0x10000 - 10000; |
903 | | } |
904 | | |
905 | | static STATIC_DATA: Data = Data { |
906 | | threshold: AArch64Mem::new(), |
907 | | biased_half: AArch64Mem::new(), |
908 | | |
909 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
910 | | mul_const: 0xabcc77118461cefd, |
911 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
912 | | hundred_million: 100000000, |
913 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
914 | | multipliers32: unsafe { |
915 | | mem::transmute::<[i32; 4], int32x4_t>([ |
916 | | DIV10K_SIG as i32, |
917 | | Data::NEG10K, |
918 | | (DIV100_SIG << 12) as i32, |
919 | | NEG100 as i32, |
920 | | ]) |
921 | | }, |
922 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
923 | | multipliers16: unsafe { |
924 | | mem::transmute::<[i16; 8], int16x8_t>([0xce0, NEG10 as i16, 0, 0, 0, 0, 0, 0]) |
925 | | }, |
926 | | |
927 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
928 | | div100: Data::splat32(DIV100_SIG), |
929 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
930 | | div10: Data::splat16(((1u32 << 16) / 10 + 1) as u16), |
931 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
932 | | neg100: Data::splat32(NEG100), |
933 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
934 | | neg10: Data::splat16((1 << 8) - 10), |
935 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
936 | | bswap: Data::pack8(15, 14, 13, 12, 11, 10, 9, 8) as u128 |
937 | | | (Data::pack8(7, 6, 5, 4, 3, 2, 1, 0) as u128) << 64, |
938 | | #[cfg(all( |
939 | | target_arch = "x86_64", |
940 | | target_feature = "sse2", |
941 | | not(target_feature = "sse4.1"), |
942 | | not(miri) |
943 | | ))] |
944 | | hundred: Data::splat32(100), |
945 | | #[cfg(all( |
946 | | target_arch = "x86_64", |
947 | | target_feature = "sse2", |
948 | | not(target_feature = "sse4.1"), |
949 | | not(miri) |
950 | | ))] |
951 | | moddiv10: Data::splat16(10 * (1 << 8) - 1), |
952 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
953 | | div10k: Data::splat64(DIV10K_SIG as u64), |
954 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
955 | | neg10k: Data::splat64(NEG10K as u64), |
956 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
957 | | zeros: Data::splat64(ZEROS), |
958 | | |
959 | | exp_shifts: ExpShiftTable::new(), |
960 | | exp_strings: ExpStringTable::new(), |
961 | | pow10_significands: Pow10SignificandTable::new(), |
962 | | exp_float_shuffles: ExpFloatShuffleTable::new(), |
963 | | }; |
964 | | |
965 | | // Converts four numbers < 10000, one in each 32-bit lane, to BCD digits. |
966 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
967 | | #[cfg_attr(feature = "no-panic", no_panic)] |
968 | | fn to_bcd_4x4(mut efgh_abcd_mnop_ijkl: int32x4_t, d: &Data) -> uint8x16_t { |
969 | | unsafe { |
970 | | // Compiler barrier, or clang breaks the subsequent MLA into UADDW + |
971 | | // MUL. |
972 | | asm!("/*{:v}*/", inout(vreg) efgh_abcd_mnop_ijkl); |
973 | | |
974 | | let ef_ab_mn_ij: int32x4_t = vqdmulhq_n_s32( |
975 | | efgh_abcd_mnop_ijkl, |
976 | | mem::transmute::<int32x4_t, [i32; 4]>(d.multipliers32)[2], |
977 | | ); |
978 | | let gh_ef_cd_ab_op_mn_kl_ij: int16x8_t = vreinterpretq_s16_s32(vmlaq_n_s32( |
979 | | efgh_abcd_mnop_ijkl, |
980 | | ef_ab_mn_ij, |
981 | | mem::transmute::<int32x4_t, [i32; 4]>(d.multipliers32)[3], |
982 | | )); |
983 | | let high_10s: int16x8_t = vqdmulhq_n_s16( |
984 | | gh_ef_cd_ab_op_mn_kl_ij, |
985 | | mem::transmute::<int16x8_t, [i16; 8]>(d.multipliers16)[0], |
986 | | ); |
987 | | vreinterpretq_u8_s16(vmlaq_n_s16( |
988 | | gh_ef_cd_ab_op_mn_kl_ij, |
989 | | high_10s, |
990 | | mem::transmute::<int16x8_t, [i16; 8]>(d.multipliers16)[1], |
991 | | )) |
992 | | } |
993 | | } |
994 | | |
995 | | // An optimized version for NEON by Dougall Johnson. |
996 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
997 | | #[cfg_attr(feature = "no-panic", no_panic)] |
998 | | #[inline] |
999 | | fn to_unshuffled_digits(value: u64, d: &Data) -> uint8x16_t { |
1000 | | let mut hundred_million = d.hundred_million; |
1001 | | |
1002 | | // Compiler barrier, or clang narrows the load to 32-bit and unpairs it. |
1003 | | unsafe { |
1004 | | asm!("/*{0}*/", inout(reg) hundred_million); |
1005 | | } |
1006 | | |
1007 | | // abcdefgh = value / 100000000, ijklmnop = value % 100000000. |
1008 | | let abcdefgh = (umul128(value, d.mul_const) >> 90) as u64; |
1009 | | let ijklmnop = value - abcdefgh * hundred_million; |
1010 | | |
1011 | | unsafe { |
1012 | | let ijklmnop_abcdefgh_64: uint64x1_t = |
1013 | | mem::transmute::<u64, uint64x1_t>((ijklmnop << 32) | abcdefgh); |
1014 | | let abcdefgh_ijklmnop: int32x2_t = vreinterpret_s32_u64(ijklmnop_abcdefgh_64); |
1015 | | |
1016 | | let abcd_ijkl: int32x2_t = vreinterpret_s32_u32(vshr_n_u32( |
1017 | | vreinterpret_u32_s32(vqdmulh_n_s32( |
1018 | | abcdefgh_ijklmnop, |
1019 | | mem::transmute::<int32x4_t, [i32; 4]>(d.multipliers32)[0], |
1020 | | )), |
1021 | | 9, |
1022 | | )); |
1023 | | let efgh_abcd_mnop_ijkl_32: int32x2_t = vmla_n_s32( |
1024 | | abcdefgh_ijklmnop, |
1025 | | abcd_ijkl, |
1026 | | mem::transmute::<int32x4_t, [i32; 4]>(d.multipliers32)[1], |
1027 | | ); |
1028 | | |
1029 | | let efgh_abcd_mnop_ijkl: int32x4_t = |
1030 | | vreinterpretq_s32_u32(vshll_n_u16(vreinterpret_u16_s32(efgh_abcd_mnop_ijkl_32), 0)); |
1031 | | |
1032 | | to_bcd_4x4(efgh_abcd_mnop_ijkl, d) |
1033 | | } |
1034 | | } |
1035 | | |
1036 | | // Converts four numbers < 10000, one in each 32-bit lane, to BCD digits. |
1037 | | // Digits in each 32-bit lane will be in order for SSE2, reversed for SSE4.1. |
1038 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
1039 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1040 | 0 | fn to_bcd_4x4(y: __m128i, d: &Data) -> __m128i { |
1041 | | unsafe { |
1042 | 0 | let div100 = _mm_load_si128(ptr::addr_of!(d.div100).cast::<__m128i>()); |
1043 | 0 | let div10 = _mm_load_si128(ptr::addr_of!(d.div10).cast::<__m128i>()); |
1044 | | |
1045 | | #[cfg(target_feature = "sse4.1")] |
1046 | | { |
1047 | | let neg100 = _mm_load_si128(ptr::addr_of!(d.neg100).cast::<__m128i>()); |
1048 | | let neg10 = _mm_load_si128(ptr::addr_of!(d.neg10).cast::<__m128i>()); |
1049 | | |
1050 | | // _mm_mullo_epi32 is SSE 4.1 |
1051 | | let z: __m128i = _mm_add_epi64( |
1052 | | y, |
1053 | | _mm_mullo_epi32(neg100, _mm_srli_epi32(_mm_mulhi_epu16(y, div100), 3)), |
1054 | | ); |
1055 | | _mm_add_epi64(z, _mm_mullo_epi16(neg10, _mm_mulhi_epu16(z, div10))) |
1056 | | } |
1057 | | |
1058 | | #[cfg(not(target_feature = "sse4.1"))] |
1059 | | { |
1060 | 0 | let hundred = _mm_load_si128(ptr::addr_of!(d.hundred).cast::<__m128i>()); |
1061 | 0 | let moddiv10 = _mm_load_si128(ptr::addr_of!(d.moddiv10).cast::<__m128i>()); |
1062 | | |
1063 | 0 | let y_div_100: __m128i = _mm_srli_epi16(_mm_mulhi_epu16(y, div100), 3); |
1064 | 0 | let y_mod_100: __m128i = _mm_sub_epi16(y, _mm_mullo_epi16(y_div_100, hundred)); |
1065 | 0 | let z: __m128i = _mm_or_si128(_mm_slli_epi32(y_mod_100, 16), y_div_100); |
1066 | 0 | _mm_sub_epi16( |
1067 | 0 | _mm_slli_epi16(z, 8), |
1068 | 0 | _mm_mullo_epi16(moddiv10, _mm_mulhi_epu16(z, div10)), |
1069 | | ) |
1070 | | } |
1071 | | } |
1072 | 0 | } |
1073 | | |
1074 | | #[cfg(not(any( |
1075 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
1076 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
1077 | | )))] |
1078 | | struct BcdResult { |
1079 | | bcd: u64, |
1080 | | len: usize, |
1081 | | } |
1082 | | |
1083 | | #[cfg(not(any( |
1084 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
1085 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
1086 | | )))] |
1087 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1088 | 0 | fn to_bcd8(abcdefgh: u64) -> BcdResult { |
1089 | | #[cfg(not(all(target_arch = "x86_64", target_feature = "sse2", not(miri))))] |
1090 | | let bcd = { |
1091 | | // An optimization from Xiang JunBo. |
1092 | | // Three steps BCD. Base 10000 -> base 100 -> base 10. |
1093 | | // div and mod are evaluated simultaneously as, e.g. |
1094 | | // (abcdefgh / 10000) << 32 + (abcdefgh % 10000) |
1095 | | // == abcdefgh + (2**32 - 10000) * (abcdefgh / 10000))) |
1096 | | // where the division on the RHS is implemented by the usual multiply + shift |
1097 | | // trick and the fractional bits are masked away. |
1098 | | let abcd_efgh = |
1099 | | abcdefgh + u64::from(NEG10K) * ((abcdefgh * u64::from(DIV10K_SIG)) >> DIV10K_EXP); |
1100 | | let ab_cd_ef_gh = abcd_efgh |
1101 | | + u64::from(NEG100) |
1102 | | * (((abcd_efgh * u64::from(DIV100_SIG)) >> DIV100_EXP) & 0x7f0000007f); |
1103 | | let a_b_c_d_e_f_g_h = ab_cd_ef_gh |
1104 | | + u64::from(NEG10) |
1105 | | * (((ab_cd_ef_gh * u64::from(DIV10_SIG)) >> DIV10_EXP) & 0xf000f000f000f); |
1106 | | a_b_c_d_e_f_g_h.to_be() |
1107 | | }; |
1108 | | |
1109 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
1110 | 0 | let bcd = { |
1111 | | // Load constants from memory. |
1112 | 0 | let mut d = ptr::addr_of!(STATIC_DATA); |
1113 | 0 | let d = unsafe { |
1114 | 0 | asm!("/*{0}*/", inout(reg) d); |
1115 | 0 | &*d |
1116 | | }; |
1117 | | |
1118 | | // Evaluate the 4-digit limbs and arrange them such that we get a |
1119 | | // result which is in the correct order. |
1120 | 0 | let abcd_efgh = (abcdefgh << 32) |
1121 | 0 | - ((10000u64 << 32) - 1) * ((abcdefgh * u64::from(DIV10K_SIG)) >> DIV10K_EXP); |
1122 | 0 | let v: __m128i = to_bcd_4x4(_mm_set_epi64x(0, abcd_efgh as i64), d); |
1123 | 0 | (unsafe { _mm_cvtsi128_si64(v) }) as u64 |
1124 | | }; |
1125 | | |
1126 | 0 | BcdResult { |
1127 | 0 | bcd, |
1128 | 0 | len: count_trailing_nonzeros(bcd), |
1129 | 0 | } |
1130 | 0 | } |
1131 | | |
1132 | | struct DecDigits<Float: FloatTraits> { |
1133 | | digits: Float::DecDigitsType, |
1134 | | // `unshuffled` is the byte-reversed BCD vector used by write_exp_float_simd. |
1135 | | #[cfg(any( |
1136 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
1137 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
1138 | | ))] |
1139 | | unshuffled: Float::DecUnshuffledType, |
1140 | | num_digits: usize, |
1141 | | } |
1142 | | |
1143 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1144 | | #[inline] |
1145 | 0 | fn to_digits_64(value: u64, #[allow(unused_variables)] d: &Data) -> DecDigits<f64> { |
1146 | | #[cfg(not(any( |
1147 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
1148 | | all(target_arch = "x86_64", target_feature = "sse2", not(miri)), |
1149 | | )))] |
1150 | | { |
1151 | | let hi = (value / 100_000_000) as u32; |
1152 | | let lo = (value % 100_000_000) as u32; |
1153 | | let hi_bcd = to_bcd8(hi as u64); |
1154 | | if lo == 0 { |
1155 | | return DecDigits { |
1156 | | digits: [hi_bcd.bcd + ZEROS, ZEROS], |
1157 | | num_digits: hi_bcd.len, |
1158 | | }; |
1159 | | } |
1160 | | let lo_bcd = to_bcd8(lo as u64); |
1161 | | DecDigits { |
1162 | | digits: [hi_bcd.bcd + ZEROS, lo_bcd.bcd + ZEROS], |
1163 | | num_digits: 8 + lo_bcd.len, |
1164 | | } |
1165 | | } |
1166 | | |
1167 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
1168 | | { |
1169 | | unsafe { |
1170 | | let unshuffled_digits = to_unshuffled_digits(value, d); |
1171 | | let digits: uint8x16_t = vrev64q_u8(unshuffled_digits); |
1172 | | let str: uint16x8_t = vaddq_u16( |
1173 | | vreinterpretq_u16_u8(digits), |
1174 | | vreinterpretq_u16_s8(vdupq_n_s8(b'0' as i8)), |
1175 | | ); |
1176 | | let is_not_zero: uint16x8_t = |
1177 | | vreinterpretq_u16_u8(vcgtzq_s8(vreinterpretq_s8_u8(digits))); |
1178 | | let nonzero_mask: u64 = |
1179 | | vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(is_not_zero, 4)), 0); |
1180 | | DecDigits { |
1181 | | digits: str, |
1182 | | unshuffled: (), |
1183 | | num_digits: 16 - (nonzero_mask.leading_zeros() as usize >> 2), |
1184 | | } |
1185 | | } |
1186 | | } |
1187 | | |
1188 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))] |
1189 | | { |
1190 | 0 | let hi = (value / 100_000_000) as u32; |
1191 | 0 | let lo = (value % 100_000_000) as u32; |
1192 | | |
1193 | | unsafe { |
1194 | 0 | let div10k = _mm_load_si128(ptr::addr_of!(d.div10k).cast::<__m128i>()); |
1195 | 0 | let neg10k = _mm_load_si128(ptr::addr_of!(d.neg10k).cast::<__m128i>()); |
1196 | 0 | let x: __m128i = _mm_set_epi64x(i64::from(hi), i64::from(lo)); |
1197 | | #[cfg_attr(target_feature = "sse4.1", allow(unused_mut))] |
1198 | 0 | let mut y: __m128i = _mm_add_epi64( |
1199 | 0 | x, |
1200 | 0 | _mm_mul_epu32(neg10k, _mm_srli_epi64(_mm_mul_epu32(x, div10k), DIV10K_EXP)), |
1201 | | ); |
1202 | | |
1203 | | // Shuffle to ensure correctly ordered result from SSE2 path. |
1204 | | #[cfg(not(target_feature = "sse4.1"))] |
1205 | 0 | { |
1206 | 0 | y = _mm_shuffle_epi32(y, _MM_SHUFFLE(0, 1, 2, 3)); |
1207 | 0 | } |
1208 | | |
1209 | | #[cfg_attr(not(target_feature = "sse4.1"), allow(unused_mut))] |
1210 | 0 | let mut bcd: __m128i = to_bcd_4x4(y, d); |
1211 | 0 | let zeros = _mm_load_si128(ptr::addr_of!(d.zeros).cast::<__m128i>()); |
1212 | | |
1213 | | // Computed against current bcd (rather than the post-bswap bcd) so |
1214 | | // the mask is derived in parallel with the shuffle on the SSE4.1 |
1215 | | // path. |
1216 | 0 | let mask = _mm_movemask_epi8(_mm_cmpgt_epi8(bcd, _mm_setzero_si128())) as u64; |
1217 | | // Trailing zeros are in the low bits for SSE4.1, the high bits for |
1218 | | // SSE2. |
1219 | 0 | let len = if cfg!(target_feature = "sse4.1") { |
1220 | 0 | 16 - mask.trailing_zeros() |
1221 | | } else { |
1222 | 0 | 64 - mask.leading_zeros() |
1223 | | }; |
1224 | | |
1225 | | #[cfg(target_feature = "sse4.1")] |
1226 | | { |
1227 | | bcd = _mm_shuffle_epi8( |
1228 | | bcd, |
1229 | | _mm_load_si128(ptr::addr_of!(d.bswap).cast::<__m128i>()), |
1230 | | ); // SSSE3 |
1231 | | } |
1232 | | |
1233 | 0 | DecDigits { |
1234 | 0 | digits: _mm_or_si128(bcd, zeros), |
1235 | 0 | #[cfg(target_feature = "sse4.1")] |
1236 | 0 | unshuffled: (), |
1237 | 0 | num_digits: len as usize, |
1238 | 0 | } |
1239 | | } |
1240 | | } |
1241 | 0 | } |
1242 | | |
1243 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1244 | | #[inline] |
1245 | 0 | fn to_digits_32(value: u64, #[allow(unused_variables)] d: &Data) -> DecDigits<f32> { |
1246 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
1247 | | { |
1248 | | // Inline to_bcd8's SSE4.1 body so we can return the unshuffled xmm too; |
1249 | | // the exponential-notation path uses it to skip the bswap-via-gpr. |
1250 | | let abcd_efgh = value + u64::from(NEG10K) * ((value * u64::from(DIV10K_SIG)) >> DIV10K_EXP); |
1251 | | let bcd_xmm = to_bcd_4x4(_mm_set_epi64x(0, abcd_efgh as i64), d); |
1252 | | let unshuffled_bcd = unsafe { _mm_cvtsi128_si64(bcd_xmm) } as u64; |
1253 | | let len = if unshuffled_bcd != 0 { |
1254 | | 8 - unshuffled_bcd.trailing_zeros() / 8 |
1255 | | } else { |
1256 | | 0 |
1257 | | }; |
1258 | | DecDigits { |
1259 | | digits: unshuffled_bcd.swap_bytes() + ZEROS, |
1260 | | unshuffled: bcd_xmm, |
1261 | | num_digits: len as usize, |
1262 | | } |
1263 | | } |
1264 | | |
1265 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
1266 | | { |
1267 | | // Inline to_bcd8's NEON body so we can return the unshuffled vector |
1268 | | // too; the exponential-notation path uses it to skip the |
1269 | | // simd->gpr->bswap->simd roundtrip needed to materialize `digits`. |
1270 | | let abcd_efgh = value + u64::from(NEG10K) * ((value * u64::from(DIV10K_SIG)) >> DIV10K_EXP); |
1271 | | let unshuffled: uint8x16_t = unsafe { |
1272 | | let input: int32x4_t = |
1273 | | vcombine_s32(vreinterpret_s32_u64(vcreate_u64(abcd_efgh)), vdup_n_s32(0)); |
1274 | | to_bcd_4x4(input, d) |
1275 | | }; |
1276 | | let unshuffled_bcd = |
1277 | | unsafe { vget_lane_u64(vreinterpret_u64_u8(vget_low_u8(unshuffled)), 0) }; |
1278 | | let len = if unshuffled_bcd != 0 { |
1279 | | 8 - unshuffled_bcd.trailing_zeros() / 8 |
1280 | | } else { |
1281 | | 0 |
1282 | | }; |
1283 | | DecDigits { |
1284 | | digits: unshuffled_bcd.swap_bytes() + ZEROS, |
1285 | | unshuffled, |
1286 | | num_digits: len as usize, |
1287 | | } |
1288 | | } |
1289 | | |
1290 | | #[cfg(not(any( |
1291 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
1292 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
1293 | | )))] |
1294 | | { |
1295 | 0 | let result = to_bcd8(value); |
1296 | 0 | DecDigits { |
1297 | 0 | digits: result.bcd + ZEROS, |
1298 | 0 | num_digits: result.len, |
1299 | 0 | } |
1300 | | } |
1301 | 0 | } |
1302 | | |
1303 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1304 | 0 | unsafe fn write_exp_float_simd_32( |
1305 | 0 | buffer: *mut u8, |
1306 | 0 | dig: &DecDigits<f32>, |
1307 | 0 | last_digit: i32, |
1308 | 0 | has_last_digit: bool, |
1309 | 0 | has_extra_digit: bool, |
1310 | 0 | exp_data: u64, |
1311 | 0 | d: &Data, |
1312 | 0 | ) -> *mut u8 { |
1313 | | // Packed for insertion into lane 1: byte 0 of `tail` lands at register byte |
1314 | | // exp_pos (8), so the exp string fills exp_pos..exp_pos+3; the prefix |
1315 | | // shifts place '0'+last_digit at last_digit_pos (12) and '.' at point_pos |
1316 | | // (13). |
1317 | 0 | let prefix = (u32::from(b'.') << 8) + u32::from(b'0') + last_digit as u32; |
1318 | | #[cfg_attr( |
1319 | | not(any( |
1320 | | all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)), |
1321 | | all(target_arch = "aarch64", target_feature = "neon", not(miri)), |
1322 | | )), |
1323 | | allow(unused_variables) |
1324 | | )] |
1325 | 0 | let tail = exp_data | (u64::from(prefix) << 32); |
1326 | 0 | let entry = unsafe { |
1327 | 0 | d.exp_float_shuffles |
1328 | 0 | .get_entry(dig.num_digits as i32, has_last_digit, has_extra_digit) |
1329 | | }; |
1330 | | |
1331 | | #[cfg(all(target_arch = "x86_64", target_feature = "sse4.1", not(miri)))] |
1332 | | unsafe { |
1333 | | let ascii: __m128i = _mm_or_si128( |
1334 | | dig.unshuffled, |
1335 | | _mm_load_si128(ptr::addr_of!(d.zeros).cast::<__m128i>()), |
1336 | | ); |
1337 | | let src: __m128i = _mm_insert_epi64(ascii, tail as i64, 1); |
1338 | | let shuffle: __m128i = _mm_load_si128(entry.shuffle.cast::<__m128i>()); |
1339 | | let out: __m128i = _mm_shuffle_epi8(src, shuffle); |
1340 | | _mm_storeu_si128(buffer.cast::<__m128i>(), out); |
1341 | | } |
1342 | | |
1343 | | #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))] |
1344 | | unsafe { |
1345 | | let ascii: uint8x16_t = vorrq_u8(dig.unshuffled, vdupq_n_u8(b'0')); |
1346 | | let src: uint8x16_t = |
1347 | | vreinterpretq_u8_u64(vsetq_lane_u64(tail, vreinterpretq_u64_u8(ascii), 1)); |
1348 | | let shuffle: uint8x16_t = vld1q_u8(entry.shuffle); |
1349 | | let out: uint8x16_t = vqtbl1q_u8(src, shuffle); |
1350 | | vst1q_u8(buffer, out); |
1351 | | } |
1352 | | |
1353 | 0 | let length = entry.length as usize - usize::from((exp_data & 0xff000000) == 0); |
1354 | 0 | unsafe { buffer.add(length) } |
1355 | 0 | } |
1356 | | |
1357 | | struct ToDecimalResult { |
1358 | | sig: i64, |
1359 | | exp: i32, |
1360 | | last_digit: u8, |
1361 | | has_last_digit: bool, |
1362 | | } |
1363 | | |
1364 | | // Here be 🐉s. |
1365 | | // Converts a binary FP number bin_sig * 2**bin_exp to the shortest decimal |
1366 | | // representation, where bin_exp = raw_exp - exp_offset. |
1367 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1368 | | #[inline] |
1369 | 0 | fn to_decimal<Float, UInt>(bin_sig: UInt, raw_exp: i64, regular: bool, d: &Data) -> ToDecimalResult |
1370 | 0 | where |
1371 | 0 | Float: FloatTraits, |
1372 | 0 | UInt: traits::UInt, |
1373 | | { |
1374 | 0 | let bin_exp = raw_exp - i64::from(Float::EXP_OFFSET); |
1375 | 0 | let num_bits = mem::size_of::<UInt>() as i32 * 8; |
1376 | | const EXTRA_SHIFT: usize = ExpShiftTable::EXTRA_SHIFT; |
1377 | | |
1378 | 0 | if !regular { |
1379 | 0 | let dec_exp = compute_dec_exp(bin_exp as i32, false); |
1380 | 0 | let shift = compute_exp_shift(bin_exp as i32, dec_exp + 1).wrapping_add(EXTRA_SHIFT as u8); |
1381 | 0 | let pow10 = unsafe { d.pow10_significands.get_unchecked(-dec_exp - 1) }; |
1382 | 0 | let p = umul192_hi128(pow10.hi, pow10.lo, (bin_sig << shift).into()); |
1383 | | |
1384 | 0 | let mut integral = p.hi >> EXTRA_SHIFT; |
1385 | 0 | let fractional = (p.hi << (64 - EXTRA_SHIFT)) | (p.lo >> EXTRA_SHIFT); |
1386 | | |
1387 | 0 | let half_ulp = pow10.hi >> (EXTRA_SHIFT + 1 - shift as usize); |
1388 | 0 | let round_up = half_ulp > u64::MAX - fractional; |
1389 | 0 | let round_down = (half_ulp >> 1) > fractional; |
1390 | 0 | integral += u64::from(round_up); |
1391 | | |
1392 | 0 | let mut digit = umul128_add_hi64(fractional, 10, (1 << 63) - 1) as i32; |
1393 | 0 | let lo = umul128_add_hi64(fractional.wrapping_sub(half_ulp >> 1), 10, !0) as i32; |
1394 | 0 | if digit < lo { |
1395 | 0 | digit = lo; |
1396 | 0 | } |
1397 | | return ToDecimalResult { |
1398 | 0 | sig: integral as i64, |
1399 | 0 | exp: dec_exp, |
1400 | 0 | last_digit: digit as u8, |
1401 | 0 | has_last_digit: !(round_up || round_down), |
1402 | | }; |
1403 | 0 | } |
1404 | | |
1405 | | const LOG10_2_SIG: u64 = 78_913; |
1406 | | const LOG10_2_EXP: i32 = 18; |
1407 | | #[allow(unused_mut)] |
1408 | 0 | let mut dec_exp = if USE_UMUL128_HI64 { |
1409 | 0 | umul128_hi64(bin_exp as u64, LOG10_2_SIG << (64 - LOG10_2_EXP)) as i32 |
1410 | | } else { |
1411 | 0 | compute_dec_exp(bin_exp as i32, true) |
1412 | | }; |
1413 | | #[cfg(not(miri))] |
1414 | | #[allow(unused_unsafe)] |
1415 | 0 | unsafe { |
1416 | 0 | // Force 32-bit reg for sxtw addressing. |
1417 | 0 | #[cfg(target_arch = "x86_64")] |
1418 | 0 | asm!("/*{0:e}*/", inout(reg) dec_exp); |
1419 | 0 | #[cfg(target_arch = "aarch64")] |
1420 | 0 | asm!("/*{0:w}*/", inout(reg) dec_exp); |
1421 | 0 | } |
1422 | 0 | let mut shift = if ExpShiftTable::ENABLE { |
1423 | 0 | *unsafe { |
1424 | 0 | d.exp_shifts |
1425 | 0 | .data |
1426 | 0 | .get_unchecked((bin_exp + i64::from(f64::EXP_OFFSET)) as usize) |
1427 | 0 | } |
1428 | | } else { |
1429 | 0 | compute_exp_shift(bin_exp as i32, dec_exp + 1).wrapping_add(EXTRA_SHIFT as u8) |
1430 | | }; |
1431 | 0 | let even = UInt::from(1) - (bin_sig & UInt::from(1)); |
1432 | | |
1433 | 0 | if num_bits == 32 { |
1434 | | const EXTRA_SHIFT: usize = 34; |
1435 | 0 | shift += (EXTRA_SHIFT - ExpShiftTable::EXTRA_SHIFT) as u8; |
1436 | 0 | let pow10_hi = unsafe { d.pow10_significands.get_unchecked(-dec_exp - 1) }.hi; |
1437 | 0 | let p = umul128_hi64(pow10_hi + 1, bin_sig.into() << shift); |
1438 | | |
1439 | 0 | let mut integral = p >> EXTRA_SHIFT; |
1440 | 0 | let fractional = p & ((1u64 << EXTRA_SHIFT) - 1); |
1441 | | |
1442 | 0 | let half_ulp = (pow10_hi >> (65 - shift as usize)) + even.into(); |
1443 | 0 | let round_up = ((fractional + half_ulp) >> EXTRA_SHIFT) != 0; |
1444 | 0 | let round_down = half_ulp > fractional; |
1445 | 0 | integral += u64::from(round_up); |
1446 | | |
1447 | 0 | let mut digit = ((fractional * 10 + (1u64 << (EXTRA_SHIFT - 1))) >> EXTRA_SHIFT) as i32; |
1448 | 0 | if fractional == (1u64 << (EXTRA_SHIFT - 2)) { |
1449 | 0 | digit = 2; // Round 2.5 to 2. |
1450 | 0 | } |
1451 | | return ToDecimalResult { |
1452 | 0 | sig: integral as i64, |
1453 | 0 | exp: dec_exp, |
1454 | 0 | last_digit: digit as u8, |
1455 | 0 | has_last_digit: !(round_up || round_down), |
1456 | | }; |
1457 | 0 | } |
1458 | | |
1459 | | // An optimization by Xiang JunBo: |
1460 | | // Scale by 10**(-dec_exp-1) to directly produce the shorter candidate |
1461 | | // (15-16 digits), deriving the extra digit from the fractional part. |
1462 | | // This eliminates div10 from the critical path. |
1463 | | // |
1464 | | // value = 5.0507837461e-27 |
1465 | | // next = 5.0507837461000010e-27 |
1466 | | // |
1467 | | // c = integral.fractional' = 5050783746100000.3153987... (value) |
1468 | | // 5050783746100001.0328635... (next) |
1469 | | // half_ulp = 0.3587324... |
1470 | | // |
1471 | | // fractional = fractional' * 2**64 = 5818079786399166407 |
1472 | | // |
1473 | | // 5050783746100000.0 c upper 5050783746100001.0 |
1474 | | // s l| L | S |
1475 | | // ──┬────┬────┼────┬────┬────┼*───┼────┬────┬───*┬────┬────┬────┼─*──┬─── |
1476 | | // .8 .9 .0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .0 | .1 |
1477 | | // └─────────────────┼─────────────────┘ next |
1478 | | // 1ulp |
1479 | | // |
1480 | | // s - shorter underestimate, S - shorter overestimate |
1481 | | // l - longer underestimate, L - longer overestimate |
1482 | 0 | let pow10 = unsafe { d.pow10_significands.get_unchecked(-dec_exp - 1) }; |
1483 | 0 | let p = umul192_hi128(pow10.hi, pow10.lo, (bin_sig << shift).into()); |
1484 | | |
1485 | 0 | let mut integral = p.hi >> EXTRA_SHIFT; |
1486 | 0 | let fractional = (p.hi << (64 - EXTRA_SHIFT)) | (p.lo >> EXTRA_SHIFT); |
1487 | | |
1488 | 0 | let half_ulp = (pow10.hi >> (EXTRA_SHIFT + 1 - shift as usize)) + even.into(); |
1489 | 0 | let round_up = fractional.wrapping_add(half_ulp) < fractional; |
1490 | 0 | let round_down = half_ulp > fractional; |
1491 | 0 | integral += u64::from(round_up); // Compute integral before digit. |
1492 | | |
1493 | | // Derive the extra digit from the fractional part (parallel with rounding). |
1494 | 0 | let mut digit = umul128_add_hi64(fractional, 10, d.biased_half.get()) as i32; |
1495 | 0 | if fractional == (1u64 << 62) { |
1496 | 0 | digit = 2; // Round 2.5 to 2. |
1497 | 0 | } |
1498 | | ToDecimalResult { |
1499 | 0 | sig: integral as i64, |
1500 | 0 | exp: dec_exp, |
1501 | 0 | last_digit: digit as u8, |
1502 | 0 | has_last_digit: !(round_up || round_down), |
1503 | | } |
1504 | 0 | } Unexecuted instantiation: zmij::to_decimal::<f64, u64> Unexecuted instantiation: zmij::to_decimal::<f32, u32> |
1505 | | |
1506 | | /// Writes the shortest correctly rounded decimal representation of `value` to |
1507 | | /// `buffer`. `buffer` should point to a buffer of size `buffer_size` or larger. |
1508 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1509 | 0 | unsafe fn write<Float>(value: Float, mut buffer: *mut u8) -> *mut u8 |
1510 | 0 | where |
1511 | 0 | Float: FloatTraits, |
1512 | | { |
1513 | 0 | let bits = value.to_bits(); |
1514 | | // It is beneficial to extract exponent and significand early. |
1515 | 0 | let bin_exp = Float::get_exp(bits); // binary exponent |
1516 | 0 | let bin_sig = Float::get_sig(bits); // binary significand |
1517 | | |
1518 | 0 | unsafe { |
1519 | 0 | *buffer = b'-'; |
1520 | 0 | } |
1521 | 0 | buffer = unsafe { buffer.add(usize::from(Float::is_negative(bits))) }; |
1522 | | |
1523 | | #[allow(unused_mut)] |
1524 | 0 | let mut d = ptr::addr_of!(STATIC_DATA); |
1525 | 0 | let d = unsafe { |
1526 | | // Load constants from memory. |
1527 | | #[cfg(all(any(target_arch = "aarch64", target_arch = "x86_64"), not(miri)))] |
1528 | 0 | asm!("/*{0}*/", inout(reg) d); |
1529 | 0 | &*d |
1530 | | }; |
1531 | 0 | let threshold = if Float::NUM_BITS == 64 { |
1532 | 0 | d.threshold.get() |
1533 | | } else { |
1534 | 0 | 10_000_000 |
1535 | | }; |
1536 | | |
1537 | | let mut dec; |
1538 | 0 | if bin_exp == 0 { |
1539 | 0 | if bin_sig == Float::SigType::from(0) { |
1540 | | return unsafe { |
1541 | 0 | *buffer = b'0'; |
1542 | 0 | *buffer.add(1) = b'.'; |
1543 | 0 | *buffer.add(2) = b'0'; |
1544 | 0 | buffer.add(3) |
1545 | | }; |
1546 | 0 | } |
1547 | 0 | dec = to_decimal::<Float, Float::SigType>(bin_sig, 1, true, d); |
1548 | 0 | let mut dec_sig = |
1549 | 0 | dec.sig * 10 + (-i64::from(dec.has_last_digit) & i64::from(dec.last_digit)); |
1550 | 0 | let mut dec_exp = dec.exp; |
1551 | 0 | while dec_sig < threshold as i64 { |
1552 | 0 | dec_sig *= 10; |
1553 | 0 | dec_exp -= 1; |
1554 | 0 | } |
1555 | 0 | let d = div10(dec_sig as u64); |
1556 | 0 | let last_digit = dec_sig - d as i64 * 10; |
1557 | 0 | dec = ToDecimalResult { |
1558 | 0 | sig: d as i64, |
1559 | 0 | exp: dec_exp, |
1560 | 0 | last_digit: last_digit as u8, |
1561 | 0 | has_last_digit: last_digit != 0, |
1562 | 0 | }; |
1563 | 0 | } else { |
1564 | 0 | dec = to_decimal::<Float, Float::SigType>( |
1565 | 0 | bin_sig | Float::IMPLICIT_BIT, |
1566 | 0 | bin_exp, |
1567 | 0 | bin_sig != Float::SigType::from(0), |
1568 | 0 | d, |
1569 | 0 | ); |
1570 | 0 | } |
1571 | 0 | let mut has_last_digit = dec.has_last_digit; |
1572 | 0 | let has_extra_digit = dec.sig >= threshold as i64; |
1573 | 0 | let mut dec_exp = dec.exp + Float::MAX_DIGITS10 as i32 - 2 + i32::from(has_extra_digit); |
1574 | 0 | if Float::NUM_BITS == 32 && dec.sig < 1_000_000 { |
1575 | 0 | dec.sig = 10 * dec.sig + (-i64::from(has_last_digit) & i64::from(dec.last_digit)); |
1576 | 0 | has_last_digit = false; |
1577 | 0 | dec_exp -= 1; |
1578 | 0 | } |
1579 | | |
1580 | | // Write significand. |
1581 | 0 | let dig = Float::to_digits(dec.sig as u64, d); |
1582 | | |
1583 | 0 | if Float::NUM_BITS == 32 |
1584 | 0 | && ExpFloatShuffleTable::ENABLE |
1585 | 0 | && !Float::FIXED_DEC_EXP.contains(&dec_exp) |
1586 | | { |
1587 | | unsafe { |
1588 | 0 | let exp_data = *d |
1589 | 0 | .exp_strings |
1590 | 0 | .data |
1591 | 0 | .get_unchecked((dec_exp + ExpStringTable::OFFSET) as usize); |
1592 | 0 | return Float::write_exp_float_simd( |
1593 | 0 | buffer, |
1594 | 0 | &dig, |
1595 | 0 | i32::from(dec.last_digit), |
1596 | 0 | has_last_digit, |
1597 | 0 | has_extra_digit, |
1598 | 0 | exp_data, |
1599 | 0 | d, |
1600 | | ); |
1601 | | } |
1602 | 0 | } |
1603 | | |
1604 | 0 | let bcd_size = if Float::NUM_BITS == 64 { 16 } else { 8 }; |
1605 | 0 | unsafe { |
1606 | 0 | buffer |
1607 | 0 | .add(usize::from(has_extra_digit)) |
1608 | 0 | .cast::<Float::DecDigitsType>() |
1609 | 0 | .write_unaligned(dig.digits); |
1610 | 0 | buffer |
1611 | 0 | .add(usize::from(has_extra_digit) + bcd_size) |
1612 | 0 | .write(b'0' + dec.last_digit); |
1613 | 0 | } |
1614 | 0 | let length = usize::from(has_extra_digit) |
1615 | 0 | + if has_last_digit { |
1616 | 0 | bcd_size + 1 |
1617 | | } else { |
1618 | 0 | dig.num_digits |
1619 | | } |
1620 | | - 1; |
1621 | | |
1622 | 0 | if Float::FIXED_DEC_EXP.contains(&dec_exp) { |
1623 | 0 | if length as i32 - 1 <= dec_exp { |
1624 | | // 1234e7 -> 12340000000.0 |
1625 | | return unsafe { |
1626 | 0 | ptr::copy(buffer.add(1), buffer, length); |
1627 | 0 | ptr::write_bytes(buffer.add(length), b'0', dec_exp as usize + 3 - length); |
1628 | 0 | *buffer.add(dec_exp as usize + 1) = b'.'; |
1629 | 0 | buffer.add(dec_exp as usize + 3) |
1630 | | }; |
1631 | 0 | } else if 0 <= dec_exp { |
1632 | | // 1234e-2 -> 12.34 |
1633 | | return unsafe { |
1634 | 0 | ptr::copy(buffer.add(1), buffer, dec_exp as usize + 1); |
1635 | 0 | *buffer.add(dec_exp as usize + 1) = b'.'; |
1636 | 0 | buffer.add(length + 1) |
1637 | | }; |
1638 | | } else { |
1639 | | // 1234e-6 -> 0.001234 |
1640 | | return unsafe { |
1641 | 0 | ptr::copy(buffer.add(1), buffer.add((1 - dec_exp) as usize), length); |
1642 | 0 | ptr::write_bytes(buffer, b'0', (1 - dec_exp) as usize); |
1643 | 0 | *buffer.add(1) = b'.'; |
1644 | 0 | buffer.add((1 - dec_exp) as usize + length) |
1645 | | }; |
1646 | | } |
1647 | 0 | } |
1648 | | |
1649 | 0 | unsafe { |
1650 | 0 | // 1234e30 -> 1.234e33 |
1651 | 0 | *buffer = *buffer.add(1); |
1652 | 0 | *buffer.add(1) = b'.'; |
1653 | 0 | } |
1654 | 0 | buffer = unsafe { buffer.add(length + usize::from(length > 1)) }; |
1655 | | |
1656 | | // Write exponent. |
1657 | 0 | if ExpStringTable::ENABLE { |
1658 | 0 | let mut exp_data = unsafe { |
1659 | 0 | *d.exp_strings |
1660 | 0 | .data |
1661 | 0 | .get_unchecked((dec_exp + ExpStringTable::OFFSET) as usize) |
1662 | | }; |
1663 | 0 | let len = (exp_data >> 48) as usize; |
1664 | 0 | exp_data = exp_data.to_le(); |
1665 | | unsafe { |
1666 | 0 | ptr::copy_nonoverlapping( |
1667 | 0 | ptr::addr_of!(exp_data).cast::<u8>(), |
1668 | 0 | buffer, |
1669 | 0 | if Float::MAX_10_EXP >= 100 { 5 } else { 4 }, |
1670 | | ); |
1671 | 0 | return buffer.add(len); |
1672 | | } |
1673 | 0 | } |
1674 | 0 | let sign_ptr = buffer; |
1675 | 0 | let e_sign = if dec_exp >= 0 { |
1676 | 0 | (u16::from(b'+') << 8) | u16::from(b'e') |
1677 | | } else { |
1678 | 0 | (u16::from(b'-') << 8) | u16::from(b'e') |
1679 | | }; |
1680 | 0 | buffer = unsafe { buffer.add(1) }; |
1681 | 0 | dec_exp = if dec_exp >= 0 { dec_exp } else { -dec_exp }; |
1682 | 0 | buffer = unsafe { buffer.add(usize::from(dec_exp >= 10)) }; |
1683 | 0 | if Float::MAX_10_EXP >= 100 { |
1684 | | // digit = dec_exp / 100 |
1685 | 0 | let digit = if USE_UMUL128_HI64 { |
1686 | 0 | umul128_hi64(dec_exp as u64, 0x290000000000000) as u32 |
1687 | | } else { |
1688 | 0 | (dec_exp as u32 * DIV100_SIG) >> DIV100_EXP |
1689 | | }; |
1690 | 0 | unsafe { |
1691 | 0 | *buffer = b'0' + digit as u8; |
1692 | 0 | } |
1693 | 0 | buffer = unsafe { buffer.add(usize::from(dec_exp >= 100)) }; |
1694 | 0 | dec_exp -= (digit * 100) as i32; |
1695 | 0 | } |
1696 | | unsafe { |
1697 | 0 | buffer |
1698 | 0 | .cast::<u16>() |
1699 | 0 | .write_unaligned(*digits2(dec_exp as usize)); |
1700 | 0 | sign_ptr.cast::<u16>().write_unaligned(e_sign.to_le()); |
1701 | 0 | buffer.add(2) |
1702 | | } |
1703 | 0 | } Unexecuted instantiation: zmij::write::<f64> Unexecuted instantiation: zmij::write::<f32> |
1704 | | |
1705 | | /// Safe API for formatting floating point numbers to text. |
1706 | | /// |
1707 | | /// ## Example |
1708 | | /// |
1709 | | /// ``` |
1710 | | /// let mut buffer = zmij::Buffer::new(); |
1711 | | /// let printed = buffer.format_finite(1.234); |
1712 | | /// assert_eq!(printed, "1.234"); |
1713 | | /// ``` |
1714 | | pub struct Buffer { |
1715 | | bytes: [MaybeUninit<u8>; BUFFER_SIZE], |
1716 | | } |
1717 | | |
1718 | | impl Buffer { |
1719 | | /// This is a cheap operation; you don't need to worry about reusing buffers |
1720 | | /// for efficiency. |
1721 | | #[inline] |
1722 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1723 | 0 | pub fn new() -> Self { |
1724 | 0 | let bytes = [MaybeUninit::<u8>::uninit(); BUFFER_SIZE]; |
1725 | 0 | Buffer { bytes } |
1726 | 0 | } Unexecuted instantiation: <zmij::Buffer>::new Unexecuted instantiation: <zmij::Buffer>::new |
1727 | | |
1728 | | /// Print a floating point number into this buffer and return a reference to |
1729 | | /// its string representation within the buffer. |
1730 | | /// |
1731 | | /// # Special cases |
1732 | | /// |
1733 | | /// This function formats NaN as the string "NaN", positive infinity as |
1734 | | /// "inf", and negative infinity as "-inf" to match std::fmt. |
1735 | | /// |
1736 | | /// If your input is known to be finite, you may get better performance by |
1737 | | /// calling the `format_finite` method instead of `format` to avoid the |
1738 | | /// checks for special cases. |
1739 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1740 | 0 | pub fn format<F: Float>(&mut self, f: F) -> &str { |
1741 | 0 | if f.is_nonfinite() { |
1742 | 0 | f.format_nonfinite() |
1743 | | } else { |
1744 | 0 | self.format_finite(f) |
1745 | | } |
1746 | 0 | } Unexecuted instantiation: <zmij::Buffer>::format::<f64> Unexecuted instantiation: <zmij::Buffer>::format::<_> |
1747 | | |
1748 | | /// Print a floating point number into this buffer and return a reference to |
1749 | | /// its string representation within the buffer. |
1750 | | /// |
1751 | | /// # Special cases |
1752 | | /// |
1753 | | /// This function **does not** check for NaN or infinity. If the input |
1754 | | /// number is not a finite float, the printed representation will be some |
1755 | | /// correctly formatted but unspecified numerical value. |
1756 | | /// |
1757 | | /// Please check [`is_finite`] yourself before calling this function, or |
1758 | | /// check [`is_nan`] and [`is_infinite`] and handle those cases yourself. |
1759 | | /// |
1760 | | /// [`is_finite`]: f64::is_finite |
1761 | | /// [`is_nan`]: f64::is_nan |
1762 | | /// [`is_infinite`]: f64::is_infinite |
1763 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1764 | 0 | pub fn format_finite<F: Float>(&mut self, f: F) -> &str { |
1765 | | unsafe { |
1766 | 0 | let end = f.write_to_zmij_buffer(self.bytes.as_mut_ptr().cast::<u8>()); |
1767 | 0 | let len = end.offset_from(self.bytes.as_ptr().cast::<u8>()) as usize; |
1768 | 0 | let slice = slice::from_raw_parts(self.bytes.as_ptr().cast::<u8>(), len); |
1769 | 0 | str::from_utf8_unchecked(slice) |
1770 | | } |
1771 | 0 | } Unexecuted instantiation: <zmij::Buffer>::format_finite::<f64> Unexecuted instantiation: <zmij::Buffer>::format_finite::<f32> Unexecuted instantiation: <zmij::Buffer>::format_finite::<_> |
1772 | | } |
1773 | | |
1774 | | /// A floating point number, f32 or f64, that can be written into a |
1775 | | /// [`zmij::Buffer`][Buffer]. |
1776 | | /// |
1777 | | /// This trait is sealed and cannot be implemented for types outside of the |
1778 | | /// `zmij` crate. |
1779 | | #[allow(unknown_lints)] // rustc older than 1.74 |
1780 | | #[allow(private_bounds)] |
1781 | | pub trait Float: private::Sealed {} |
1782 | | impl Float for f32 {} |
1783 | | impl Float for f64 {} |
1784 | | |
1785 | | mod private { |
1786 | | pub trait Sealed: crate::traits::Float { |
1787 | | fn is_nonfinite(self) -> bool; |
1788 | | fn format_nonfinite(self) -> &'static str; |
1789 | | unsafe fn write_to_zmij_buffer(self, buffer: *mut u8) -> *mut u8; |
1790 | | } |
1791 | | |
1792 | | impl Sealed for f32 { |
1793 | | #[inline] |
1794 | 0 | fn is_nonfinite(self) -> bool { |
1795 | | const EXP_MASK: u32 = 0x7f800000; |
1796 | 0 | let bits = self.to_bits(); |
1797 | 0 | bits & EXP_MASK == EXP_MASK |
1798 | 0 | } |
1799 | | |
1800 | | #[cold] |
1801 | | #[cfg_attr(feature = "no-panic", inline)] |
1802 | 0 | fn format_nonfinite(self) -> &'static str { |
1803 | | const MANTISSA_MASK: u32 = 0x007fffff; |
1804 | | const SIGN_MASK: u32 = 0x80000000; |
1805 | 0 | let bits = self.to_bits(); |
1806 | 0 | if bits & MANTISSA_MASK != 0 { |
1807 | 0 | crate::NAN |
1808 | 0 | } else if bits & SIGN_MASK != 0 { |
1809 | 0 | crate::NEG_INFINITY |
1810 | | } else { |
1811 | 0 | crate::INFINITY |
1812 | | } |
1813 | 0 | } |
1814 | | |
1815 | | #[cfg_attr(feature = "no-panic", inline)] |
1816 | 0 | unsafe fn write_to_zmij_buffer(self, buffer: *mut u8) -> *mut u8 { |
1817 | 0 | unsafe { crate::write(self, buffer) } |
1818 | 0 | } |
1819 | | } |
1820 | | |
1821 | | impl Sealed for f64 { |
1822 | | #[inline] |
1823 | 0 | fn is_nonfinite(self) -> bool { |
1824 | | const EXP_MASK: u64 = 0x7ff0000000000000; |
1825 | 0 | let bits = self.to_bits(); |
1826 | 0 | bits & EXP_MASK == EXP_MASK |
1827 | 0 | } Unexecuted instantiation: <f64 as zmij::private::Sealed>::is_nonfinite Unexecuted instantiation: <f64 as zmij::private::Sealed>::is_nonfinite |
1828 | | |
1829 | | #[cold] |
1830 | | #[cfg_attr(feature = "no-panic", inline)] |
1831 | 0 | fn format_nonfinite(self) -> &'static str { |
1832 | | const MANTISSA_MASK: u64 = 0x000fffffffffffff; |
1833 | | const SIGN_MASK: u64 = 0x8000000000000000; |
1834 | 0 | let bits = self.to_bits(); |
1835 | 0 | if bits & MANTISSA_MASK != 0 { |
1836 | 0 | crate::NAN |
1837 | 0 | } else if bits & SIGN_MASK != 0 { |
1838 | 0 | crate::NEG_INFINITY |
1839 | | } else { |
1840 | 0 | crate::INFINITY |
1841 | | } |
1842 | 0 | } |
1843 | | |
1844 | | #[cfg_attr(feature = "no-panic", inline)] |
1845 | 0 | unsafe fn write_to_zmij_buffer(self, buffer: *mut u8) -> *mut u8 { |
1846 | 0 | unsafe { crate::write(self, buffer) } |
1847 | 0 | } |
1848 | | } |
1849 | | } |
1850 | | |
1851 | | impl Default for Buffer { |
1852 | | #[inline] |
1853 | | #[cfg_attr(feature = "no-panic", no_panic)] |
1854 | 0 | fn default() -> Self { |
1855 | 0 | Buffer::new() |
1856 | 0 | } |
1857 | | } |