1use radix_fmt::Radix;
2use swc_atoms::atom;
3use swc_common::{util::take::Take, Spanned, SyntaxContext};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{
6 number::ToJsString,
7 unicode::{is_high_surrogate, is_low_surrogate},
8 ExprExt, IsEmpty, Type, Value,
9};
10
11use super::Pure;
12#[cfg(feature = "debug")]
13use crate::debug::dump;
14use crate::{
15 compress::{
16 pure::Ctx,
17 util::{eval_as_number, is_pure_undefined_or_null},
18 },
19 util::ValueExt,
20};
21
22impl Pure<'_> {
23 pub(super) fn optimize_lit_cmp(&mut self, n: &mut BinExpr) -> Option<Expr> {
27 if n.op != op!("==") && n.op != op!("!=") {
28 return None;
29 }
30 let flag = n.op == op!("!=");
31 let mut make_lit_bool = |value: bool| {
32 self.changed = true;
33 Some(
34 Lit::Bool(Bool {
35 span: n.span,
36 value: flag ^ value,
37 })
38 .into(),
39 )
40 };
41 match (
42 n.left.get_type(self.expr_ctx).opt()?,
43 n.right.get_type(self.expr_ctx).opt()?,
44 ) {
45 (lt, rt) if lt != rt => {}
47 (Type::Obj, Type::Obj) => {}
48 (Type::Num, Type::Num) => {
49 let l = n.left.as_pure_number(self.expr_ctx).opt()?;
50 let r = n.right.as_pure_number(self.expr_ctx).opt()?;
51 report_change!("Optimizing: literal comparison => num");
52 return make_lit_bool(l == r);
53 }
54 (Type::Str, Type::Str) => {
55 let l = &n.left.as_pure_string(self.expr_ctx).opt()?;
56 let r = &n.right.as_pure_string(self.expr_ctx).opt()?;
57 report_change!("Optimizing: literal comparison => str");
58 return make_lit_bool(l == r);
59 }
60 (_, _) => {
61 let l = n.left.as_pure_bool(self.expr_ctx).opt()?;
62 let r = n.right.as_pure_bool(self.expr_ctx).opt()?;
63 report_change!("Optimizing: literal comparison => bool");
64 return make_lit_bool(l == r);
65 }
66 };
67
68 None
69 }
70
71 pub(super) fn eval_array_spread(&mut self, e: &mut Expr) {
72 if !self.options.evaluate {
73 return;
74 }
75
76 let Expr::Array(ArrayLit { elems, .. }) = e else {
77 return;
78 };
79
80 if !elems.iter().any(|elem| match elem {
81 Some(ExprOrSpread {
82 spread: Some(..),
83 expr,
84 }) => expr.is_array(),
85 _ => false,
86 }) {
87 return;
88 }
89
90 report_change!("evaluate: Evaluated array spread");
91 self.changed = true;
92
93 let mut new_elems = Vec::with_capacity(elems.len());
94
95 for elem in elems.take() {
96 match elem {
97 Some(ExprOrSpread {
98 spread: Some(..),
99 expr,
100 }) if expr.is_array() => {
101 new_elems.extend(expr.expect_array().elems);
102 }
103 _ => {
104 new_elems.push(elem);
105 }
106 }
107 }
108
109 *elems = new_elems;
110 }
111
112 pub(super) fn eval_logical_expr(&mut self, e: &mut Expr) {
113 let Expr::Bin(
114 b @ BinExpr {
115 op: op!("||") | op!("&&"),
116 ..
117 },
118 ) = e
119 else {
120 return;
121 };
122
123 let (purity, lv) = b.left.cast_to_bool(self.expr_ctx);
124
125 if purity.is_pure() {
126 if let Value::Known(lv) = lv {
127 match (lv, b.op) {
128 (true, op!("||")) => {
129 self.changed = true;
130 report_change!("evaluate: `true || foo` => `true`");
131
132 *e = *b.left.take();
133 }
134 (false, op!("||")) => {
135 self.changed = true;
136 report_change!("evaluate: `false || foo` => `foo`");
137
138 *e = *b.right.take();
139 }
140 (true, op!("&&")) => {
141 self.changed = true;
142 report_change!("evaluate: `true && foo` => `foo`");
143
144 *e = *b.right.take();
145 }
146 (false, op!("&&")) => {
147 self.changed = true;
148 report_change!("evaluate: `false && foo` => `false`");
149
150 *e = *b.left.take();
151 }
152 _ => {}
153 }
154 }
155 } else {
156 if let Value::Known(lv) = lv {
157 match (lv, b.op) {
158 (true, op!("||")) => {
159 self.changed = true;
160 report_change!("evaluate: `truthy || foo` => `truthy`");
161 *e = *b.left.take();
162 }
163
164 (false, op!("&&")) => {
165 self.changed = true;
166 report_change!("evaluate: `falsy && foo` => `falsy`");
167 *e = *b.left.take();
168 }
169
170 (true, op!("&&")) => {
171 self.changed = true;
172 report_change!("evaluate: `truthy && foo` => `truthy, foo`");
173 *e = *Expr::from_exprs(vec![b.left.take(), b.right.take()]);
174 }
175
176 (false, op!("||")) => {
177 self.changed = true;
178 report_change!("evaluate: `falsy || foo` => `falsy, foo`");
179 *e = *Expr::from_exprs(vec![b.left.take(), b.right.take()]);
180 }
181
182 _ => {}
183 }
184 }
185 }
186 }
187
188 pub(super) fn eval_array_method_call(&mut self, e: &mut Expr) {
189 if !self.options.evaluate {
190 return;
191 }
192
193 if self.ctx.intersects(
194 Ctx::IN_DELETE
195 .union(Ctx::IS_UPDATE_ARG)
196 .union(Ctx::IS_LHS_OF_ASSIGN),
197 ) {
198 return;
199 }
200
201 let call = match e {
202 Expr::Call(e) => e,
203 _ => return,
204 };
205
206 let has_spread = call.args.iter().any(|arg| arg.spread.is_some());
207
208 for arg in &call.args {
209 if arg.expr.may_have_side_effects(self.expr_ctx) {
210 return;
211 }
212 }
213
214 let callee = match &mut call.callee {
215 Callee::Super(_) | Callee::Import(_) => return,
216 Callee::Expr(e) => &mut **e,
217 #[cfg(swc_ast_unknown)]
218 _ => panic!("unable to access unknown nodes"),
219 };
220
221 if let Expr::Member(MemberExpr {
222 span,
223 obj,
224 prop: MemberProp::Ident(method_name),
225 }) = callee
226 {
227 if obj.may_have_side_effects(self.expr_ctx) {
228 return;
229 }
230
231 let arr = match &mut **obj {
232 Expr::Array(arr) => arr,
233 _ => return,
234 };
235
236 let has_spread_elem = arr.elems.iter().any(|s| {
237 matches!(
238 s,
239 Some(ExprOrSpread {
240 spread: Some(..),
241 ..
242 })
243 )
244 });
245
246 if &*method_name.sym == "slice" {
249 if has_spread || has_spread_elem {
250 return;
251 }
252
253 match call.args.len() {
254 0 => {
255 self.changed = true;
256 report_change!("evaluate: Dropping array.slice call");
257 *e = *obj.take();
258 }
259 1 => {
260 if let Value::Known(start) = call.args[0].expr.as_pure_number(self.expr_ctx)
261 {
262 if start.is_sign_negative() {
263 return;
264 }
265
266 let start = start.floor() as usize;
267
268 self.changed = true;
269 report_change!("evaluate: Reducing array.slice({}) call", start);
270
271 if start >= arr.elems.len() {
272 *e = ArrayLit {
273 span: *span,
274 elems: Default::default(),
275 }
276 .into();
277 return;
278 }
279
280 let elems = arr.elems.drain(start..).collect();
281
282 *e = ArrayLit { span: *span, elems }.into();
283 }
284 }
285 _ => {
286 let start = call.args[0].expr.as_pure_number(self.expr_ctx);
287 let end = call.args[1].expr.as_pure_number(self.expr_ctx);
288 if let Value::Known(start) = start {
289 if start.is_sign_negative() {
290 return;
291 }
292
293 let start = start.floor() as usize;
294
295 if let Value::Known(end) = end {
296 if end.is_sign_negative() {
297 return;
298 }
299
300 let end = end.floor() as usize;
301 let end = end.min(arr.elems.len());
302
303 if start >= end {
304 return;
305 }
306
307 self.changed = true;
308 report_change!(
309 "evaluate: Reducing array.slice({}, {}) call",
310 start,
311 end
312 );
313 if start >= arr.elems.len() {
314 *e = ArrayLit {
315 span: *span,
316 elems: Default::default(),
317 }
318 .into();
319 return;
320 }
321
322 let elems = arr.elems.drain(start..end).collect();
323
324 *e = ArrayLit { span: *span, elems }.into();
325 }
326 }
327 }
328 }
329 return;
330 }
331
332 if self.options.unsafe_passes
333 && &*method_name.sym == "toString"
334 && arr.elems.len() == 1
335 && arr.elems[0].is_some()
336 {
337 report_change!("evaluate: Reducing array.toString() call");
338 self.changed = true;
339 *obj = arr.elems[0]
340 .take()
341 .map(|elem| elem.expr)
342 .unwrap_or_else(|| Expr::undefined(*span));
343 }
344 }
345 }
346
347 pub(super) fn eval_fn_method_call(&mut self, e: &mut Expr) {
348 if !self.options.evaluate {
349 return;
350 }
351
352 if self.ctx.intersects(
353 Ctx::IN_DELETE
354 .union(Ctx::IS_UPDATE_ARG)
355 .union(Ctx::IS_LHS_OF_ASSIGN),
356 ) {
357 return;
358 }
359
360 let call = match e {
361 Expr::Call(e) => e,
362 _ => return,
363 };
364
365 let has_spread = call.args.iter().any(|arg| arg.spread.is_some());
366
367 for arg in &call.args {
368 if arg.expr.may_have_side_effects(self.expr_ctx) {
369 return;
370 }
371 }
372
373 let callee = match &mut call.callee {
374 Callee::Super(_) | Callee::Import(_) => return,
375 Callee::Expr(e) => &mut **e,
376 #[cfg(swc_ast_unknown)]
377 _ => panic!("unable to access unknown nodes"),
378 };
379
380 if let Expr::Member(MemberExpr {
381 obj,
382 prop: MemberProp::Ident(method_name),
383 ..
384 }) = callee
385 {
386 if obj.may_have_side_effects(self.expr_ctx) {
387 return;
388 }
389
390 let f = match &mut **obj {
391 Expr::Fn(v) => v,
392 _ => return,
393 };
394
395 if &*method_name.sym == "valueOf" {
396 if has_spread {
397 return;
398 }
399
400 self.changed = true;
401 report_change!("evaluate: Reduced `function.valueOf()` into a function expression");
402
403 *e = *obj.take();
404 return;
405 }
406
407 if self.options.unsafe_passes
408 && &*method_name.sym == "toString"
409 && f.function.params.is_empty()
410 && f.function.body.is_empty()
411 {
412 if has_spread {
413 return;
414 }
415
416 self.changed = true;
417 report_change!("evaluate: Reduced `function.toString()` into a string");
418
419 *e = Str {
420 span: call.span,
421 value: atom!("function(){}").into(),
422 raw: None,
423 }
424 .into();
425 }
426 }
427 }
428
429 pub(super) fn eval_arguments_member_access(&mut self, e: &mut Expr) {
430 let member = match e {
431 Expr::Member(e) => e,
432 _ => return,
433 };
434
435 if !member.obj.is_ident_ref_to("arguments") {
436 return;
437 }
438
439 match &mut member.prop {
440 MemberProp::Ident(_) => {}
441 MemberProp::PrivateName(_) => {}
442 MemberProp::Computed(p) => {
443 if let Expr::Lit(Lit::Str(s)) = &*p.expr {
444 if let Some(value) = s.value.as_str() {
445 if let Ok(value) = value.parse::<u32>() {
446 p.expr = Lit::Num(Number {
447 span: s.span,
448 value: value as f64,
449 raw: None,
450 })
451 .into();
452 }
453 }
454 }
455 }
456 #[cfg(swc_ast_unknown)]
457 _ => panic!("unable to access unknown nodes"),
458 }
459 }
460
461 pub(super) fn eval_number_call(&mut self, e: &mut Expr) {
463 if self.options.unsafe_passes && self.options.unsafe_math {
464 if let Expr::Call(CallExpr {
465 span,
466 callee: Callee::Expr(callee),
467 args,
468 ..
469 }) = e
470 {
471 if args.len() == 1 && args[0].spread.is_none() {
472 if callee.is_ident_ref_to("Number") {
473 self.changed = true;
474 report_change!(
475 "evaluate: Reducing a call to `Number` into an unary operation"
476 );
477
478 *e = UnaryExpr {
479 span: *span,
480 op: op!(unary, "+"),
481 arg: args.take().into_iter().next().unwrap().expr,
482 }
483 .into();
484 }
485 }
486 }
487 }
488 }
489
490 pub(super) fn eval_number_method_call(&mut self, e: &mut Expr) {
492 if !self.options.evaluate {
493 return;
494 }
495
496 let (num, method, args) = match e {
497 Expr::Call(CallExpr {
498 callee: Callee::Expr(callee),
499 args,
500 ..
501 }) => match &mut **callee {
502 Expr::Member(MemberExpr {
503 obj,
504 prop: MemberProp::Ident(prop),
505 ..
506 }) => match &mut **obj {
507 Expr::Lit(Lit::Num(obj)) => (obj, prop, args),
508 _ => return,
509 },
510 _ => return,
511 },
512 _ => return,
513 };
514
515 if args
516 .iter()
517 .any(|arg| arg.expr.may_have_side_effects(self.expr_ctx))
518 {
519 return;
520 }
521
522 if &*method.sym == "toFixed" {
523 if let Some(precision) = args
539 .first()
540 .map_or(Some(0f64), |arg| eval_as_number(self.expr_ctx, &arg.expr))
542 {
543 let f = precision.trunc() as u8;
544
545 if !(0..=20).contains(&f) {
551 return;
552 }
553
554 let mut buffer = ryu_js::Buffer::new();
555 let value = buffer.format_to_fixed(num.value, f);
556
557 self.changed = true;
558 report_change!(
559 "evaluate: Evaluating `{}.toFixed({})` as `{}`",
560 num,
561 precision,
562 value
563 );
564
565 *e = Lit::Str(Str {
566 span: e.span(),
567 raw: None,
568 value: value.into(),
569 })
570 .into();
571 }
572
573 return;
574 }
575
576 if &*method.sym == "toPrecision" {
577 if args.is_empty() {
579 let value = num.value.to_js_string().into();
582
583 self.changed = true;
584 report_change!(
585 "evaluate: Evaluating `{}.toPrecision()` as `{}`",
586 num,
587 value
588 );
589
590 *e = Lit::Str(Str {
591 span: e.span(),
592 raw: None,
593 value,
594 })
595 .into();
596 return;
597 }
598
599 if let Some(precision) = args
600 .first()
601 .and_then(|arg| eval_as_number(self.expr_ctx, &arg.expr))
602 {
603 let p = precision.trunc() as usize;
604 if !(1..=21).contains(&p) {
606 return;
607 }
608
609 let value = f64_to_precision(num.value, p);
610 self.changed = true;
611 report_change!(
612 "evaluate: Evaluating `{}.toPrecision()` as `{}`",
613 num,
614 value
615 );
616 *e = Lit::Str(Str {
617 span: e.span(),
618 raw: None,
619 value: value.into(),
620 })
621 .into();
622 return;
623 }
624 }
625
626 if &*method.sym == "toExponential" {
627 if args.is_empty() {
629 let value = f64_to_exponential(num.value).into();
630
631 self.changed = true;
632 report_change!(
633 "evaluate: Evaluating `{}.toExponential()` as `{}`",
634 num,
635 value
636 );
637
638 *e = Lit::Str(Str {
639 span: e.span(),
640 raw: None,
641 value,
642 })
643 .into();
644 return;
645 } else if let Some(precision) = args
646 .first()
647 .and_then(|arg| eval_as_number(self.expr_ctx, &arg.expr))
648 {
649 let p = precision.trunc() as usize;
650 if !(0..=20).contains(&p) {
652 return;
653 }
654
655 let value = f64_to_exponential_with_precision(num.value, p).into();
656
657 self.changed = true;
658 report_change!(
659 "evaluate: Evaluating `{}.toPrecision({})` as `{}`",
660 num,
661 precision,
662 value
663 );
664
665 *e = Lit::Str(Str {
666 span: e.span(),
667 raw: None,
668 value,
669 })
670 .into();
671 return;
672 }
673 }
674
675 if &*method.sym == "toString" {
676 if let Some(base) = args
677 .first()
678 .map_or(Some(10f64), |arg| eval_as_number(self.expr_ctx, &arg.expr))
679 {
680 if base.trunc() == 10. {
681 let value = num.value.to_js_string().into();
682 *e = Lit::Str(Str {
683 span: e.span(),
684 raw: None,
685 value,
686 })
687 .into();
688 return;
689 }
690
691 if num.value.fract() == 0.0 && (2.0..=36.0).contains(&base) && base.fract() == 0.0 {
692 let base = base.floor() as u8;
693
694 self.changed = true;
695
696 let value = {
697 let x = num.value;
698 if x < 0. {
699 format!("-{}", Radix::new(-x as u128, base))
701 } else {
702 Radix::new(x as u128, base).to_string()
703 }
704 }
705 .into();
706
707 *e = Lit::Str(Str {
708 span: e.span(),
709 raw: None,
710 value,
711 })
712 .into()
713 }
714 }
715 }
716 }
717
718 pub(super) fn eval_opt_chain(&mut self, e: &mut Expr) {
719 let opt = match e {
720 Expr::OptChain(e) => e,
721 _ => return,
722 };
723
724 match &mut *opt.base {
725 OptChainBase::Member(MemberExpr { span, obj, .. }) => {
726 if is_pure_undefined_or_null(self.expr_ctx, obj) {
728 self.changed = true;
729 report_change!(
730 "evaluate: Reduced an optional chaining operation because object is \
731 always null or undefined"
732 );
733
734 *e = *Expr::undefined(*span);
735 }
736 }
737
738 OptChainBase::Call(OptCall { span, callee, .. }) => {
739 if is_pure_undefined_or_null(self.expr_ctx, callee) {
740 self.changed = true;
741 report_change!(
742 "evaluate: Reduced a call expression with optional chaining operation \
743 because object is always null or undefined"
744 );
745
746 *e = *Expr::undefined(*span);
747 }
748 }
749 #[cfg(swc_ast_unknown)]
750 _ => panic!("unable to access unknown nodes"),
751 }
752 }
753
754 pub(super) fn eval_trivial_values_in_expr(&mut self, seq: &mut SeqExpr) {
755 if seq.exprs.len() < 2 {
756 return;
757 }
758
759 'outer: for idx in 0..seq.exprs.len() {
760 let (a, b) = seq.exprs.split_at_mut(idx);
761
762 for a in a.iter().rev() {
763 if let Some(b) = b.first_mut() {
764 self.eval_trivial_two(a, b);
765
766 match &**b {
767 Expr::Ident(..) | Expr::Lit(..) => {}
768 _ => break 'outer,
769 }
770 }
771 }
772 }
773 }
774
775 pub(super) fn eval_member_expr(&mut self, e: &mut Expr) {
776 if self.ctx.contains(Ctx::IN_OPT_CHAIN) {
777 return;
778 }
779
780 let member_expr = match e {
781 Expr::Member(x) => x,
782 _ => return,
783 };
784
785 if let Some(replacement) =
786 self.optimize_member_expr(&mut member_expr.obj, &member_expr.prop)
787 {
788 *e = replacement;
789 self.changed = true;
790 report_change!(
791 "member_expr: Optimized member expression as {}",
792 dump(&*e, false)
793 );
794 }
795 }
796
797 fn eval_trivial_two(&mut self, a: &Expr, b: &mut Expr) {
798 if let Expr::Assign(AssignExpr {
799 left: a_left,
800 op: op!("="),
801 right: a_right,
802 ..
803 }) = a
804 {
805 match &**a_right {
806 Expr::Lit(..) => {}
807 _ => return,
808 }
809
810 if let AssignTarget::Simple(SimpleAssignTarget::Ident(a_left)) = a_left {
811 if let Expr::Ident(b_id) = b {
812 if b_id.ctxt == a_left.id.ctxt && b_id.sym == a_left.id.sym {
813 report_change!("evaluate: Trivial: `{}`", a_left.id);
814 *b = *a_right.clone();
815 self.changed = true;
816 }
817 }
818 }
819 }
820 }
821}
822
823impl Pure<'_> {
825 pub(super) fn eval_str_method_call(&mut self, e: &mut Expr) {
827 if !self.options.evaluate {
828 return;
829 }
830
831 if self.ctx.intersects(
832 Ctx::IN_DELETE
833 .union(Ctx::IS_UPDATE_ARG)
834 .union(Ctx::IS_LHS_OF_ASSIGN),
835 ) {
836 return;
837 }
838
839 let call = match e {
840 Expr::Call(v) => v,
841 _ => return,
842 };
843
844 let (s, method) = match &call.callee {
845 Callee::Super(_) | Callee::Import(_) => return,
846 Callee::Expr(callee) => match &**callee {
847 Expr::Member(MemberExpr {
848 obj,
849 prop: MemberProp::Ident(prop),
850 ..
851 }) => match &**obj {
852 Expr::Lit(Lit::Str(s)) => (s.clone(), prop.sym.clone()),
853 _ => return,
854 },
855 _ => return,
856 },
857 #[cfg(swc_ast_unknown)]
858 _ => panic!("unable to access unknown nodes"),
859 };
860
861 let new_val = match &*method {
862 "toLowerCase" => s.value.to_lowercase(),
863 "toUpperCase" => s.value.to_uppercase(),
864 "charCodeAt" => {
865 if call.args.len() != 1 {
866 return;
867 }
868 if let Expr::Lit(Lit::Num(Number { value, .. })) = &*call.args[0].expr {
869 if value.fract() != 0.0 {
870 return;
871 }
872
873 let idx = value.round() as i64 as usize;
874 let c = s.value.to_ill_formed_utf16().nth(idx);
875
876 match c {
877 Some(v) => {
878 self.changed = true;
879 report_change!(
880 "evaluate: Evaluated `charCodeAt` of a string literal as `{}`",
881 v
882 );
883 *e = Lit::Num(Number {
884 span: call.span,
885 value: v as usize as f64,
886 raw: None,
887 })
888 .into()
889 }
890 None => {
891 self.changed = true;
892 report_change!(
893 "evaluate: Evaluated `charCodeAt` of a string literal as `NaN`",
894 );
895 *e = Ident::new(atom!("NaN"), e.span(), SyntaxContext::empty()).into()
896 }
897 }
898 }
899 return;
900 }
901 "codePointAt" => {
902 if call.args.len() != 1 {
903 return;
904 }
905 if let Expr::Lit(Lit::Num(Number { value, .. })) = &*call.args[0].expr {
906 if value.fract() != 0.0 {
907 return;
908 }
909
910 let idx = value.round() as i64 as usize;
911 let mut c = s.value.to_ill_formed_utf16().skip(idx).peekable();
912 match c.next() {
913 Some(v) => {
914 match (v, c.peek()) {
915 (high, Some(&low))
916 if is_high_surrogate(high as u32)
917 && is_low_surrogate(low as u32) =>
918 {
919 let code_point = swc_ecma_utils::unicode::pair_to_code_point(
921 high as u32,
922 low as u32,
923 );
924 self.changed = true;
925 report_change!(
926 "evaluate: Evaluated `codePointAt` of a string literal as \
927 `{}`",
928 code_point
929 );
930 *e = Lit::Num(Number {
931 span: call.span,
932 value: code_point as f64,
933 raw: None,
934 })
935 .into();
936 return;
937 }
938 _ => {
939 self.changed = true;
941 report_change!(
942 "evaluate: Evaluated `codePointAt` of a string literal as \
943 `{}`",
944 v
945 );
946 *e = Lit::Num(Number {
947 span: call.span,
948 value: v as usize as f64,
949 raw: None,
950 })
951 .into()
952 }
953 }
954 }
955 None => {
956 self.changed = true;
957 report_change!(
958 "evaluate: Evaluated `codePointAt` of a string literal as `NaN`",
959 );
960 *e = Ident::new(
961 atom!("NaN"),
962 e.span(),
963 SyntaxContext::empty().apply_mark(self.marks.unresolved_mark),
964 )
965 .into()
966 }
967 }
968 }
969 return;
970 }
971 _ => return,
972 };
973
974 self.changed = true;
975 report_change!("evaluate: Evaluated `{method}` of a string literal");
976 *e = Lit::Str(Str {
977 value: new_val.into(),
978 raw: None,
979 ..s
980 })
981 .into();
982 }
983}
984
985fn f64_to_precision(value: f64, precision: usize) -> String {
988 let mut x = value;
989 let p_i32 = precision as i32;
990
991 let mut s = String::new();
993 let mut m: String;
994 let mut e: i32;
995
996 if x < 0. {
999 s.push('-');
1000 x = -x;
1001 }
1002
1003 if x == 0.0 {
1006 m = "0".repeat(precision);
1007 e = 0;
1008 } else {
1010 m = format!("{x:.100}");
1014
1015 e = flt_str_to_exp(&m);
1017 if e < 0 {
1019 m = m.split_off((1 - e) as usize);
1020 } else if let Some(n) = m.find('.') {
1021 m.remove(n);
1022 }
1023 if round_to_precision(&mut m, precision) {
1025 e += 1;
1026 }
1027
1028 let great_exp = e >= p_i32;
1030 if e < -6 || great_exp {
1031 assert_ne!(e, 0);
1032
1033 if precision > 1 {
1035 m.insert(1, '.');
1036 }
1037 m.push('e');
1039 if great_exp {
1041 m.push('+');
1042 }
1043 m.push_str(&e.to_string());
1045
1046 return s + &*m;
1047 }
1048 }
1049
1050 let e_inc = e + 1;
1052 if e_inc == p_i32 {
1053 return s + &*m;
1054 }
1055
1056 if e >= 0 {
1058 m.insert(e_inc as usize, '.');
1059 } else {
1061 s.push('0');
1062 s.push('.');
1063 s.push_str(&"0".repeat(-e_inc as usize));
1064 }
1065
1066 s + &*m
1068}
1069
1070fn flt_str_to_exp(flt: &str) -> i32 {
1071 let mut non_zero_encountered = false;
1072 let mut dot_encountered = false;
1073 for (i, c) in flt.chars().enumerate() {
1074 if c == '.' {
1075 if non_zero_encountered {
1076 return (i as i32) - 1;
1077 }
1078 dot_encountered = true;
1079 } else if c != '0' {
1080 if dot_encountered {
1081 return 1 - (i as i32);
1082 }
1083 non_zero_encountered = true;
1084 }
1085 }
1086 (flt.len() as i32) - 1
1087}
1088
1089fn round_to_precision(digits: &mut String, precision: usize) -> bool {
1090 if digits.len() > precision {
1091 let to_round = digits.split_off(precision);
1092 let mut digit = digits
1093 .pop()
1094 .expect("already checked that length is bigger than precision")
1095 as u8;
1096 if let Some(first) = to_round.chars().next() {
1097 if first > '4' {
1098 digit += 1;
1099 }
1100 }
1101
1102 if digit as char == ':' {
1103 let mut replacement = String::from("0");
1106 let mut propagated = false;
1107 for c in digits.chars().rev() {
1108 let d = match (c, propagated) {
1109 ('0'..='8', false) => (c as u8 + 1) as char,
1110 (_, false) => '0',
1111 (_, true) => c,
1112 };
1113 replacement.push(d);
1114 if d != '0' {
1115 propagated = true;
1116 }
1117 }
1118 digits.clear();
1119 let replacement = if propagated {
1120 replacement.as_str()
1121 } else {
1122 digits.push('1');
1123 &replacement.as_str()[1..]
1124 };
1125 for c in replacement.chars().rev() {
1126 digits.push(c);
1127 }
1128 !propagated
1129 } else {
1130 digits.push(digit as char);
1131 false
1132 }
1133 } else {
1134 digits.push_str(&"0".repeat(precision - digits.len()));
1135 false
1136 }
1137}
1138
1139fn f64_to_exponential(n: f64) -> String {
1142 match n.abs() {
1143 x if x >= 1.0 || x == 0.0 => format!("{n:e}").replace('e', "e+"),
1144 _ => format!("{n:e}"),
1145 }
1146}
1147
1148fn f64_to_exponential_with_precision(n: f64, prec: usize) -> String {
1155 let mut res = format!("{n:.prec$e}");
1156 let idx = res.find('e').expect("'e' not found in exponential string");
1157 if res.as_bytes()[idx + 1] != b'-' {
1158 res.insert(idx + 1, '+');
1159 }
1160 res
1161}