dnsl48 / fraction Goto Github PK
View Code? Open in Web Editor NEW[Rust] Lossless fractions and decimals; drop-in float replacement
License: Apache License 2.0
[Rust] Lossless fractions and decimals; drop-in float replacement
License: Apache License 2.0
It would be nice to have api for directly converting to floats, since conversion cannot fail (as far as I know)
In the following example, the computation of g_rad
will cause an overflow in either of the functions. This overflow error does not occur when compiled in release mode because rust does not do bound checking in release mode.
extern crate fraction;
use crate::fraction::GenericDecimal;
pub type Decimal = GenericDecimal<u128, u16>;
use std::f64::consts::TAU;
fn as_single_call() -> Decimal {
let tt = -0.0000000000002962836210611707;
let circ_ratio = Decimal::from(TAU) / Decimal::from(360.0);
let multiplication = Decimal::from(35_999.050 * tt);
let sum = Decimal::from(357.528) + multiplication;
dbg!(sum);
dbg!(circ_ratio);
let g_rad = circ_ratio * sum; // <==== This is the line which fails (line 79 in my editor)
g_rad
}
fn as_several_calls() -> Decimal {
let tt = -0.0000000000002962836210611707;
let g_rad = (Decimal::from(TAU) / Decimal::from(360.0))
* (Decimal::from(357.528) + Decimal::from(35_999.050 * tt));
g_rad
}
fn main() {
let sgl = as_single_call();
let svl = as_several_calls();
println!("{}\n{}", sgl, svl);
}
Output in release mode:
RUST_BACKTRACE=1 cargo run --release
Compiling paraod v0.1.0 (/home/chris/Workspace/rust/paraod)
Finished release [optimized] target(s) in 0.74s
Running `target/release/paraod`
[src/main.rs:76] sum = GenericDecimal(357.527999989334071111237863 | prec=24; Rational(Plus, Ratio { numer: 357527999989334071111237863, denom: 1000000000000000000000000 }); 357.527999989334071111237863)
[src/main.rs:77] circ_ratio = GenericDecimal(0.017453292519943 | prec=15; Rational(Plus, Ratio { numer: 3141592653589793, denom: 180000000000000000 }); 0.01745329251994329444444444444444)
0.832634730828580153746246
0.832634730828580153746246
Output in debug mode:
RUST_BACKTRACE=1 cargo run
Compiling paraod v0.1.0 (/home/chris/Workspace/rust/paraod)
Finished dev [unoptimized + debuginfo] target(s) in 0.40s
Running `target/debug/paraod`
[src/main.rs:76] sum = GenericDecimal(357.527999989334071111237863 | prec=24; Rational(Plus, Ratio { numer: 357527999989334071111237863, denom: 1000000000000000000000000 }); 357.527999989334071111237863)
[src/main.rs:77] circ_ratio = GenericDecimal(0.017453292519943 | prec=15; Rational(Plus, Ratio { numer: 3141592653589793, denom: 180000000000000000 }); 0.01745329251994329444444444444444)
thread 'main' panicked at 'attempt to multiply with overflow', /home/chris/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/arith.rs:323:1
stack backtrace:
0: rust_begin_unwind
at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39/library/std/src/panicking.rs:475
1: core::panicking::panic_fmt
at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39/library/core/src/panicking.rs:85
2: core::panicking::panic
at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39/library/core/src/panicking.rs:50
3: <u128 as core::ops::arith::Mul>::mul
at /home/chris/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/arith.rs:316
4: <num_rational::Ratio<T> as core::ops::arith::Mul>::mul
at /home/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/num-rational-0.2.4/src/lib.rs:766
5: <fraction::fraction::GenericFraction<T> as core::ops::arith::Mul>::mul
at /home/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/fraction-0.6.3/src/fraction/mod.rs:1153
6: <fraction::decimal::GenericDecimal<T,P> as core::ops::arith::Mul>::mul
at /home/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/fraction-0.6.3/src/decimal/mod.rs:130
7: paraod::as_single_call
at ./src/main.rs:79
8: paraod::main
at ./src/main.rs:92
9: core::ops::function::FnOnce::call_once
at /home/chris/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Thanks for the work!
I wanted to point out that there are two more traits to implement from the num_traits.
num_traits::identities::ConstOne
and
num_traits::identities::ConstZero
I would do a PR if you accept implementing these.
They should be the same as the respective One
and Zero
traits implementation.
fraction
version: 0.6.3
For a project that uses fraction
I want to implement scientific notation printing of numbers. Sometimes get_precision()
does not reflect the actual precision currently used (for example 1 / 100
vs from_decimal_str("0.001")
), but using calc_precision()
updates this. However this function never terminates for 1 / 3
.
use fraction::BigDecimal;
fn main() {
let x = BigDecimal::from(1) / BigDecimal::from(3);
dbg!(x.calc_precision());
}
Hi Serge, thanks for all your contributions to open source.
I just wanted to let you know that you're eligible to claim funding for your package from our sponsor Sentry. Let me know if this is something you'd like to claim via thanks.dev.
In https://docs.rs/fraction/0.6.2/fraction/prelude/index.html, the links to
are all broken.
Hi, I am encountering a problem with the Fraction crate where the simplification of the fraction causes a loss in data.
Here is the explanation:
I need to keep track of a percentage over time. The percentage is for example Bad apples out of the total apples. So i can keep track of the percentage of bad apples.
Let's say we iterate over the apple for each bad apple i increment the denominator AND the nominator. if we start with a bad apple it is 1/1 so 100%, next round a good apple, so i increment only the denominator: 1/2 so 50%.
Now we start with a good apple. We have 0/1 so 0%. And if we have a second good apple after that. I still increment only the denominator. This makes 0/2. but it is simplified with 0/1 and i just lost track of how much apples are bad.
I hope you understand what i mean and that it is relevant.
Thank you
add clippy
checks on CI stage
Other crates may expect numbers of types f32
or f64
. Does a way exist to convert a Fraction
to a f64
without using a string as an intermediate representation?
I use this crate to convert an f64
into an fps value (video framerate) consisting of u32
numerator and denominator. The documentation for Ratio::<u32>::from_f64()
reads:
Converts a
f64
to return an optional value of this type. If the value cannot be represented by this type, thenNone
is returned.
I did a few tests and transformed your explanation into the following doc comment for my Fps::from_f64()
function:
Panics if, irrationally for a frame rate, the value is so large that a
u32
numerator can't describe it.
Is that accurate and does it cover everything?
My observations (using pi, shifting the decimal point):
0.0000000000314159
gives me numerator: 0, denominator: 1
.1
. After that, you get None
.I wondered whether there can be a solution (a fraction) that's so imprecise (not accurately covering enough f64
digits) that "the value cannot be represented by" the Ratio<u32>
?
If my description concerning the numerator only is more accurate, maybe your doc comment should also describe it that way to not leave questions open.
It would be useful if you implemented the Sum trait in order to use the .sum() on an iterator over a vec of fractions.
At the moment it fails with the following error:
the trait bound `fraction::fraction::GenericFraction<u64>: std::iter::Sum` is not satisfied
the trait `std::iter::Sum` is not implemented for `fraction::fraction::GenericFraction<u64>`rustc(E0277)
When I want to use Fraction in nalgebra (a linear algebra library), there are some traits that do not meet the requirements:
fn main() {
type Fraction = fraction::Fraction;
type Matrix = nalgebra::Matrix2<Fraction>;
let a = Matrix::new(
Fraction::from(1),
Fraction::from(2),
Fraction::from(3),
Fraction::from(4),
);
println!("{:.3}", a.try_inverse().unwrap());
}
Error message:
--> src\main.rs:15:25
|
15 | println!("{:.3}", a.try_inverse().unwrap());
| ^^^^^^^^^^^ method cannot be called due to unsatisfied trait bounds
53 | pub enum GenericFraction<T>
| ---------------------------
| |
| doesn't satisfy `<_ as SimdValue>::Element = GenericFraction<u64>`
| doesn't satisfy `<_ as SimdValue>::SimdBool = bool`
| doesn't satisfy `GenericFraction<u64>: ComplexField`
| doesn't satisfy `GenericFraction<u64>: Field`
| doesn't satisfy `GenericFraction<u64>: FromPrimitive`
| doesn't satisfy `GenericFraction<u64>: SimdValue`
| doesn't satisfy `_: SubsetOf<GenericFraction<u64>>`
| doesn't satisfy `_: SupersetOf<f64>`
|
= note: the following trait bounds were not satisfied:
`GenericFraction<u64>: ComplexField`
`<GenericFraction<u64> as SimdValue>::SimdBool = bool`
which is required by `GenericFraction<u64>: ComplexField`
`<GenericFraction<u64> as SimdValue>::Element = GenericFraction<u64>`
which is required by `GenericFraction<u64>: ComplexField`
`GenericFraction<u64>: Field`
which is required by `GenericFraction<u64>: ComplexField`
`GenericFraction<u64>: SimdValue`
which is required by `GenericFraction<u64>: ComplexField`
`GenericFraction<u64>: FromPrimitive`
which is required by `GenericFraction<u64>: ComplexField`
`GenericFraction<u64>: simba::scalar::subset::SupersetOf<f64>`
which is required by `GenericFraction<u64>: ComplexField`
`GenericFraction<u64>: simba::scalar::subset::SubsetOf<GenericFraction<u64>>`
which is required by `GenericFraction<u64>: ComplexField`
Is there any way to solve this problem?
Hi there,
I was wondering if there were a way to initialize a Decimal without passing through a string formatting?
Looking at the flamegraph of a library which does literally thousands of computations using decimal, the conversion of an f64
into a GenericDecimal<u128, u16>
takes 4.16% of the computation time (cf. attachment).
Hence, I was wondering what I could increase the performance. Thanks
We're still on postgresql ^0.15
, which is unmaintained and requires some yanked packages.
#24 64.60 error[E0658]: trait bounds other than `Sized` on const fn parameters are unstable
#24 64.60 --> /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/fraction-0.12.1/src/fraction/generic_fraction.rs:164:6
#24 64.60 |
#24 64.60 164 | impl<T> GenericFraction<T>
#24 64.60 | ^
#24 64.60 ...
#24 64.60 275 | pub const fn new_raw(num: T, den: T) -> GenericFraction<T> {
#24 64.60 | ---------------------------------------------------------- function declared as const here
#24 64.60 |
#24 64.60 = note: see issue #93706 <https://github.com/rust-lang/rust/issues/93706> for more information
Now that TryFrom and TryInto are stable, the internal replacement should be deprecated in their favour, as hinted at by the documentation.
Since Rust supports full Unicode and because of the name of this crate, I think the fraction slash '⁄'
is a better delimiter than the solidus '/'
for display purposes. In any case, it should not be an error in parsing to use:
Here is a discussion I started on Rust internals. and here is a SO unicode discussion on fractions.
When using decimal to replace is f64/32, some times we need to send the data through network or save them into file. Serde helps a lot. OrderedFloat supports serde. Do you have plan to support it?
I get a panic when I run the following code (with fraction=0.6.2):
use fraction::Decimal;
fn main() {
let d1 = Decimal::from("0.0002") * Decimal::from("9876.54321");
let d2 = Decimal::from("51.53023471399515");
let d3 = d1 + d2;
println!("{:?} + {:?} = {:?}", d1, d2, d3);
}
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/fraction-test`
thread 'main' panicked at 'attempt to multiply with overflow', /rustc/eae3437dfe991621e8afdc82734f4a172d7ddf9b/src/libcore/ops/arith.rs:318:45
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
I need precisely the GenericDecimal
type in my (embedded) project but unfortunately I don't have a heap available and fraction
links towards std
.
Are there any plans to support (a limited set) of this crate for no-std
environments?
Hi, I'm adding trigonometric functions as I need them for my project. Since try_into() isn't allowed in consts and I can't think of any other way to do different values for them My current approach is to have a Consts trait with an impl for GeneralFraction, GeneralFraction, GeneralFraction and so on then I made a Trigametric trait which requires the Consts trait.
I currently just have atan() and atan2() you can view my work in the trigonometrics branch of my fork
Instead of implementing this
Line 21 in c069bda
What's your thought on this? Is there a problem with num_trait's trait that is incompatible with this crate?
[1]https://docs.rs/num-traits/0.2.12/num_traits/real/trait.Real.html
Not sure if it is intended but zeros store negatives inside. When I print a zero fraction I get -0
.
Even though I have feature "with-serde-support" on, when I implement a struct or enum that contains DynaInt or any type that uses it and place #[derive(Serialize)] on my type I get:
error: the trait bound DynaInt<u64, BigUint>: Serialize
is not satisfied
label: the trait Serialize
is not implemented for DynaInt<u64, BigUint>
Serialization seems to work fine for GenericDecimal. I'm fairly new to Rust - is there something I'm doing wrong? Or is there some reason serialization is not supported for DynaInt?
The examples from the readme aren't up to date anymore. Need to revisit all of them.
Pretty simple to reproduce: assert_eq!(BigFraction::zero(), BigFraction::neg_zero());
Well, this is kind of annoying. One of the advantages of Fractions over floating point is that you can do exact comparisons. But now we have an edge case that we have to handle before each comparison. I don't see any reason for this being desired behavior.
I'm currently writing a basic calculator with this library and I noticed at the moment there is no .pow() or .root() style functions for fractions.
Also when performing square roots should they simply be approximated or should numbers simply represent simplified square roots, popping all of the irrational roots in a vector or something like that. For example, something like this
let foo = Fraction::from(75u32);
let result = foo.sqrt();
assert_eq!(5, result.get_base_multiplicand()); // Not actually functional code just here to get a point across
assert_eq!(3, result.get_root_multipliers()[0]);
println!("{:9}", result); // -> 8.660254037
I would like to contribute I'm just unsure how as to go about tackling this problem, at the moment
Hi! Did you compare perfomance of std::f64 and your Decimal from string conversions?
Compiling fraction v0.3.5
error[E0277]: the trait bound `T: std::clone::Clone` is not satisfied
--> /Users/thill/.cargo/registry/src/github.com-1ecc6299db9ec823/fraction-0.3.5/src/lib.rs:149:21
|
149 | Rational (Sign, Ratio<T>),
| ^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `T`
|
= help: consider adding a `where T: std::clone::Clone` bound
= note: required because of the requirements on the impl of `std::hash::Hash` for `num::rational::Ratio<T>`
= note: required by `std::hash::Hash::hash`
error[E0277]: the trait bound `T: num::Integer` is not satisfied
--> /Users/thill/.cargo/registry/src/github.com-1ecc6299db9ec823/fraction-0.3.5/src/lib.rs:149:21
|
149 | Rational (Sign, Ratio<T>),
| ^^^^^^^^^ the trait `num::Integer` is not implemented for `T`
|
= help: consider adding a `where T: num::Integer` bound
= note: required because of the requirements on the impl of `std::hash::Hash` for `num::rational::Ratio<T>`
= note: required by `std::hash::Hash::hash`
error: aborting due to previous error(s)
error: Could not compile `fraction`.
Build failed, waiting for other jobs to finish...
error: build failed
Fraction::from_str("10000000000000.0000000000").unwrap()
will fail with overflow error while it can be represented without loss.
The from_str implementation use the number of digits to guard against overflow, but in this case, the fractional part trailing 0 should not be counted.
This issue arise easily when using bigdecimal::BigDecimal::to_string
as input of Fraction::from_str
(conversion between bigdecimal and fraction may looks strange, but bigdecimal is better supported by postgresql while some code use Fraction for computation).
Using version 0.8.0, this testcase panics:
#[test]
fn divide() {
fraction::division::divide_to_string(-1, 1, 10, false);
}
Panic:
thread 'tests::divide' panicked at 'internal error: entered unreachable code', C:.cargo\registry\src\github.com-1ecc6299db9ec823\fraction-0.8.0\src\division.rs:350:21
All the standard integer types support it, is there any reason to exclude it? Default value would be zero.
it could be helpful to expose num crate traits through fraction so that we could use its functionality without explicit dependency on num
crate in other packages. (e.g. from_str_radix
requires Num
trait).
I know about power and root function mentioned in #60
but are there any others?
I'm working on something, at least for my own personal use until we come up with an official solution. I haven't worked out all the details yet so won't go into them here.
Curious if you'd be open to a PR adding support for ruint in fraction
and decimal
as a num-bigint
alternative. I think the constraints would all work. ruint
supports conversions into num-bigint
, but having native support would be nice.
I remember having this problem before but I can't remember my solution.
Could we impl Into of something?
I feel like just using format!("x:.9}").parse::<f32>().unwrap()
It looks like the fraction library doesn't support parsing fractions in the format of 1/2
. I wrote a toy Postgres type (https://github.com/scott-wilson/pg_fraction), and I'd like to move converting the fraction to this crate.
If this is not currently supported, then I suggest adding support for FromStr::from_str
. Also, I recommend using nom for parsing the string, since it makes parsing strings fairly easy (see https://github.com/scott-wilson/pg_fraction/blob/e799ca9102a7f34b7a7d3241a5fcfda4a7b2be57/src/lib.rs#L31)
I'll happy set up a PR to add support for parsing fractions, and also to use nom for parsing (including updating GenericFraction::from_decimal_str to the new parser). Let me know if this is something for me to tackle, and I'll start on it this week.
cargo check
is complaining,
warning: trait objects without an explicit `dyn` are deprecated
--> src/division.rs:542:21
|
542 | writeable: &mut Write,
| ^^^^^ help: use `dyn`: `dyn Write`
|
= note: `#[warn(bare_trait_objects)]` on by default
warning: trait objects without an explicit `dyn` are deprecated
--> src/division.rs:756:36
|
756 | pub fn write_digit(writeable: &mut Write, digit: u8) -> Result<bool, DivisionError>
| ^^^^^ help: use `dyn`: `dyn Write`
warning: trait objects without an explicit `dyn` are deprecated
--> src/error.rs:56:23
|
56 | ExternalError(Box<Error + Send + Sync>)
| ^^^^^^^^^^^^^^^^^^^ help: use `dyn`: `dyn Error + Send + Sync`
warning: trait objects without an explicit `dyn` are deprecated
--> src/fraction/display.rs:192:45
|
192 | pub fn format_sign(sign: Sign, buffer: &mut fmt::Write, format: &Format) -> fmt::Result {
| ^^^^^^^^^^ help: use `dyn`: `dyn fmt::Write`
warning: trait objects without an explicit `dyn` are deprecated
--> src/fraction/display.rs:225:18
|
225 | buffer: &mut fmt::Write,
| ^^^^^^^^^^ help: use `dyn`: `dyn fmt::Write`
warning: trait objects without an explicit `dyn` are deprecated
--> src/fraction/display.rs:351:20
|
351 | V: FnOnce(&mut fmt::Write, &Format) -> Result<usize, fmt::Error>,
| ^^^^^^^^^^ help: use `dyn`: `dyn fmt::Write`
warning: trait objects without an explicit `dyn` are deprecated
--> src/fraction/display.rs:343:18
|
343 | buffer: &mut fmt::Write,
| ^^^^^^^^^^ help: use `dyn`: `dyn fmt::Write`
Finished dev [unoptimized + debuginfo] target(s) in 0.13s
I am not sure if it will break anything, but its quite a simple fix.
Fraction::floor
rounds negative Fractions upwards (towards zero), and Fraction::ceil
rounds negative fractions towards negative infinity. Since the docs says that floor
should "Returns the largest integer less than or equal to the value" I'm assuming this is unintentional
let f = Fraction::new_neg(1u8, 2u8);
println!("{} {}", f.floor(), f.ceil());
This example prints -0 -1
when the output should be -1 -0
Hi.
I can into this while working on a chemical equation balancer, this may be a bug, or it might be something I'm going wrong.
I've gotten all of the matrix stuff done, and it yields a set of coefficients. The ones in my example are as follows: [-0.5, -0.33333334, -0.16666667, 1.0]
.
I create a fraction from these numbers, as follows:
let f = Fraction::from(*coeff);
if let Some(d) = f.denom() {
denoms.push(*d as f32);
} else {
panic!("Unable to obtain a valid denominator.");
}
Which should be correct, I believe.
The code produces a list of denominators as follows: [2.0, 50000000.0, 100000000.0, 1.0]
The first and last entries look fine, but there definitely appears to be something wrong with the other two entries. If this isn't a bug, then what am I doing wrong here and how can I fix it?
Thanks in advance.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.