// Turning doubles into text, the way CPython's `format_float_short` does.
///|
/// Decimal digits of a finite double: `0. * 10^decpt`.
priv struct Dec {
neg : Bool
digits : String
decpt : Int32
}
///|
/// Drop trailing zeros from a digit string, keeping one digit.
fn strip_zeros(text : String) -> String {
let mut n = text.length()
while n > 1 && text.get_char(n - 1) == Some('0') {
n -= 1
}
if n == text.length() {
text
} else {
slice(text, 0, n)
}
}
///|
/// Small signed decimal exponent, as written by `Double::to_string`.
fn parse_exp(text : String) -> Int32 {
let mut i = 0
let mut sign = 1
if text.get_char(0) == Some('-') {
sign = -1
i = 1
} else if text.get_char(0) == Some('+') {
i = 1
}
let mut value = 0
while i < text.length() {
match text.get_char(i) {
Some(c) => value = value * 10 + c.to_int() - '0'.to_int()
None => break
}
i += 1
}
sign * value
}
///|
/// The shortest digit string that reads back as `d`, as `(digits, decpt)`.
///
/// The platform already has one: `Double::to_string` is Ryƫ, which is shortest
/// and correctly rounded. Only the spelling is thrown away here, so that the
/// same digits can be laid out Python's way.
fn ryu_parts(d : Double) -> (String, Int32) {
let text = d.to_string()
let start = if text.get_char(0) == Some('-') { 1 } else { 0 }
let mut i = start
let mut mantissa_end = text.length()
let mut exp = 0
while i < text.length() {
match text.get_char(i) {
Some(c) =>
if c == 'e' || c == 'E' {
mantissa_end = i
exp = parse_exp(slice(text, i + 1, text.length()))
break
}
None => break
}
i += 1
}
let mantissa = slice(text, start, mantissa_end)
let mut int_len = mantissa.length()
let mut digits = mantissa
match mantissa.find(".") {
Some(dot) => {
int_len = dot
digits = slice(mantissa, 0, dot) +
slice(mantissa, dot + 1, mantissa.length())
}
None => ()
}
let mut decpt = int_len + exp
let mut first = 0
while first < digits.length() - 1 && digits.get_char(first) == Some('0') {
first += 1
}
if first > 0 {
digits = slice(digits, first, digits.length())
decpt -= first
}
let stripped = strip_zeros(digits)
if stripped == "0" {
("0", 1)
} else {
(stripped, decpt)
}
}
///|
/// `d` as decimal digits, in one of three modes.
///
/// Mode 0 is the shortest round-tripping string, mode 2 has `precision`
/// significant digits with trailing zeros stripped, mode 3 has `precision`
/// digits after the point. Modes 2 and 3 round half to even on the exact value.
fn dtoa(d : Double, mode : Int32, precision : Int32) -> Dec {
let neg = d.reinterpret_as_uint64() >> 63 == 1UL
if d == 0.0 {
return { neg, digits: "0", decpt: 1, }
}
let (_, num, den) = double_ratio(d)
if mode == 0 {
let (digits, decpt) = ryu_parts(d)
{ neg, digits, decpt, }
} else if mode == 2 {
let wanted = if precision < 1 { 1 } else { precision }
let (digits, decpt) = sig_digits(num, den, wanted)
{ neg, digits: strip_zeros(digits), decpt, }
} else {
let (digits, decpt) = place_digits(num, den, precision)
{ neg, digits, decpt, }
}
}
///|
/// Layout a finite double's digits as text, mirroring CPython's
/// `format_float_short`.
///
/// `code` is `e`, `f`, `g` or `r`. `precision` is the digit count that code
/// wants: significant digits for `e` and `g`, digits after the point for `f`.
fn float_short(
d : Double,
code : Char,
precision : Int32,
always_sign : Bool,
add_dot0 : Bool,
alt : Bool,
neg0 : Bool,
upper : Bool,
) -> String {
if d.is_nan() {
// CPython drops a NaN's sign but keeps a forced plus.
let text = if upper { "NAN" } else { "nan" }
return (if always_sign { "+" } else { "" }) + text
}
if d.is_inf() {
let text = if upper { "INF" } else { "inf" }
let sign = if d < 0.0 { "-" } else if always_sign { "+" } else { "" }
return sign + text
}
let mode = if code == 'f' { 3 } else if code == 'r' { 0 } else { 2 }
let dec = dtoa(d, mode, precision)
let digits = dec.digits
let mut decpt = dec.decpt
let mut sign = if dec.neg { "-" } else { "" }
if neg0 && dec.neg && digits == "0" {
sign = ""
}
if sign == "" && always_sign {
sign = "+"
}
let digits_len = digits.length()
let mut use_exp = false
let mut vdigits_end = digits_len
match code {
'e' => {
use_exp = true
vdigits_end = precision
}
'f' => vdigits_end = decpt + precision
'g' => {
if decpt <= -4 ||
decpt > (if add_dot0 { precision - 1 } else { precision }) {
use_exp = true
}
if alt {
vdigits_end = precision
}
}
_ => if decpt <= -4 || decpt > 16 { use_exp = true }
}
let mut exp = 0
if use_exp {
exp = decpt - 1
decpt = 1
}
let vdigits_start = if decpt <= 0 { decpt - 1 } else { 0 }
if !use_exp && add_dot0 {
if vdigits_end < decpt + 1 {
vdigits_end = decpt + 1
}
} else if vdigits_end < decpt {
vdigits_end = decpt
}
let out = StringBuilder()
out.write_string(sign)
let mut p = vdigits_start
while p < vdigits_end {
if p == decpt {
out.write_char('.')
}
if p >= 0 && p < digits_len {
out.write_char(digits.get_char(p).unwrap())
} else {
out.write_char('0')
}
p += 1
}
if vdigits_end == decpt {
out.write_char('.')
}
let mut text = out.to_string()
if !alt && text.get_char(text.length() - 1) == Some('.') {
text = slice(text, 0, text.length() - 1)
}
if use_exp {
let e_text = if upper { "E" } else { "e" }
let sign_text = if exp < 0 { "-" } else { "+" }
let magnitude = if exp < 0 { -exp } else { exp }
let pad = if magnitude < 10 { "0" } else { "" }
text + e_text + sign_text + pad + magnitude.to_string()
} else {
text
}
}
///|
/// Thirteen lowercase hexadecimal digits, zero padded: the mantissa fraction of
/// a double, which `float.hex` always writes out in full.
fn hex13(frac : UInt64) -> String {
let alphabet = "0123456789abcdef"
let sb = StringBuilder()
let mut i = 12
while i >= 0 {
let nibble = ((frac >> (i * 4)) & 0xFUL).to_int()
sb.write_char(alphabet.get_char(nibble).unwrap())
i -= 1
}
sb.to_string()
}
///|
/// The hexadecimal form of a double: `float.hex`.
pub fn float_hex(d : Double) -> String {
if d.is_nan() {
return "nan"
}
if d.is_inf() {
return if d < 0.0 { "-inf" } else { "inf" }
}
let bits = d.reinterpret_as_uint64()
let sign = if bits >> 63 == 1UL { "-" } else { "" }
let biased = ((bits >> 52) & 0x7FFUL).to_int()
let frac = bits & 0xF_FFFF_FFFF_FFFFUL
if d == 0.0 {
return sign + "0x0.0p+0"
}
let lead = if biased == 0 { "0x0." } else { "0x1." }
let exp = if biased == 0 { -1022 } else { biased - 1023 }
let sign_text = if exp < 0 { "-" } else { "+" }
let magnitude = if exp < 0 { -exp } else { exp }
sign + lead + hex13(frac) + "p" + sign_text + magnitude.to_string()
}