-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
format_as_decimal implementation #39
Conversation
For now - just took a quick look on it and it looks really nice. I'll try to read the whole PR with more focus this week, but the next two nights I'm working overtime so I'm not sure if I can to do it before Wednesday evening. |
For my use cases I would much prefer having the precision be a maximum, and not have trailing zeroes. I would love to also have a function like |
Since |
Thanks a lot, this is working great in my trial 😄 |
a12b67c
to
108d68f
Compare
Well, after another thought it feels to be more flexible if we return a remainder of the division, not an indicator whether it's complete or not. And then, outside we can check with |
Whoever may need this functionality while the PR is making it, can use the fraction crate, which is the origin of the code in this PR. Particularly you may look at fraction::division::divide_to_string or the other functions in the division module. |
On trailing zeros, my expectation is that we'd behave just as fn main() {
println!("{:.6}", 1.0/5.0);
println!("{:.6}", 1.0/6.0);
println!("{:.6}", 1.0/7.0);
}
|
Yes, that sounds reasonable. However, that implies we can achieve a result without trailing zeroes with not specifying a particular precision, which is not the case in this particular PR. To do so we'll have to have a hardcoded default precision which we use when a user doesn't pass it through the format parameter. On the other hand, I believe that leading and trailing zeroes are a presentational detail and when necessary could be achieved through using That said, if we make the output without trailing zeroes possible for only undefined precision, we're limiting the possible range of values to the default precision value, which is less flexible. I cannot see the obvious winner in those 2 approaches. We either choose to be aligned with the My personal preference was to be more flexible, so that I didn't want to bloat this PR with the presentation details implementation and postponed it to a separate PR. However, I will completely understand if we choose to be aligned with the stdlib, sacrificing the flexibility. |
On trailing zeroes... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I can live with the flag for trailing zeros, thanks.
Rounding is an issue though.
Can you also run this through cargo fmt
?
fn format_as_decimal() { | ||
use super::{Ratio, Zero}; | ||
|
||
let data: [(u8, u8, &'static str, bool); 60] = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This table can be excluded from formatting: #[cfg_attr(rustfmt, rustfmt_skip)]
(The newer #[rustfmt::skip]
won't work with older rustc.)
(1u8, 9u8, "0.1111111111111111111111111111111111111111111111111111111111111111", false), | ||
(1u8, 8u8, "0.125", true), | ||
(1u8, 7u8, "0.1428571428571428571428571428571428571428571428571428571428571428", false), | ||
(1u8, 6u8, "0.1666666666666666666666666666666666666666666666666666666666666666", false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We ought to be rounding values -- "0.166...667"
... which I realize is harder, but I think it's important.
remainder = remainder - red_div_rem_diff.1; | ||
|
||
(remainder, digit) | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block deserves a lot of comments, as it's much less obvious than the rest.
|
||
/// Writes the number into the output buffer in decimal representation. | ||
/// No leading zeroes (except a single zero for the integer part) | ||
/// Returns remainder of the division |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I meant to comment here too -- it's not really clear what this remainder represents. It's not just numer % denom
, but rather numer % (denom * 10^precision)
, right? I'm not really sure what anyone would do with that, except check that it's non-zero as you have in tests. That gets hairier if we start rounding too...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No wait, it must be (numer * 10^precision) % denom
, right? See, this is not obvious... :)
Sorry, I've lost my interest. Otherwise, whoever wants can take this PR and patch it with any formatting they desire. /// Calculate decimal whole and fractional parts
/// and pass them to the consumer
///
/// Consumer first invocation will always have the whole part of the resulting number.
/// All the following calls pass the fractional part digit by digit.
/// Consumer should return false to stop calculation before the fractional part
/// is complete.
/// Returns false if the consumer stopped calculation,
/// otherwise returns true if the fractional part has been calculated completely
fn calculate_decimal<C, E>(&self, mut consumer: C) -> Result<bool, E>
where
T: CheckedMul,
C: FnMut(T) -> Result<bool, E>
{
let (integer, mut remainder): (T, T) = self.numer().div_rem(self.denom());
if !consumer(integer)? {
return Ok(false);
}
if !remainder.is_zero() {
let divisor = self.denom();
// Build the number 10 from scratch so we can handle both signed and unsigned types (T of i8 or u8)
let mut _10 = T::one() + T::one() + T::one(); // 3
_10 = _10.clone() * _10 + T::one(); // 10
loop {
if remainder.is_zero() {
return Ok(true);
}
let (rem, digit) = remainder.checked_mul(&_10).map_or_else(
|| {
// Capacity of T is not enough to multiply the remainder by 10
let (reduced_divisor, reduced_divisor_rem) = divisor.div_rem(&_10);
let mut digit = remainder.div_floor(&reduced_divisor);
let mut remainder = remainder.clone();
remainder = remainder - digit.clone() * reduced_divisor.clone();
let mut red_div_rem_diff =
(reduced_divisor_rem.clone() * digit.clone()).div_rem(&_10);
loop {
if red_div_rem_diff.0 > remainder {
digit = digit - T::one();
remainder = remainder + reduced_divisor.clone();
red_div_rem_diff =
(reduced_divisor_rem.clone() * digit.clone()).div_rem(&_10);
} else {
break;
}
}
remainder = remainder - red_div_rem_diff.0;
remainder = remainder * _10.clone();
if red_div_rem_diff.1 > remainder {
digit = digit - T::one();
remainder = remainder + divisor.clone();
}
remainder = remainder - red_div_rem_diff.1;
(remainder, digit)
},
|mut remainder| {
let digit = remainder.div_floor(divisor);
remainder = remainder - digit.clone() * divisor.clone();
(remainder, digit)
},
);
remainder = rem;
if !consumer(digit)? {
return Ok(false);
}
}
}
Ok(true)
} |
Fixes #10
pub fn format_as_decimal
which prints into anyfmt::Write
pub fn as_decimal_string -> String
, which calculates the string length, allocates it and builds the number