malachite_base/num/conversion/string/
to_sci.rs1use crate::num::arithmetic::traits::{CheckedLogBase2, NegAssign, Pow, UnsignedAbs};
10use crate::num::basic::integers::PrimitiveInt;
11use crate::num::basic::signeds::PrimitiveSigned;
12use crate::num::basic::unsigneds::PrimitiveUnsigned;
13use crate::num::conversion::string::options::{SciSizeOptions, ToSciOptions};
14use crate::num::conversion::string::to_string::BaseFmtWrapper;
15use crate::num::conversion::string::to_string::{
16 digit_to_display_byte_lower, digit_to_display_byte_upper,
17};
18use crate::num::conversion::traits::{ExactFrom, ToSci};
19use crate::rounding_modes::RoundingMode::*;
20use crate::slices::slice_trailing_zeros;
21use alloc::string::String;
22use core::fmt::{Display, Formatter, Write};
23
24pub struct SciWrapper<'a, T: ToSci> {
26 pub(crate) x: &'a T,
27 pub(crate) options: ToSciOptions,
28}
29
30impl<T: ToSci> Display for SciWrapper<'_, T> {
31 #[inline]
32 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
33 self.x.fmt_sci(f, self.options)
34 }
35}
36
37#[doc(hidden)]
38pub fn write_exponent<T: PrimitiveInt>(
39 f: &mut Formatter,
40 options: ToSciOptions,
41 exp: T,
42) -> core::fmt::Result {
43 f.write_char(if options.get_e_lowercase() { 'e' } else { 'E' })?;
44 if exp > T::ZERO && (options.get_force_exponent_plus_sign() || options.get_base() >= 15) {
45 f.write_char('+')?;
46 }
47 write!(f, "{exp}")
48}
49
50fn write_helper<T>(x: T, f: &mut Formatter, options: ToSciOptions) -> core::fmt::Result
51where
52 BaseFmtWrapper<T>: Display,
53{
54 let w = BaseFmtWrapper {
55 x,
56 base: options.base,
57 };
58 if options.lowercase {
59 Display::fmt(&w, f)
60 } else {
61 write!(f, "{w:#}")
62 }
63}
64
65fn fmt_sci_valid_unsigned<T: PrimitiveUnsigned>(x: T, options: ToSciOptions) -> bool {
66 if x == T::ZERO || options.rounding_mode != Exact {
67 return true;
68 }
69 match options.size_options {
70 SciSizeOptions::Complete | SciSizeOptions::Scale(_) => true,
71 SciSizeOptions::Precision(precision) => {
72 let t_base = T::from(options.base);
73 let log = x.floor_log_base(t_base);
74 if log < precision {
75 return true;
76 }
77 let neg_scale = log - precision + 1;
78 if let Some(base_log) = options.base.checked_log_base_2() {
79 x.divisible_by_power_of_2(base_log * neg_scale)
80 } else {
81 x.divisible_by(Pow::pow(t_base, neg_scale))
82 }
83 }
84 }
85}
86
87fn fmt_sci_unsigned<T: PrimitiveUnsigned>(
88 mut x: T,
89 f: &mut Formatter,
90 options: ToSciOptions,
91) -> core::fmt::Result
92where
93 BaseFmtWrapper<T>: Display,
94{
95 match options.size_options {
96 SciSizeOptions::Complete | SciSizeOptions::Scale(0) => write_helper(x, f, options),
97 SciSizeOptions::Scale(scale) => {
98 write_helper(x, f, options)?;
99 if options.include_trailing_zeros {
100 f.write_char('.')?;
101 for _ in 0..scale {
102 f.write_char('0')?;
103 }
104 }
105 Ok(())
106 }
107 SciSizeOptions::Precision(precision) => {
108 let t_base = T::from(options.base);
109 let log = if x == T::ZERO {
110 0
111 } else {
112 x.floor_log_base(t_base)
113 };
114 if log < precision {
115 write_helper(x, f, options)?;
117 if options.include_trailing_zeros {
118 let extra_zeros = precision - log - 1;
119 if extra_zeros != 0 {
120 f.write_char('.')?;
121 for _ in 0..extra_zeros {
122 f.write_char('0')?;
123 }
124 }
125 }
126 Ok(())
127 } else {
128 let mut e = log;
130 let neg_scale = log - precision + 1;
131 if let Some(base_log) = options.base.checked_log_base_2() {
132 x.shr_round_assign(base_log * neg_scale, options.rounding_mode);
133 } else {
134 x.div_round_assign(Pow::pow(t_base, neg_scale), options.rounding_mode);
135 }
136 let mut chars = x.to_digits_desc(&options.base);
137 let mut len = chars.len();
138 let p = usize::exact_from(precision);
139 if len > p {
140 chars.pop();
142 len -= 1;
143 e += 1;
144 }
145 assert_eq!(len, p);
146 if !options.include_trailing_zeros {
147 chars.truncate(len - slice_trailing_zeros(&chars));
148 }
149 if options.lowercase {
150 for digit in &mut chars {
151 *digit = digit_to_display_byte_lower(*digit).unwrap();
152 }
153 } else {
154 for digit in &mut chars {
155 *digit = digit_to_display_byte_upper(*digit).unwrap();
156 }
157 }
158 len = chars.len();
159 if len != 1 {
160 chars.push(b'0');
161 chars.copy_within(1..len, 2);
162 chars[1] = b'.';
163 }
164 f.write_str(&String::from_utf8(chars).unwrap())?;
165 write_exponent(f, options, e)
166 }
167 }
168 }
169}
170
171#[inline]
172fn fmt_sci_valid_signed<T: PrimitiveSigned>(x: T, options: ToSciOptions) -> bool
173where
174 <T as UnsignedAbs>::Output: PrimitiveUnsigned,
175{
176 fmt_sci_valid_unsigned(x.unsigned_abs(), options)
177}
178
179fn fmt_sci_signed<T: PrimitiveSigned>(
180 x: T,
181 f: &mut Formatter,
182 mut options: ToSciOptions,
183) -> core::fmt::Result
184where
185 <T as UnsignedAbs>::Output: PrimitiveUnsigned,
186{
187 let abs = x.unsigned_abs();
188 if x >= T::ZERO {
189 abs.fmt_sci(f, options)
190 } else {
191 options.rounding_mode.neg_assign();
192 f.write_char('-')?;
193 abs.fmt_sci(f, options)
194 }
195}
196
197macro_rules! impl_to_sci_unsigned {
198 ($t:ident) => {
199 impl ToSci for $t {
200 #[inline]
210 fn fmt_sci_valid(&self, options: ToSciOptions) -> bool {
211 fmt_sci_valid_unsigned(*self, options)
212 }
213
214 #[inline]
235 fn fmt_sci(&self, f: &mut Formatter, options: ToSciOptions) -> core::fmt::Result {
236 fmt_sci_unsigned(*self, f, options)
237 }
238 }
239 };
240}
241apply_to_unsigneds!(impl_to_sci_unsigned);
242
243macro_rules! impl_to_sci_signed {
244 ($t:ident) => {
245 impl ToSci for $t {
246 #[inline]
256 fn fmt_sci_valid(&self, options: ToSciOptions) -> bool {
257 fmt_sci_valid_signed(*self, options)
258 }
259
260 #[inline]
281 fn fmt_sci(&self, f: &mut Formatter, options: ToSciOptions) -> core::fmt::Result {
282 fmt_sci_signed(*self, f, options)
283 }
284 }
285 };
286}
287apply_to_signeds!(impl_to_sci_signed);