Crates.io | astro_nalgebra |
lib.rs | astro_nalgebra |
version | 0.1.1 |
source | src |
created_at | 2024-01-03 05:13:00.323818 |
updated_at | 2024-01-03 18:15:33.384267 |
description | Implementation of astro-float for nalgebra |
homepage | |
repository | https://github.com/benjamin-cates/astro_nalgebra |
max_upload_size | |
id | 1087049 |
size | 63,325 |
astro_float
for nalgebra
This package implements the traits required to use the linear algebra library nalgebra on the type BigFloat
in astro_float. This library exports it's own type [BigFloat<CTX>
] where CTX
is a computational context that stores precision and rounding mode.
Using this library with a precision and rounding mode that is known at compile-time is straight forward using the [ConstCtx
] computational context. It is recommended to store BigFloat<ConstCtx<P,RM>>
in a type alias so the code is more readable.
use astro_nalgebra::{BigFloat, ConstCtx, RoundingMode};
use nalgebra::Vector2;
// This defines a type that has a precision upper bound of
// 1024 bits in the mantissa and no explicit rounding mode
type BF1024 = BigFloat<ConstCtx<1024>>;
// See the documentation on ConstCtx for how to specify a rounding mode
fn main() {
let two: BF1024 = "2".parse().unwrap();
let six: BF1024 = "6".parse().unwrap();
let vec: Vector2<BF1024> = Vector2::new(two,six);
let seven: BF1024 = "7".parse().unwrap();
// Prints [2/7, 6/7] as decimals until 1024 bits
println!("{}", vec / seven);
}
While it is completely allowed to name the type something like f1024
, it does technically break the floating point naming scheme because the type BigFloat<ConstCtx<64>>
has 64 bits in the mantissa, while types like f64
only have 52 bits in the mantissa with 12 bits reserved for sign and exponent. So f64
and BigFloat<ConstCtx<64>>
are not the same.
Dynamic precision is more tricky to implement because some methods outlined in nalgebra::RealField
do not have any arguments, so the precision has to be stored in the type. However run-time determined variables cannot be stored in a const generic, so there has to be a dummy type with the methods get_prec
and get_rm
. There is a macro to quickly define this dummy type which references a global, thread-safe OnceLock
that has to be set at runtime.
Example:
use astro_nalgebra::{BigFloat, make_dyn_ctx, RoundingMode};
// This macro takes in the name of a dynamic context (the new type to be made)
// And the name of a global OnceLock to store the precision and rounding mode.
// The name of this global variable is not important, it just has to be unique
// within the scope of the macro call.
make_dyn_ctx!(DynCtx, DYN_CTX_CELL);
type DynFloat = BigFloat<DynCtx>;
fn main() {
let precision = 88;
let rounding_mode = RoundingMode::None;
// Sets the precision and rounding mode of the DynCtx context.
// This method can only be called once or it will panic.
DynCtx::set(precision, rounding_mode);
let num: DynFloat = "120".parse().unwrap();
}
There are two possible ways this library could have been implemented:
astro_float::BigFloat
that stored precision in the struct itself.The only way to guarantee proper accuracy is to use the latter technique. Here is an example of where the former technique would break:
use nalgebra::RealField;
fn mul_by_pi<T: RealField>(val: T) -> T {
val * T::pi()
}
If the desired precision of the calculation was stored in val, then the call to BigFloat::pi()
would not be able to access the precision because RealField::pi()
does not take any arguments. Therefore, the desired precision has to be stored in the type itself.