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