///|
fn dps_to_prec_approx(dps : Int) -> Int {
if dps <= 0 {
64
} else {
dps * 4 + 20
}
}
///|
fn auto_parse_prec(s : String) -> Int {
let mut digits = 0
for c in s {
if c >= '0' && c <= '9' {
digits += 1
}
}
let mut p = dps_to_prec_approx(digits + 16)
if p < 256 {
p = 256
}
if p > 4096 {
p = 4096
}
p
}
///|
fn prec_to_dps_approx(prec : Int) -> Int {
if prec <= 0 {
15
} else {
prec * 30103 / 100000 + 1
}
}
///|
fn strip_spaces(s : String) -> String {
let mut out = ""
for c in s {
if c != ' ' && c != '\t' && c != '\n' && c != '\r' {
out = out + c.to_string()
}
}
out
}
///|
fn split_exact_two(s : String, sep : String) -> (String, String)? {
let parts : Array[String] = []
for part in s.split(sep) {
parts.push(part.to_owned())
}
if parts.length() == 2 {
Some((parts[0], parts[1]))
} else {
None
}
}
///|
fn substring(s : String, start : Int, end : Int) -> String {
if start >= end {
""
} else {
s.unsafe_substring(start~, end~)
}
}
///|
fn split_exp_string(s : String) -> (String, String) {
match split_exact_two(s, "e") {
Some((m, e)) => (m, e)
None => (s, "")
}
}
///|
fn first_diff_index(a : String, b : String) -> Int {
let min_len = if a.length() < b.length() { a.length() } else { b.length() }
let mut i = 0
while i < min_len {
if a.get_char(i) != b.get_char(i) {
break
}
i += 1
}
i
}
///|
fn mpi_from_str_a_b(
x : String,
y : String,
percent : Bool,
prec : Int,
) -> RawMpi raise MpiError {
let base_prec = if prec > 0 { prec } else { auto_parse_prec(x + y) }
let wp = base_prec + 20
let out_prec = if prec > 0 { prec } else { 0 }
let xa = @mpf.from_str(x, prec=wp, rnd=@mpf.round_floor) catch {
err => raise from_mpf_error(err)
}
let xb = @mpf.from_str(x, prec=wp, rnd=@mpf.round_ceiling) catch {
err => raise from_mpf_error(err)
}
let mut width = @mpf.from_str(y, prec=wp, rnd=@mpf.round_ceiling) catch {
err => raise from_mpf_error(err)
}
if @mpf.mpf_sign(width) < 0 {
raise ValueError("mpi_from_str: interval width must be nonnegative")
}
if percent {
let xa_abs = @mpf.mpf_abs(xa, wp, @mpf.round_ceiling)
let xb_abs = @mpf.mpf_abs(xb, wp, @mpf.round_ceiling)
let (_, mx) = @mpf.mpf_min_max([xa_abs, xb_abs]) catch {
err => raise from_mpf_error(err)
}
width = @mpf.mpf_mul(mx, width, wp, @mpf.round_ceiling)
width = @mpf.mpf_div(width, @mpf.from_int(100), wp, @mpf.round_ceiling) catch {
err => raise from_mpf_error(err)
}
}
let lo = @mpf.mpf_sub(xa, width, out_prec, @mpf.round_floor)
let hi = @mpf.mpf_add(xb, width, out_prec, @mpf.round_ceiling)
mpi_make(lo, hi)
}
///|
pub fn mpi_from_str(s : String, prec? : Int = 0) -> RawMpi raise MpiError {
let text = strip_spaces(s)
let wp = if prec > 0 { prec } else { auto_parse_prec(text) }
match split_exact_two(text, "+-") {
Some((x, y)) => return mpi_from_str_a_b(x, y, false, wp)
None => ()
}
if text.contains("(") {
if text.has_prefix("(") || !text.contains(")") {
raise ParseError("mpi_from_str: malformed interval string")
}
match split_exact_two(text, "(") {
Some((x, tail)) => {
if !tail.has_suffix(")") {
raise ParseError("mpi_from_str: malformed interval string")
}
let mut y = substring(tail, 0, tail.length() - 1)
let percent = y.has_suffix("%")
if percent {
y = substring(y, 0, y.length() - 1)
}
return mpi_from_str_a_b(x, y, percent, wp)
}
None => raise ParseError("mpi_from_str: malformed interval string")
}
}
if text.contains(",") {
if !text.contains("[") || !text.contains("]") {
raise ParseError("mpi_from_str: malformed interval string")
}
if text.has_prefix("[") {
if !text.has_suffix("]") {
raise ParseError("mpi_from_str: malformed interval string")
}
let inner = substring(text, 1, text.length() - 1)
match split_exact_two(inner, ",") {
Some((a, b)) => {
let lo = @mpf.from_str(a, prec=wp, rnd=@mpf.round_floor) catch {
err => raise from_mpf_error(err)
}
let hi = @mpf.from_str(b, prec=wp, rnd=@mpf.round_ceiling) catch {
err => raise from_mpf_error(err)
}
return mpi_make(lo, hi)
}
None => raise ParseError("mpi_from_str: malformed interval string")
}
}
match split_exact_two(text, "[") {
Some((prefix, rhs1)) =>
match split_exact_two(rhs1, ",") {
Some((y, rhs2)) =>
match split_exact_two(rhs2, "]") {
Some((z, suffix)) => {
let lo = @mpf.from_str(
prefix + y + suffix,
prec=wp,
rnd=@mpf.round_floor,
) catch {
err => raise from_mpf_error(err)
}
let hi = @mpf.from_str(
prefix + z + suffix,
prec=wp,
rnd=@mpf.round_ceiling,
) catch {
err => raise from_mpf_error(err)
}
return mpi_make(lo, hi)
}
None =>
raise ParseError("mpi_from_str: malformed interval string")
}
None => raise ParseError("mpi_from_str: malformed interval string")
}
None => raise ParseError("mpi_from_str: malformed interval string")
}
}
let lo = @mpf.from_str(text, prec=wp, rnd=@mpf.round_floor) catch {
err => raise from_mpf_error(err)
}
let hi = @mpf.from_str(text, prec=wp, rnd=@mpf.round_ceiling) catch {
err => raise from_mpf_error(err)
}
mpi_make(lo, hi)
}
///|
pub fn mpi_to_str_opts(
s : RawMpi,
dps? : Int = 15,
mode? : String = "brackets",
use_spaces? : Bool = true,
error_dps? : Int = 4,
left_bracket? : String = "[",
right_bracket? : String = "]",
base? : Int = 10,
binary_exp? : Bool = false,
) -> String raise MpiError {
let sp = if use_spaces { " " } else { "" }
let a_str = @mpf.to_str_opts(s.lo, dps~, base~, binary_exp~) catch {
err => raise from_mpf_error(err)
}
let b_str = @mpf.to_str_opts(s.hi, dps~, base~, binary_exp~) catch {
err => raise from_mpf_error(err)
}
match mode {
"plusminus" => {
let p = dps_to_prec_approx(dps)
let mid = mpi_mid(s, p)
let delta_half = @mpf.mpf_shift(mpi_delta(s, p), -1)
let mid_str = @mpf.to_str_opts(mid, dps~, base~, binary_exp~) catch {
err => raise from_mpf_error(err)
}
let delta_str = @mpf.to_str_opts(delta_half, dps~, base~, binary_exp~) catch {
err => raise from_mpf_error(err)
}
mid_str + sp + "+-" + sp + delta_str
}
"percent" => {
let p = dps_to_prec_approx(dps)
let mid = mpi_mid(s, p)
let delta = mpi_delta(s, p)
let percent = if @mpf.is_zero(mid) {
@mpf.fzero
} else {
let num = @mpf.mpf_mul_int(delta, 100, p + 12, @mpf.round_nearest)
let den = @mpf.mpf_mul_int(mid, 2, p + 12, @mpf.round_nearest)
@mpf.mpf_div(num, den, p + 12, @mpf.round_nearest) catch {
err => raise from_mpf_error(err)
}
}
let mid_str = @mpf.to_str_opts(mid, dps~, base~, binary_exp~) catch {
err => raise from_mpf_error(err)
}
let pct_str = @mpf.to_str_opts(percent, dps=error_dps, base~, binary_exp~) catch {
err => raise from_mpf_error(err)
}
mid_str + sp + "(" + pct_str + "%)"
}
"brackets" => left_bracket + a_str + "," + sp + b_str + right_bracket
"diff" => {
let mut left = a_str
let mut right = b_str
if left == right {
left = @mpf.to_str_opts(s.lo, dps=dps + 3, base~, binary_exp~) catch {
err => raise from_mpf_error(err)
}
right = @mpf.to_str_opts(s.hi, dps=dps + 3, base~, binary_exp~) catch {
err => raise from_mpf_error(err)
}
}
let (lm, le) = split_exp_string(left)
let (rm, re) = split_exp_string(right)
if le == re {
if lm != rm {
let i = first_diff_index(lm, rm)
let prefix = substring(lm, 0, i)
let ltail = substring(lm, i, lm.length())
let rtail = substring(rm, i, rm.length())
let exp_suffix = if le is "" { "" } else { "e" + le }
prefix +
left_bracket +
ltail +
"," +
sp +
rtail +
right_bracket +
exp_suffix
} else {
let exp_suffix = if le is "" { "" } else { "e" + le }
lm + left_bracket + right_bracket + exp_suffix
}
} else {
left_bracket + left + "," + sp + right + right_bracket
}
}
_ => raise UnsupportedError("mpi_to_str_opts: unknown mode")
}
}
///|
pub fn mpi_to_str(s : RawMpi, dps? : Int = 15) -> String raise MpiError {
mpi_to_str_opts(s, dps~)
}
///|
pub fn mpi_str(s : RawMpi, prec : Int) -> String raise MpiError {
let dps = prec_to_dps_approx(prec) + 5
mpi_to_str_opts(s, dps~)
}