Source code
Revision control
Copy as Markdown
Other Tools
/* 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
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<W: Write, V: Floating>(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<Notation, fmt::Error>;
pub trait Floating : dtoa::Floating {
fn write<W: Write>(self, dest: &mut W) -> DtoaResult;
}
impl Floating for f32 {
fn write<W: Write>(self, dest: &mut W) -> DtoaResult {
write_with_prec(dest, self, 6)
}
}
impl Floating for f64 {
fn write<W: 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
// 584674a70af74521ce40350dba776ea67cfcbaa7/src/dtoa.rs#L465
const BUFFER_SIZE: usize = 24;
fn write_with_prec<W, V>(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)
}