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

Support single precision floats in grisu formatting #1361

Merged
merged 1 commit into from
Oct 18, 2019

Conversation

orivej
Copy link
Contributor

@orivej orivej commented Oct 12, 2019

Depends on #1360

Fixes #1336

Copy link
Contributor

@vitaut vitaut left a comment

Choose a reason for hiding this comment

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

Thanks for another great PR! A few comments inline.

@@ -423,6 +425,17 @@ class fp {
lower.f <<= lower.e - upper.e;
lower.e = upper.e;
}

void compute_float_boundaries(fp& lower, fp& upper) const {
constexpr significand_type half_step =
Copy link
Contributor

Choose a reason for hiding this comment

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

Does step mean ulp here? If yes, I recommend using ulp instead of step since it's an established term.

Comment on lines 363 to 366
static FMT_CONSTEXPR_DECL const int double_significand_size =
std::numeric_limits<double>::digits - 1;
static FMT_CONSTEXPR_DECL const int float_significand_size =
std::numeric_limits<float>::digits - 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest replacing these two with a single function to reduce duplication:

  template <typename T>
  static int float_significand_size() { return std::numeric_limits<T>::digits - 1; }

@@ -935,8 +948,8 @@ template <typename Double> FMT_FUNC void fallback_format(Double v, int exp10) {

template <typename Double,
enable_if_t<(sizeof(Double) == sizeof(uint64_t)), int>>
FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
unsigned options, int& exp) {
FMT_API bool grisu_format(Double value, size_t sizeof_value, buffer<char>& buf,
Copy link
Contributor

Choose a reason for hiding this comment

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

Since sizeof_value is only used to pass one bit of information (whether argument is float or double) I suggest folding it into options.

@orivej orivej force-pushed the float2 branch 2 times, most recently from d6a0654 to a887908 Compare October 13, 2019 18:24
@vitaut
Copy link
Contributor

vitaut commented Oct 15, 2019

Looks good but let me double check the logic in compute_float_boundaries.

Copy link
Contributor Author

@orivej orivej left a comment

Choose a reason for hiding this comment

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

OK. I have left some notes.


void compute_float_boundaries(fp& lower, fp& upper) const {
constexpr int min_normal_e = std::numeric_limits<float>::min_exponent -
std::numeric_limits<double>::digits;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The smallest positive normal float is 2^-126. fp(2^-126) is fp(2^52, -178). float min_exponent is -125, double digits is 53: both of these numbers are off by one from what we need, but these 1s cancel each other out.

std::numeric_limits<double>::digits;
significand_type half_ulp = 1 << (std::numeric_limits<double>::digits -
std::numeric_limits<float>::digits - 1);
if (min_normal_e > e) half_ulp <<= min_normal_e - e;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When the float is normal, half_ulp is an obvious constant. When the float is subnormal, the cast to double shifts the significand to the left and effectively decreases the exponent by the number of shifted bits. Then we need to shift half_ulp to the left by the same amount.

std::numeric_limits<float>::digits - 1);
if (min_normal_e > e) half_ulp <<= min_normal_e - e;
upper = normalize<0>(fp(f + half_ulp, e));
lower = fp(f - (half_ulp >> (f == implicit_bit && e > min_normal_e)), e);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The condition is e > min_normal_e, not e >= min_normal_e, because the smallest positive normal float is the average of its neighbors. (The same is true for the smallest positive normal double but compute_boundaries does not account for that which may be either a bug or irrelevant.)

Copy link
Contributor

Choose a reason for hiding this comment

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

The same is true for the smallest positive normal double but compute_boundaries does not account for that which may be either a bug or irrelevant.

Great catch! It's definitely a bug which fortunately doesn't affect the output. Will be fixed in eb879a6.

@vitaut vitaut merged commit 599e0ae into fmtlib:master Oct 18, 2019
@vitaut
Copy link
Contributor

vitaut commented Oct 18, 2019

Thanks for the detailed explanation, merged.

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.

Support single precision floats in grisu formatting
2 participants