/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ extern crate dtoa; use std::fmt::Write; use std::{fmt, str}; /// Format the given `value` into `dest` and return the notation it uses. #[inline] pub fn write(dest: &mut W, value: V) -> DtoaResult { Floating::write(value, dest) } /// Form of the formatted floating-point number. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Notation { /// Whether it contains a decimal point. pub decimal_point: bool, /// Whether it uses E-notation. pub scientific: bool, } impl Notation { fn integer() -> Self { Notation { decimal_point: false, scientific: false, } } } /// Result of formatting the number. pub type DtoaResult = Result; pub trait Floating : dtoa::Floating { fn write(self, dest: &mut W) -> DtoaResult; } impl Floating for f32 { fn write(self, dest: &mut W) -> DtoaResult { write_with_prec(dest, self, 6) } } impl Floating for f64 { fn write(self, dest: &mut W) -> DtoaResult { write_with_prec(dest, self, 15) } } // dtoa's buffer is 24 bytes, so we use the same length here. We may // need to update if dtoa changes its number in the future. See // https://github.com/dtolnay/dtoa/blob/ // 584674a70af74521ce40350dba776ea67cfcbaa7/src/dtoa.rs#L465 const BUFFER_SIZE: usize = 24; fn write_with_prec(dest: &mut W, value: V, prec: usize) -> DtoaResult where W: Write, V: dtoa::Floating { let mut buf = [b'\0'; BUFFER_SIZE + 8]; let len = dtoa::write(&mut buf[1..], value).map_err(|_| fmt::Error)?; let (result, notation) = restrict_prec(&mut buf[0..len + 1], prec); dest.write_str(if cfg!(debug_assertions) { str::from_utf8(result).unwrap() } else { // safety: dtoa only generates ascii. unsafe { str::from_utf8_unchecked(result) } })?; Ok(notation) } fn restrict_prec(buf: &mut [u8], prec: usize) -> (&[u8], Notation) { let len = buf.len(); debug_assert!(len <= BUFFER_SIZE + 1, "dtoa may have changed its buffer size"); // Put a leading zero to capture any carry. debug_assert!(buf[0] == b'\0', "Caller must prepare an empty byte for us"); buf[0] = b'0'; // Remove the sign for now. We will put it back at the end. let sign = match buf[1] { s @ b'+' | s @ b'-' => { buf[1] = b'0'; Some(s) } _ => None, }; // Locate dot, exponent, and the first significant digit. let mut pos_dot = None; let mut pos_exp = None; let mut prec_start = None; for i in 1..len { if buf[i] == b'.' { debug_assert!(pos_dot.is_none()); pos_dot = Some(i); } else if buf[i] == b'e' { pos_exp = Some(i); // We don't change exponent part, so stop here. break; } else if prec_start.is_none() && buf[i] != b'0' { debug_assert!(buf[i] >= b'1' && buf[i] <= b'9'); prec_start = Some(i); } } let prec_start = match prec_start { Some(i) => i, // If there is no non-zero digit at all, it is just zero. None => return (&buf[0..1], Notation::integer()), }; // Coefficient part ends at 'e' or the length. let coeff_end = pos_exp.unwrap_or(len); // Decimal dot is effectively at the end of coefficient part if no // dot presents before that. let pos_dot = pos_dot.unwrap_or(coeff_end); // Find the end position of the number within the given precision. let prec_end = { let end = prec_start + prec; if pos_dot > prec_start && pos_dot <= end { end + 1 } else { end } }; let mut new_coeff_end = coeff_end; if prec_end < coeff_end { // Round to the given precision. let next_char = buf[prec_end]; new_coeff_end = prec_end; if next_char >= b'5' { for i in (0..prec_end).rev() { if buf[i] == b'.' { continue; } if buf[i] != b'9' { buf[i] += 1; new_coeff_end = i + 1; break; } buf[i] = b'0'; } } } if new_coeff_end < pos_dot { // If the precision isn't enough to reach the dot, set all digits // in-between to zero and keep the number until the dot. for i in new_coeff_end..pos_dot { buf[i] = b'0'; } new_coeff_end = pos_dot; } else { // Strip any trailing zeros. for i in (0..new_coeff_end).rev() { if buf[i] != b'0' { if buf[i] == b'.' { new_coeff_end = i; } break; } new_coeff_end = i; } } // Move exponent part if necessary. let real_end = if let Some(pos_exp) = pos_exp { let exp_len = len - pos_exp; if new_coeff_end != pos_exp { for i in 0..exp_len { buf[new_coeff_end + i] = buf[pos_exp + i]; } } new_coeff_end + exp_len } else { new_coeff_end }; // Add back the sign and strip the leading zero. let result = if let Some(sign) = sign { if buf[1] == b'0' && buf[2] != b'.' { buf[1] = sign; &buf[1..real_end] } else { debug_assert!(buf[0] == b'0'); buf[0] = sign; &buf[0..real_end] } } else { if buf[0] == b'0' && buf[1] != b'.' { &buf[1..real_end] } else { &buf[0..real_end] } }; // Generate the notation info. let notation = Notation { decimal_point: pos_dot < new_coeff_end, scientific: pos_exp.is_some(), }; (result, notation) }