// Floats: reading them, writing them, and rounding them the way Python does.
///|
/// `float(text)`: Python's float literal syntax.
///
/// Accepts `inf`, `infinity` and `nan` in any case with an optional sign,
/// decimal forms with or without a fractional part or an exponent, single
/// underscores between digits, and surrounding whitespace. Hexadecimal literals
/// are not float syntax; `float_of_hex` reads those.
pub fn parse_float(text : StringView) -> Double raise Refused {
let s = text.to_owned()
let n = s.length()
let mut i = 0
while i < n && is_space(s.get_char(i).unwrap()) {
i += 1
}
let mut neg = false
if i < n {
match s.get_char(i).unwrap() {
'-' => {
neg = true
i += 1
}
'+' => i += 1
_ => ()
}
}
let rest = slice(s, i, n)
let word = lowercase_ascii(rest)
if word.has_prefix("infinity") && i + 8 <= n {
let after = i + 8
if only_space_after(s, after) {
return if neg { -@double.infinity } else { @double.infinity }
}
}
if word.has_prefix("inf") && i + 3 <= n {
if only_space_after(s, i + 3) {
return if neg { -@double.infinity } else { @double.infinity }
}
}
if word.has_prefix("nan") && i + 3 <= n {
if only_space_after(s, i + 3) {
return @double.not_a_number
}
}
let int_part = StringBuilder()
let frac_part = StringBuilder()
let mut seen_digit = false
let mut underscore_ok = false
while i < n {
match s.get_char(i).unwrap() {
c =>
if is_digit(c) {
int_part.write_char(c)
seen_digit = true
underscore_ok = true
} else if c == '_' {
if !underscore_ok {
malformed(i, "underscore must sit between two digits")
}
underscore_ok = false
} else {
break
}
}
i += 1
}
if seen_digit && !underscore_ok {
malformed(i - 1, "underscore must sit between two digits")
}
if i < n && s.get_char(i) == Some('.') {
i += 1
underscore_ok = false
let mut frac_seen = false
while i < n {
match s.get_char(i).unwrap() {
c =>
if is_digit(c) {
frac_part.write_char(c)
seen_digit = true
frac_seen = true
underscore_ok = true
} else if c == '_' {
if !underscore_ok {
malformed(i, "underscore must sit between two digits")
}
underscore_ok = false
} else {
break
}
}
i += 1
}
if frac_seen && !underscore_ok {
malformed(i - 1, "underscore must sit between two digits")
}
}
if !seen_digit {
malformed(i, "expected a digit")
}
let mut exp = 0
if i < n && (s.get_char(i) == Some('e') || s.get_char(i) == Some('E')) {
i += 1
let mut exp_neg = false
if i < n {
match s.get_char(i).unwrap() {
'-' => {
exp_neg = true
i += 1
}
'+' => i += 1
_ => ()
}
}
let exp_digits = StringBuilder()
let mut exp_count = 0
let mut exp_seen = false
let mut exp_underscore_ok = false
while i < n {
match s.get_char(i).unwrap() {
c =>
if is_digit(c) {
if exp_count < 6 {
exp_digits.write_char(c)
exp_count += 1
}
exp_seen = true
exp_underscore_ok = true
} else if c == '_' {
if !exp_underscore_ok {
malformed(i, "underscore must sit between two digits")
}
exp_underscore_ok = false
} else {
break
}
}
i += 1
}
if !exp_seen {
malformed(i, "expected an exponent digit")
}
if !exp_underscore_ok {
malformed(i - 1, "underscore must sit between two digits")
}
let magnitude = parse_exp(exp_digits.to_string())
exp = if exp_neg { -magnitude } else { magnitude }
}
let mut tail = i
while tail < n && is_space(s.get_char(tail).unwrap()) {
tail += 1
}
if tail < n {
malformed(tail, "unexpected trailing character")
}
let digits = int_part.to_string() + frac_part.to_string()
let decpt = int_part.to_string().length() + exp
signed_decimal_to_double(neg, digits, decpt)
}
///|
/// Whether everything from `start` to the end of `s` is whitespace.
fn only_space_after(s : String, start : Int32) -> Bool {
let mut i = start
while i < s.length() {
if !is_space(s.get_char(i).unwrap()) {
return false
}
i += 1
}
true
}
///|
/// ASCII-only lowercasing, for keyword recognition.
fn lowercase_ascii(s : String) -> String {
let sb = StringBuilder()
let mut i = 0
while i < s.length() {
sb.write_char(s.get_char(i).unwrap().to_ascii_lowercase())
i += 1
}
sb.to_string()
}
///|
/// `0. * 10^decpt` as the nearest double.
fn signed_decimal_to_double(
neg : Bool,
digits : String,
decpt : Int32,
) -> Double {
let mut first = 0
while first < digits.length() - 1 && digits.get_char(first) == Some('0') {
first += 1
}
let body = strip_zeros(slice(digits, first, digits.length()))
if body == "0" {
return if neg { -0.0 } else { 0.0 }
}
let point = decpt - first
if point > 320 {
return if neg { -@double.infinity } else { @double.infinity }
}
if point < -400 {
return if neg { -0.0 } else { 0.0 }
}
let mantissa = @bigint.BigInt::from_string(body)
let shift = point - body.length()
if shift >= 0 {
ratio_to_double(neg, mantissa * pow10(shift), 1N)
} else {
ratio_to_double(neg, mantissa, pow10(-shift))
}
}
///|
/// `repr(float)`: the shortest string that reads back as the same double.
pub fn repr_float(d : Double) -> String {
float_short(d, 'r', 0, false, true, false, false, false)
}
///|
/// `float.fromhex`: the double a hexadecimal literal denotes.
///
/// The `0x` prefix is optional, the exponent is decimal and introduced by `p`,
/// and the rounding is the same half-to-even as every other conversion here.
pub fn float_of_hex(text : StringView) -> Double raise Refused {
let s = text.to_owned()
let n = s.length()
let mut i = 0
while i < n && is_space(s.get_char(i).unwrap()) {
i += 1
}
let mut neg = false
if i < n {
match s.get_char(i).unwrap() {
'-' => {
neg = true
i += 1
}
'+' => i += 1
_ => ()
}
}
let rest = lowercase_ascii(slice(s, i, n))
if rest.has_prefix("infinity") && only_space_after(s, i + 8) {
return if neg { -@double.infinity } else { @double.infinity }
}
if rest.has_prefix("inf") && only_space_after(s, i + 3) {
return if neg { -@double.infinity } else { @double.infinity }
}
if rest.has_prefix("nan") && only_space_after(s, i + 3) {
return @double.not_a_number
}
if i + 1 < n &&
s.get_char(i) == Some('0') &&
(s.get_char(i + 1) == Some('x') || s.get_char(i + 1) == Some('X')) {
i += 2
}
let hex_digits = StringBuilder()
let mut frac_len = 0
let mut seen_digit = false
let mut in_fraction = false
while i < n {
let c = s.get_char(i).unwrap()
if c == '.' && !in_fraction {
in_fraction = true
i += 1
continue
}
if c == '_' {
i += 1
continue
}
let v = if is_digit(c) {
c.to_int() - '0'.to_int()
} else if c >= 'a' && c <= 'f' {
c.to_int() - 'a'.to_int() + 10
} else if c >= 'A' && c <= 'F' {
c.to_int() - 'A'.to_int() + 10
} else {
-1
}
if v < 0 {
break
}
hex_digits.write_char(c.to_ascii_lowercase())
seen_digit = true
if in_fraction {
frac_len += 1
}
i += 1
}
if !seen_digit {
malformed(i, "expected a hexadecimal digit")
}
let mut exp = 0
if i < n && (s.get_char(i) == Some('p') || s.get_char(i) == Some('P')) {
i += 1
let mut exp_neg = false
if i < n {
match s.get_char(i).unwrap() {
'-' => {
exp_neg = true
i += 1
}
'+' => i += 1
_ => ()
}
}
let exp_digits = StringBuilder()
let mut exp_count = 0
while i < n && is_digit(s.get_char(i).unwrap()) {
if exp_count < 6 {
exp_digits.write_char(s.get_char(i).unwrap())
exp_count += 1
}
i += 1
}
if exp_count == 0 {
malformed(i, "expected an exponent digit")
}
let magnitude = parse_exp(exp_digits.to_string())
exp = if exp_neg { -magnitude } else { magnitude }
}
while i < n && is_space(s.get_char(i).unwrap()) {
i += 1
}
if i < n {
malformed(i, "unexpected trailing character")
}
let mantissa = @bigint.BigInt::from_string(hex_digits.to_string(), radix=16)
let scale = exp - 4 * frac_len
if scale > 2000 {
return if neg { -@double.infinity } else { @double.infinity }
}
if scale < -2000 {
return if neg { -0.0 } else { 0.0 }
}
let value = if scale >= 0 {
ratio_to_double(neg, mantissa << scale, 1N)
} else {
ratio_to_double(neg, mantissa, 1N << -scale)
}
if value.is_inf() {
raise Overflow(reason="hexadecimal value too large to represent as a float")
}
value
}
///|
/// `round(d, ndigits)`: round to `ndigits` decimal places, ties to even, on the
/// exact value of `d`.
pub fn round_float(d : Double, ndigits~ : Int32) -> Double {
if d.is_nan() {
return d
}
if d.is_inf() {
return d
}
let (neg, num, den) = double_ratio(d)
let (scaled, scale) = if ndigits >= 0 {
(div_round(num * pow10(ndigits), den), pow10(ndigits))
} else {
(div_round(num, den * pow10(-ndigits)), 1N)
}
let value = if ndigits >= 0 {
ratio_to_double(neg, scaled, scale)
} else {
ratio_to_double(neg, scaled * pow10(-ndigits), 1N)
}
value
}
///|
/// `round(d)`: the nearest integer, ties to even.
pub fn round_big(d : Double) -> @bigint.BigInt raise Refused {
if d.is_nan() {
raise Domain(reason="cannot round NaN to an integer")
}
if d.is_inf() {
raise Overflow(reason="cannot round an infinity to an integer")
}
let (neg, num, den) = double_ratio(d)
let q = div_round(num, den)
if neg {
-q
} else {
q
}
}
///|
/// `math.floor(d)`, as an integer.
pub fn floor_big(d : Double) -> @bigint.BigInt raise Refused {
if d.is_nan() {
raise Domain(reason="cannot floor NaN")
}
if d.is_inf() {
raise Overflow(reason="cannot floor an infinity")
}
let (neg, num, den) = double_ratio(d)
let q = num / den
if neg && num % den != 0N {
-q - 1N
} else if neg {
-q
} else {
q
}
}
///|
/// `math.ceil(d)`, as an integer.
pub fn ceil_big(d : Double) -> @bigint.BigInt raise Refused {
if d.is_nan() {
raise Domain(reason="cannot ceil NaN")
}
if d.is_inf() {
raise Overflow(reason="cannot ceil an infinity")
}
let (neg, num, den) = double_ratio(d)
let q = num / den
if !neg && num % den != 0N {
q + 1N
} else if neg {
-q
} else {
q
}
}
///|
/// `math.trunc(d)`, as an integer.
pub fn trunc_big(d : Double) -> @bigint.BigInt raise Refused {
if d.is_nan() {
raise Domain(reason="cannot truncate NaN")
}
if d.is_inf() {
raise Overflow(reason="cannot truncate an infinity")
}
let (neg, num, den) = double_ratio(d)
let q = num / den
if neg {
-q
} else {
q
}
}
///|
/// `float.as_integer_ratio`: the exact numerator and denominator of `d`.
pub fn as_integer_ratio(
d : Double,
) -> (@bigint.BigInt, @bigint.BigInt) raise Refused {
if d.is_nan() {
raise Domain(reason="cannot convert NaN to a ratio")
}
if d.is_inf() {
raise Overflow(reason="cannot convert an infinity to a ratio")
}
let (neg, num, den) = double_ratio(d)
let common = gcd(num, den)
let numerator = num / common
if neg {
(-numerator, den / common)
} else {
(numerator, den / common)
}
}
///|
/// `d` as an exact fraction.
pub fn as_ratio(d : Double) -> Fraction raise Refused {
if d.is_nan() {
raise Domain(reason="cannot convert NaN to a fraction")
}
if d.is_inf() {
raise Overflow(reason="cannot convert an infinity to a fraction")
}
let (neg, num, den) = double_ratio(d)
make(if neg { -num } else { num }, den)
}