num-rational
num-rational copied to clipboard
num_rational::Ratio: Add the ability to format as a decimal
From @joshlf on December 21, 2017 20:10
Ratio's Display implementation formats as a fraction (e.g., 17/42). It would be useful if there were a way to format instead as a decimal (e.g., 0.4047619048).
Copied from original issue: rust-num/num#358
Having issue #4 for float conversions may suffice here, as you could convert and then use any floating point formatting you like. Issue #1 for more formatting traits is also related.
It'd be nice to have #1 so that you could print in full precision, but having float conversions would certainly be a good start, and probably good enough for most use cases.
It'd be nice if we could somehow format the repeating part of the ratio, e.g. 1/22 becomes 0.0_45.
Of course, this would not be very trivial to implement and the floating-point conversion is probably best for most cases.
A related concern is infinite representations. E.g., how do you print 1/3? I think the best would be to have at least some mechanism of getting a precise string representation if possible (i.e., a method that returns Option<String> and None if it's an infinite representation), and then maybe the default formatter (fmt::Display) could just fall back to the float representation or a truncated representation or something if it couldn't print with full accuracy.
Every rational number's fractional part is either finite or repeating. You can print 1/3 exactly as 0.(3).
Note however that the repeating part of x/n might be up to n-1 digits long! (see A051626) This can be impractical to display even for Rational32, nevermind Rational64 or BigRational.
True. We could limit it by printing sth like 0.3431(538…). But in the absence of arbitrary formatting parameters, we would need a proxy type to change printing behaviour. As far as I'm aware cycle detection requires allocation as well.
Any progress on that? I'd like to implement Gauss–Legendre algorithm to calculate Pi numbers, and I need to print the number as a decimal. I'd like to have something like print!("{:500}", my_num) which would print first 500 decimal digits. It would solve the problem with infinite decimal representation.
I'm not aware that anyone has worked on this.
I have not worked on this.
I started to work on some small solution for personal needs recently. I'll try to clean it up and send it as a pull request for review and maybe adding it to the crate later.
It's nothing fancy though - just printing decimal representation to a specified precision(eg. 42 places after separator).
I just finished refactorization of my code. But now I'm having some problems while trying to integrate it into the fmt method in the crate.
I have the feeling that adding a lot of traits dependencies is not a good practice. But currently my code is using multiplication, division and modulo of two Ratio<T> and power of Ratio<T> to the usize. I have another, earlier implementation which doesn't use almost any of those traits, but instead have couple of loops which I would prefer to avoid.
I`ll try to make one of those work(or at least compile) in the next few days.
EDIT: I've managed to compile it and make it pass some new tests. Pull request is now on.
Did you end up putting up a PR? I had an idea - maybe this should be a method that returns an option? E.g., fn decimal(&self) -> Option<String>. Maybe also take a base parameter so it doesn't have to be base-10.
The PR is hanging with its next version - as_decimal as a new method.
Using Option sound best - I'll try to make it so in a next few days and push an update to the same branch as the PR.
Changing it to use other base shouldn't be a big change, but then we should probably also change the name of the method. Any ideas?
EDIT[2018-08-21]:
Made change to have Option, but I'm not sure how important it is since we always return a String. That's why I still didn't push it to the repo(and PR).
Hi folks. I implemented a division module (with 2 coroutines and fn divide_to_string) for my project and I could contribute it to this crate if you are to consider that. The main features would be as follows:
- The module implements division of two generic integers into a decimal numeral
- The integers should implement following traits:
Clone + Integer + From<u8> + ToPrimitive + CheckedMul + DivAssign + SubAssign, so works gracefully withBigUintfor example. - Lossless division, linear complexity, no heap allocations, no stack overflows
- Implemented as a coroutine producing a separate
u8for every digit in the final number, so potentially some formatting could be applied on the fly fn divide_to_string<I>(dividend: I, divisor: I, mut precision: usize) -> Result<String, DivisionError>
Here are some examples:
assert_eq!(÷_to_string(807, 31, 4).unwrap(), "26.0322");
assert!(match divide_to_string(1, 0, 1).err().unwrap() {
DivisionError::DivisionByZero => true,
_ => false
});
BigUint example:
let num = "820123456789012345678901234567890123456789";
let den = "420420420420240240420240420240420420420";
let result_1024 = "1950.7222222204153880534352079149419688493160244330759069204925259490806291881834407646199555744655166719234903156227406500953778757619920899563294795746797267246703798051247915744867884912121330007705286911209791422213223230426822190127666529437572025264829201539629594175021000386486667365851813562015885600393107707737453721144405603009059457352854387023006301508907770762997683254372534811586343569328131455924964081595076622200116354403760742833073621862325616259444084808295834752749082040590201012701034118255745516514972270054835928011374231619434684843847682844123336846713100440285930668493675706096464476187809105398269815519780303174023949885603320678109566772031394249633001137109155600514761384776616827086908396960584246277151426685095767130738996420461009015758184659365258827537226581854494195009714400547427050697946126012192691851549545189173091941689299722345297086508711845865506663262915436874050594522308464492248968916100678819937355384703664061966410613989688966690413319849627099468906113991173042101218";
let asrt1 = divide_to_string(
BigUint::from_str_radix(num, 10).ok().unwrap(),
BigUint::from_str_radix(den, 10).ok().unwrap(),
1024
).ok();
assert_eq!(asrt1.unwrap(), result_1024);
Here is the module itself: https://github.com/dnsl48/fraction/blob/0.4.x/src/division.rs
If you are interested, I could prepare a PR.
@dnsl48 - there's an open PR in #37, so it would be great if you could collaborate there.
Fair enough. @Hazardius that's your PR, do you feel like reworking it completely? Your call.
@dnsl48 I'm unable to work on this before this weekend, when I was planning to fix things pointed out by cuviper . I'm sure I won't be able to rework code in that PR completely on my own, so I wouldn't mind if you start from scratch. Just let me know so I could close it.
@dnsl48, @Hazardius, to be clear, I was not making a judgement of which of you had a better approach. I haven't compared your work in any depth. I'm just hoping that as two interested parties, you can work together to come to the best implementation.
@cuviper I wouldn't mind judging my code as poor. I think about it that way. ;) I'll be happy to read through any PR solving that issue and maybe do some smaller changes, but right now I'm having a hard time finding a moment to focus on reworking my code completely(except for [maybe] weekends - so I'll try to read dnsl48's code this weekend after I fix things pointed out by you).
I've just found an issue in my code that could make overflows due to some corner cases, e.g.
Ratio<u8>(177, 253).
I'll have to deal with it first.
Sorry @Hazardius, my implementation is too different so I made a separate PR. It's also Hacktoberfest going on, so I feel that a separate PR would be a fair thing in this situation. I'd be glad if you could have a review of my PR though, and give any feedback you can think of.
I have not read the entire thread, but why not using something like
format!("{:.2}",ratio.num() as f64 / ratio.denum() as f64)
where you can change the fraction amount by replacing the format string?
@rudolfschmidt because I may want to have 100 or more significant digits in answer, which is not possible with f64, decimal or any other standard type.
@rudolfschmidt because I may want to have 100 or more significant digits in answer, which is not possible with f64, decimal or any other standard type.
I agree that the result is just an approximation, maybe enough for visual purposes in most cases.
I am open to better solutions that I can apply to my own code as well.
In my Java days, I used to convert into double that should be an f64 in Rust, divided and printed the output.
Let's me show some example.
Assume we are writing SuperPI and use num-rational for that purpose. Now we calculated first 1000 digits of PI. The question now is how do we print it? We cannot use approximation because it goes against the whole purpose of the app.
You can always cast to f64 to get approxmation but there are plenty cases when you need significant digits.
I am open to better solutions that I can apply to my own code as well.
If you need it lossless, that functionality is implemented in the fraction module.
type D = fraction::Decimal;
let result = D::from(0.5) / D::from(0.3);
assert_eq!(format!("{:.4}", result), "1.6666");
It's open sourced. Anyone willing can check out the code and make a PR for this repo, if you'd like. (P.S. sorry, I don't have time to do that myself)
I'm interested in implementing this feature and creating a PR, however, I have a different idea on the API. I prefer to integrate the formatting into std::fmt with its precision parameter. We can either abuse the Pointer format or change the LowerExp/UpperExp to display decimal digits, and use the alternate flag to display cycle or not.
My proposal to abuse Pointer format:
format!("{:p}", Rational32::new(1, 3))->1.3333333333(we should have a default length for the decimals, let's say 10. We can also choose to display to full length if the rational doesn't have a repeating cycle)format!("{:.4p}", Rational32::new(1, 3))->1.3333format!("{:#p}", Rational32::new(1, 3))->1._3(use_to split the cycle, any marker will work)format!("{:#.4p}", Rational32::new(1, 3))->1._3_3_3_3(display multiple cycle marker)format!("{:p}", Rational32::new(123, 456))->1.2697368421format!("{:.6p}", Rational32::new(123, 456))->1.269736format!("{:#p}", Rational32::new(123, 456))->1.269_7368421...(the repeating cycle is 736842105263157894, which doesn't fit in the default 10 digits, thus a ellipsis is printed. Finding only the start of the cycle could be hard to implement, if so, we can leave it the same as{:p})format!("{:#.6p}", Rational32::new(123, 456))->1.269_736...
To use LowerExp/UpperExp, we can either use them as intended (display in scientific form), or abuse them as above. I personally prefer abuse Pointer format and use LowerExp/UpperExp to display in scientific form (rather than the current behavior, which displays the numerator and denominator separatly in scientific form) at the same time.
If this idea could get some likes, I'm happy to try to implement it and make a PR.
A reasonable modification to my proposal above is to print ... whenever the decimal is not finite, and append a second marker to the fraction to indicate the end of cycle:
format!("{:p}", Rational32::new(1, 3))->1.3333333333...format!("{:.4p}", Rational32::new(1, 3))->1.3333...format!("{:#p}", Rational32::new(1, 3))->1._3_
IMHO, since it is acceptable in some places to put parentheses around the repeatand, I would personally prefer that over using underscores. I feel like abusing Unicode or other methods are probably less than ideal, but this at least will help indicate the repeatand just as well.