///|
/// Minimal arbitrary-precision floating literal backed by `symnum`'s
/// mpmath-compatible `Mpf`.
pub struct Float {
value : @symnum.Mpf
prec : Int
} derive(Eq)
///|
fn float_default_prec(prec : Int) -> Int {
if prec > 0 {
prec
} else {
53
}
}
///|
fn float_common_prec(lhs : Float, rhs : Float) -> Int {
if lhs.precision() > rhs.precision() {
lhs.precision()
} else {
rhs.precision()
}
}
///|
fn float_promote(value : Float, prec : Int) -> Float {
if value.precision() >= prec {
value
} else {
Float::from_mpf(
@symnum.mpf_pos(value.to_mpf(), prec, @symnum.round_nearest),
prec~,
)
}
}
///|
/// Wrap an existing raw `Mpf` together with the precision it should carry in
/// Symbit's core object layer.
pub fn Float::from_mpf(value : @symnum.Mpf, prec? : Int = 53) -> Float {
{ value, prec: float_default_prec(prec) }
}
///|
/// Parse a decimal string into a core `Float`.
pub fn Float::from_str(
text : String,
prec? : Int = 53,
rnd? : @symnum.RoundMode = @symnum.round_nearest,
) -> Float raise @symnum.MpfError {
let p = float_default_prec(prec)
{ value: @symnum.from_str(text, prec=p, rnd~), prec: p }
}
///|
/// Convert a machine `Double` into a core `Float`.
pub fn Float::from_double(
value : Double,
prec? : Int = 53,
rnd? : @symnum.RoundMode = @symnum.round_nearest,
) -> Float {
let p = float_default_prec(prec)
{ value: @symnum.from_float(value, prec=p, rnd~), prec: p }
}
///|
/// Convert an exact rational into a core `Float` at the requested precision.
pub fn Float::from_rational(
num : BigInt,
den : BigInt,
prec : Int,
rnd? : @symnum.RoundMode = @symnum.round_nearest,
) -> Float raise @symnum.MpfError {
let p = float_default_prec(prec)
{ value: @symnum.from_rational(num, den, p, rnd~), prec: p }
}
///|
/// Convert a `BigRational` into a floating literal.
pub fn Float::from_exact(
value : @symnum.BigRational,
prec? : Int = 53,
rnd? : @symnum.RoundMode = @symnum.round_nearest,
) -> Float raise @symnum.MpfError {
let p = float_default_prec(prec)
Float::from_rational(value.numerator(), value.denominator(), p, rnd~)
}
///|
/// Construct a floating literal from an integer.
pub fn Float::from_int(value : Int, prec? : Int = 53) -> Float {
let p = float_default_prec(prec)
{ value: @symnum.from_int(value), prec: p }
}
///|
/// Return the underlying raw `Mpf` payload.
pub fn Float::to_mpf(self : Float) -> @symnum.Mpf {
self.value
}
///|
/// Return the tracked binary precision.
pub fn Float::precision(self : Float) -> Int {
self.prec
}
///|
/// True when the underlying payload is finite.
pub fn Float::is_finite(self : Float) -> Bool {
@symnum.is_finite(self.value)
}
///|
/// Convert the floating literal back into an exact rational pair when finite.
pub fn Float::to_rational(
self : Float,
) -> (BigInt, BigInt) raise @symnum.MpfError {
@symnum.to_rational(self.value)
}
///|
/// Convert the floating literal to a machine `Double`.
pub fn Float::to_double(
self : Float,
strict? : Bool = false,
rnd? : @symnum.RoundMode = @symnum.round_nearest,
) -> Double raise @symnum.MpfError {
@symnum.to_float(self.value, strict~, rnd~)
}
///|
/// Render the floating literal with a stable decimal representation.
pub fn Float::format(self : Float, dps? : Int) -> String {
let digits = match dps {
Some(v) if v > 0 => v
_ => if self.prec < 5 { 0 } else { @symnum.prec_to_dps(self.prec) }
}
@symnum.to_str(self.value, dps=digits) catch {
_ => ""
}
}
///|
pub fn Float::reciprocal(self : Float) -> Float raise @symnum.MpfError {
Float::from_mpf(
@symnum.mpf_div(@symnum.fone, self.value, self.prec, @symnum.round_nearest),
prec=self.prec,
)
}
///|
pub impl Add for Float with add(self, other : Float) -> Float {
let p = float_common_prec(self, other)
let lhs = float_promote(self, p)
let rhs = float_promote(other, p)
Float::from_mpf(
@symnum.mpf_add(lhs.to_mpf(), rhs.to_mpf(), p, @symnum.round_nearest),
prec=p,
)
}
///|
pub impl Mul for Float with mul(self, other : Float) -> Float {
let p = float_common_prec(self, other)
let lhs = float_promote(self, p)
let rhs = float_promote(other, p)
Float::from_mpf(
@symnum.mpf_mul(lhs.to_mpf(), rhs.to_mpf(), p, @symnum.round_nearest),
prec=p,
)
}
///|
pub impl Neg for Float with neg(self) -> Float {
Float::from_mpf(
@symnum.mpf_neg(self.to_mpf(), self.prec, @symnum.round_nearest),
prec=self.prec,
)
}
///|
pub impl Show for Float with to_string(self) {
self.format()
}
///|
pub impl Show for Float with output(self, logger : &Logger) -> Unit {
logger.write_string(self.to_string())
}
///|
/// Top-level convenience wrapper matching the rest of `symcore`.
pub fn float_from_str(
text : String,
prec? : Int = 53,
rnd? : @symnum.RoundMode = @symnum.round_nearest,
) -> Float raise @symnum.MpfError {
Float::from_str(text, prec~, rnd~)
}
///|
/// Top-level convenience wrapper from `Double`.
pub fn float_from_double(
value : Double,
prec? : Int = 53,
rnd? : @symnum.RoundMode = @symnum.round_nearest,
) -> Float {
Float::from_double(value, prec~, rnd~)
}
///|
/// Top-level convenience wrapper from an exact rational.
pub fn float_from_exact(
value : @symnum.BigRational,
prec? : Int = 53,
rnd? : @symnum.RoundMode = @symnum.round_nearest,
) -> Float raise @symnum.MpfError {
Float::from_exact(value, prec~, rnd~)
}