Skip to content
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

Make Display for Decimal more human-friendly #477

Merged
merged 5 commits into from
Mar 11, 2023
Merged

Conversation

jobarr-amzn
Copy link
Contributor

@jobarr-amzn jobarr-amzn commented Mar 10, 2023

Description of changes:

Before this all Decimals would be displayed as e.g. 37d-2 instead of 0.37. After this decimals are displayed as ABC.DEF, ABCd3 or 0.000ABC unless the number of padding zeroes grows large, in which case it will fall back to A.BCd-E for some exponent.

This also prefers to quantify the scale of larger numbers, e.g. ABCDEFGd0 will be written as A.BCDEFGd6 instead of ABCDEFG.

"large" is henceforth "six" until someone changes it ;)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Comment on lines 301 to 304
// The index of the decimal
let i_d = len as i64 + self.exponent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "index of the decimal" mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment:

        // The index of the decimal, relative to the magnitude representation
        //       0123
        // Given ABCDd-2, the decimal gets inserted at position 2.
        let i_d = len as i64 + self.exponent;

write!(f, "{}.{}d{}", &digits[0..1], &digits[1..len], (i_d - 1))
} else if self.exponent > 0 {
// e.g. ABC0000. or ABC.
let zeroes = "0".repeat(self.exponent as usize);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is adding more digits of precision to the output.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout. I've fixed it and the tests pass now.

@codecov
Copy link

codecov bot commented Mar 10, 2023

Codecov Report

Patch coverage: 100.00% and project coverage change: +0.03 🎉

Comparison is base (e3f7bd1) 90.13% compared to head (9925c62) 90.16%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #477      +/-   ##
==========================================
+ Coverage   90.13%   90.16%   +0.03%     
==========================================
  Files          76       76              
  Lines       13548    13568      +20     
==========================================
+ Hits        12211    12234      +23     
+ Misses       1337     1334       -3     
Impacted Files Coverage Δ
src/types/decimal.rs 96.18% <100.00%> (+0.28%) ⬆️

... and 4 files with indirect coverage changes

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

src/types/decimal.rs Outdated Show resolved Hide resolved
write!(f, "{}.{}", &digits[0..ui_d], &digits[ui_d..len])
} else if i_d > -6 {
// e.g. 0.ABC or 0.000ABC
let zeroes = "0".repeat(i_d.abs() as usize);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add Clippy's suggestions here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into rust-lang/rust-clippy#7416 and had a very frustrating time before I managed to get clippy running.

Fixed the cast_abs_to_unsigned issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later obviated by Zack's format string suggestion.

// The index of the decimal, relative to the magnitude representation
// 0123
// Given ABCDd-2, the decimal gets inserted at position 2.
let i_d = len as i64 + self.exponent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about d_index?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I used decimal_index, but then rustfmt forces some of the write! cases into multiple lines. I shortened some variable names to keep everything on one line because I found the multiline version so much less appealing.

d_index is similarly too long. I wish I could get rustfmt to tell me which constraint it's running into, because it seems to not fire when line length is 83 but does fire on line length 84? So maybe it's not line length, maybe it's length of parameter list? I don't know.

write!(f, "-").unwrap();
};

if self.exponent == 0 && len > 6 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you make 6 a named constant and/or add a comment explaining how you chose it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do both. Happy to change to some other value, as well.

src/types/decimal.rs Outdated Show resolved Hide resolved
// exponent < 0, there is a fractional component
if i_d > 0 {
// e.g. A.BC or AB.C
let ui_d = i_d as usize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's helpful, you can shadow the existing name in this scope so you don't have to think of a new one. (IMO, ui_d is a bit cryptic, though I gather it stands for unsigned_index_of_d?). Shadowing has the potential to be a bit confusing for readers outside of an IDE, but in this case it only lasts one line.

write!(f, "{}.{}", &digits[0..ui_d], &digits[ui_d..len])
} else if i_d > -6 {
// e.g. 0.ABC or 0.000ABC
let zeroes = "0".repeat(i_d.abs() as usize);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will allocate a new string, but you can achieve the same thing using the format string you pass to write!. See the docs for Fill/Alignment and Sign/#/0 for details.

Alternatively, create one very long static ZEROS: &str = "0000000..."; and just take a slice of the desired length 😛

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the format string passed to write! was a great suggestion, took a little fiddling but I like it :D

jobarr-amzn and others added 3 commits March 10, 2023 07:19
Co-authored-by: Matthew Pope <81593196+popematt@users.noreply.github.com>
Co-authored-by: Zack Slayton <zack.slayton@gmail.com>
Copy link
Contributor

@zslayton zslayton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 A nice improvement!

fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// Inspired by the formatting conventions of Java's BigDecimal.toString()
const WIDE_NUMBER: i64 = 6; // if you think about it, six is a lot 🙃
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a usize, which would allow you to skip:

  • casting len as i64 down on line 308
  • casting WIDE_NUMBER as usize on line 314

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah WIDE_NUMBER should be a usize.

  • If I don't cast len as i64 on line 308 thendot_index gets inferred as usize, but that's not right because dot_index should be able to be negative.
  • As far as I understand it, I either have to cast WIDE_NUMBER as usize on line 314 to agree with len or cast WIDE_NUMBER as i64 on line 324 for easiest comparison with the negative-valued dot_index there. The latter is the right call because WIDE_NUMBER should be a usize.

Unless I'm missing something, which is probable :) I'd also happily take feedback or second opinions on the WIDE_NUMBER concept- whether we ought to have it at all, whether it ought to be applied "evenly" on both whole and fractional numbers, and how large it should be.

@jobarr-amzn jobarr-amzn merged commit f49659e into main Mar 11, 2023
@jobarr-amzn jobarr-amzn deleted the display-decimal branch March 11, 2023 00:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants