1const MAX_SAFE_INTEGER: u64 = (1 << 53) - 1;
2
3const DIGIT_PAIRS: &[u8; 200] = b"\
4 0001020304050607080910111213141516171819\
5 2021222324252627282930313233343536373839\
6 4041424344454647484950515253545556575859\
7 6061626364656667686970717273747576777879\
8 8081828384858687888990919293949596979899";
9
10fn decimal(buf: &mut [u8; 20], mut n: u64) -> usize {
11 let mut i = buf.len();
12 while n >= 100 {
13 let pair = (n % 100) as usize * 2;
14 n /= 100;
15 i -= 2;
16 buf[i..i + 2].copy_from_slice(&DIGIT_PAIRS[pair..pair + 2]);
17 }
18 if n >= 10 {
19 let pair = n as usize * 2;
20 i -= 2;
21 buf[i..i + 2].copy_from_slice(&DIGIT_PAIRS[pair..pair + 2]);
22 } else {
23 i -= 1;
24 buf[i] = b'0' + n as u8;
25 }
26 i
27}
28
29fn push_decimal(out: &mut String, n: u64) {
30 if n < 10 {
31 out.push(char::from(b'0' + n as u8));
32 return;
33 }
34 let mut buf = [0; 20];
35 let start = decimal(&mut buf, n);
36 out.reserve(buf.len() - start);
37 for &b in &buf[start..] {
38 out.push(char::from(b));
39 }
40}
41
42pub fn push_int(out: &mut String, n: i64) {
58 if n.unsigned_abs() <= MAX_SAFE_INTEGER {
59 if n < 0 {
60 out.push('-');
61 }
62 push_decimal(out, n.unsigned_abs());
63 } else {
64 push_number(out, n as f64);
65 }
66}
67
68struct Short {
71 buf: [u8; 48],
72 len: usize,
73}
74
75impl Short {
76 const fn new() -> Short {
77 Short {
78 buf: [0; 48],
79 len: 0,
80 }
81 }
82
83 fn of(digits: &str) -> Short {
84 let mut short = Short::new();
85 short.extend(digits.bytes());
86 short
87 }
88
89 fn extend(&mut self, bytes: impl Iterator<Item = u8>) {
90 for b in bytes {
91 self.buf[self.len] = b;
92 self.len += 1;
93 }
94 }
95
96 fn as_str(&self) -> &str {
97 std::str::from_utf8(&self.buf[..self.len]).expect("only ASCII is written")
98 }
99}
100
101impl std::fmt::Write for Short {
102 fn write_str(&mut self, s: &str) -> std::fmt::Result {
103 let end = self.len + s.len();
104 self.buf
105 .get_mut(self.len..end)
106 .ok_or(std::fmt::Error)?
107 .copy_from_slice(s.as_bytes());
108 self.len = end;
109 Ok(())
110 }
111}
112
113pub fn push_number(out: &mut String, x: f64) {
131 use std::fmt::Write;
132 if x.is_nan() {
133 out.push_str("NaN");
134 return;
135 }
136 if x == 0.0 {
137 out.push('0');
138 return;
139 }
140 if x.is_infinite() {
141 out.push_str(if x < 0.0 { "-Infinity" } else { "Infinity" });
142 return;
143 }
144 if x < 0.0 {
145 out.push('-');
146 }
147 let x = x.abs();
148 if x.fract() == 0.0 && x <= MAX_SAFE_INTEGER as f64 {
149 push_decimal(out, x as u64);
150 return;
151 }
152 let (digits, exp) = shortest_digits(x);
153 let digits = digits.as_str();
154 let k = digits.len() as i32;
155 let n = exp + 1;
156 if k <= n && n <= 21 {
157 out.push_str(digits);
158 out.extend(std::iter::repeat_n('0', (n - k) as usize));
159 } else if 0 < n && n <= 21 {
160 out.push_str(&digits[..n as usize]);
161 out.push('.');
162 out.push_str(&digits[n as usize..]);
163 } else if -6 < n && n <= 0 {
164 out.push_str("0.");
165 out.extend(std::iter::repeat_n('0', (-n) as usize));
166 out.push_str(digits);
167 } else {
168 out.push_str(&digits[..1]);
169 if k > 1 {
170 out.push('.');
171 out.push_str(&digits[1..]);
172 }
173 let e = n - 1;
174 let _ = write!(out, "e{}{}", if e < 0 { '-' } else { '+' }, e.abs());
175 }
176}
177
178fn shortest_digits(x: f64) -> (Short, i32) {
179 use std::fmt::Write;
180 let mut sci = Short::new();
181 let _ = write!(sci, "{x:e}");
182 let (mantissa, exp) = sci
183 .as_str()
184 .split_once('e')
185 .expect("`{:e}` writes an exponent");
186 let exp: i32 = exp.parse().expect("`{:e}` writes an integer exponent");
187 let mut digits = Short::new();
188 digits.extend(mantissa.bytes().filter(|b| *b != b'.'));
189 if let Some((exact, exact_exp)) = few_exact_digits(x)
190 && exact_exp == exp
191 {
192 let exact = exact.as_str();
193 let k = digits.len;
194 if exact.len() == k + 1 && exact.ends_with('5') {
195 let lower = &exact[..k];
196 let upper = increment_digits(lower);
197 let even = |d: &str| {
198 d.bytes()
199 .last()
200 .is_some_and(|b| (b - b'0').is_multiple_of(2))
201 };
202 if upper.len() == k && (digits.as_str() == lower || digits.as_str() == upper) {
203 digits = Short::of(if even(lower) { lower } else { &upper });
204 }
205 }
206 }
207 (digits, exp)
208}
209
210fn few_exact_digits(x: f64) -> Option<(Short, i32)> {
211 use std::fmt::Write;
212 let bits = x.to_bits();
213 let biased = ((bits >> 52) & 0x7ff) as i32;
214 let fraction = bits & ((1 << 52) - 1);
215 let (m, e) = if biased == 0 {
216 (fraction, -1074)
217 } else {
218 (fraction | 1 << 52, biased - 1075)
219 };
220 let shift = m.trailing_zeros();
221 let (m, e) = (m >> shift, e + shift as i32);
222 let (value, scale) = if e < 0 {
223 if -e >= 26 {
224 return None;
225 }
226 (u128::from(m) * 5u128.pow(e.unsigned_abs()), e)
227 } else if e <= 74 {
228 (u128::from(m) << e, 0)
229 } else if e < 82 {
230 let mut s = String::new();
231 let _ = write!(s, "{x:.40e}");
232 let (mantissa, exp) = s.split_once('e')?;
233 let digits: String = mantissa.chars().filter(|c| *c != '.').collect();
234 let digits = digits.trim_end_matches('0');
235 let exp = exp.parse().ok()?;
236 return (digits.len() <= 18).then(|| (Short::of(digits), exp));
237 } else {
238 return None;
239 };
240 let mut digits = Short::new();
241 let _ = write!(digits, "{value}");
242 let all = digits.len;
243 digits.len = digits.as_str().trim_end_matches('0').len();
244 (digits.len <= 18).then_some((digits, all as i32 - 1 + scale))
245}
246
247#[must_use]
263pub fn js_round(x: f64) -> f64 {
264 let f = x.floor();
265 let r = if x - f >= 0.5 { f + 1.0 } else { f };
266 if r == 0.0 && x.is_sign_negative() {
267 -0.0
268 } else {
269 r
270 }
271}
272
273#[must_use]
289pub fn js_sign(x: f64) -> f64 {
290 if x > 0.0 {
291 1.0
292 } else if x < 0.0 {
293 -1.0
294 } else {
295 x
296 }
297}
298
299#[must_use]
314pub fn js_max(a: f64, b: f64) -> f64 {
315 if a.is_nan() || b.is_nan() {
316 f64::NAN
317 } else if a > b || (a == b && b.is_sign_negative()) {
318 a
319 } else {
320 b
321 }
322}
323
324#[must_use]
339pub fn js_min(a: f64, b: f64) -> f64 {
340 if a.is_nan() || b.is_nan() {
341 f64::NAN
342 } else if a < b || (a == b && a.is_sign_negative()) {
343 a
344 } else {
345 b
346 }
347}
348
349#[must_use]
365pub fn js_to_fixed(x: f64, digits: u32) -> String {
366 if x.is_nan() {
367 return "NaN".to_owned();
368 }
369 if x.abs() >= 1e21 || x.is_infinite() {
370 let mut s = String::new();
371 push_number(&mut s, x);
372 return s;
373 }
374 let d = digits as usize;
375 let (exact, exp) = exact_digits(x);
376 let scaled = match usize::try_from(exp + 1 + digits as i32) {
377 Ok(0) => if exact.as_bytes()[0] >= b'5' {
378 "1"
379 } else {
380 "0"
381 }
382 .to_owned(),
383 Ok(keep) => rounded_half_up(&exact, keep),
384 Err(_) => "0".to_owned(),
385 };
386 let mut out = String::with_capacity(d + 3);
387 if x < 0.0 {
388 out.push('-');
389 }
390 out.extend(std::iter::repeat_n(
391 '0',
392 (d + 1).saturating_sub(scaled.len()),
393 ));
394 out.push_str(&scaled);
395 if d > 0 {
396 out.insert(out.len() - d, '.');
397 }
398 out
399}
400
401#[must_use]
424pub fn js_to_precision(x: f64, precision: u32) -> String {
425 use std::fmt::Write;
426 if !x.is_finite() {
427 let mut s = String::new();
428 push_number(&mut s, x);
429 return s;
430 }
431 assert!(
432 (1..=100).contains(&precision),
433 "toPrecision() takes a precision from 1 to 100: {precision} was given"
434 );
435 let p = precision as usize;
436 let (digits, e) = significant(x, p);
437 let mut out = String::with_capacity(p + 8);
438 if x < 0.0 {
439 out.push('-');
440 }
441 if e < -6 || e >= precision as i32 {
442 push_mantissa(&mut out, &digits);
443 let _ = write!(out, "e{}{}", if e < 0 { '-' } else { '+' }, e.abs());
444 } else if let Ok(point) = usize::try_from(e) {
445 let whole = point + 1;
446 out.push_str(&digits[..whole]);
447 if whole < p {
448 out.push('.');
449 out.push_str(&digits[whole..]);
450 }
451 } else {
452 out.push_str("0.");
453 out.extend(std::iter::repeat_n('0', (-e - 1) as usize));
454 out.push_str(&digits);
455 }
456 out
457}
458
459#[must_use]
482pub fn js_to_exponential(x: f64, fraction_digits: Option<u32>) -> String {
483 use std::fmt::Write;
484 if !x.is_finite() {
485 let mut s = String::new();
486 push_number(&mut s, x);
487 return s;
488 }
489 let (digits, e) = match fraction_digits {
490 Some(f) => {
491 assert!(
492 f <= 100,
493 "toExponential() takes from 0 to 100 fraction digits: {f} were given"
494 );
495 significant(x, f as usize + 1)
496 }
497 None if x == 0.0 => ("0".to_owned(), 0),
498 None => {
499 let (digits, e) = shortest_digits(x.abs());
500 (digits.as_str().to_owned(), e)
501 }
502 };
503 let mut out = String::with_capacity(digits.len() + 8);
504 if x < 0.0 {
505 out.push('-');
506 }
507 push_mantissa(&mut out, &digits);
508 let _ = write!(out, "e{}{}", if e < 0 { '-' } else { '+' }, e.abs());
509 out
510}
511
512fn push_mantissa(out: &mut String, digits: &str) {
513 out.push_str(&digits[..1]);
514 if digits.len() > 1 {
515 out.push('.');
516 out.push_str(&digits[1..]);
517 }
518}
519
520fn significant(x: f64, keep: usize) -> (String, i32) {
521 let (exact, e) = exact_digits(x);
522 let mut digits = rounded_half_up(&exact, keep);
523 if digits.len() > keep {
524 digits.truncate(keep);
525 (digits, e + 1)
526 } else {
527 (digits, e)
528 }
529}
530
531fn exact_digits(x: f64) -> (String, i32) {
532 use std::fmt::Write;
533 let mut s = String::new();
534 let _ = write!(s, "{:.800e}", x.abs());
535 let (mantissa, exp) = s.split_once('e').expect("`{:e}` writes an exponent");
536 let exp = exp.parse().expect("`{:e}` writes an integer exponent");
537 let mut digits: String = mantissa.chars().filter(|c| *c != '.').collect();
538 digits.truncate(digits.trim_end_matches('0').len().max(1));
539 (digits, exp)
540}
541
542fn rounded_half_up(exact: &str, keep: usize) -> String {
543 match exact.as_bytes().get(keep) {
544 None => {
545 let mut digits = exact.to_owned();
546 digits.extend(std::iter::repeat_n('0', keep - exact.len()));
547 digits
548 }
549 Some(&next) if next >= b'5' => increment_digits(&exact[..keep]),
550 Some(_) => exact[..keep].to_owned(),
551 }
552}
553
554fn increment_digits(digits: &str) -> String {
555 let mut bytes: Vec<u8> = digits.bytes().collect();
556 for b in bytes.iter_mut().rev() {
557 if *b == b'9' {
558 *b = b'0';
559 } else {
560 *b += 1;
561 return String::from_utf8(bytes).expect("ASCII digits");
562 }
563 }
564 bytes.insert(0, b'1');
565 String::from_utf8(bytes).expect("ASCII digits")
566}
567
568#[derive(Debug, Clone, Copy)]
584pub struct Js<T>(pub T);
585
586impl std::fmt::Display for Js<i64> {
587 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
588 if self.0.unsigned_abs() > MAX_SAFE_INTEGER {
589 return Js(self.0 as f64).fmt(f);
590 }
591 let mut buf = [0; 20];
592 let start = decimal(&mut buf, self.0.unsigned_abs());
593 if self.0 < 0 {
594 f.write_str("-")?;
595 }
596 f.write_str(std::str::from_utf8(&buf[start..]).expect("ASCII digits"))
597 }
598}
599
600impl std::fmt::Display for Js<f64> {
601 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
602 let mut s = String::new();
603 push_number(&mut s, self.0);
604 f.write_str(&s)
605 }
606}
607
608#[cfg(test)]
609mod tests;