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

Fix #580 by using a fixed-point implementation for unit conversions using integer representations #615

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

burnpanck
Copy link
Contributor

This PR provides an implementation of what I played with in this comment to issue 580. Basically, it completely distinguishes floating-point and integral paths. For floating-point paths, the conversion implementation remains the same (a single multiplication with a floating-point representation of the conversion factor). For the integral paths, it now also uses a single multiplication, but now with a fixed-point representation. (mixed conversions take the floating-point path). Fixed-point multiplications have the advantage that they are comparatively cheap (much cheaper than a division) and can accurately describe all reasonable conversion factors between two n-bit numbers using a n.n (2n total) fixed-point representation. Unreasonable conversion factors are those larger than 2^n or smaller than 2^-n, i.e. those which either overflow for all input values or underflow for all input values. The cost of such a fixed-point multiplication is at most that of 4x n-bit integer multiplication plus some bookkeeping if their output is restricted to n-bit (as in the C++ standard; unlike typical hardware); if the output of a n-bit multiplication can be 2n (most instruction-sets provide those), it will just take two of them.

With the implementation change here, the computation of conversions between two quantity types of the same dimension and both integer type stay tightly within the types mandated by the source and destination type, without expanding to intmax unless necessary. In general, this will never cause overflows unless the result type actually cannot carry the result (the fixed-point multiplication is guaranteed to be sufficient). However, this practice triggered the weakly related #614, where it turned a silent overflow into a compilation failure. For now, this PR just disables those two test. Once we have a fix for that one, we should re-enable those tests and rebase this PR.

src/core/include/mp-units/bits/fixed_point.h Show resolved Hide resolved
Comment on lines +136 to +137
Th hi;
Tl lo;
Copy link
Owner

Choose a reason for hiding this comment

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

Can we make those private? In case we do, can we name them with the _ postfix to be consistent with all other places in the library?

};


constexpr double_width_int operator+(Tl rhs) const
Copy link
Owner

Choose a reason for hiding this comment

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

Should it be a non-member two-parameter function to make sure that conversions work for lhs and rhs? Should there be another overload with reversed arguments?

Tl lo;
};

#if false && defined(__SIZEOF_INT128__)
Copy link
Owner

Choose a reason for hiding this comment

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

Always false?

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, CI complains about the true-path because __int128_t is non-standard. Given that this is an implementation-detail, I should probably look for a way to disable that warning instead.

#endif

template<typename T>
inline constexpr std::size_t integer_rep_width_v = std::numeric_limits<std::make_unsigned_t<T>>::digits;
Copy link
Owner

Choose a reason for hiding this comment

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

inline not needed from variable templates.


template<std::integral U>
requires(integer_rep_width_v<U> <= integer_rep_width_v<T>)
auto scale(U v) const
Copy link
Owner

Choose a reason for hiding this comment

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

constexpr?

fractional_bits);
}

repr_t int_repr_is_an_implementation_detail_;
Copy link
Owner

Choose a reason for hiding this comment

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

private?

Comment on lines 57 to 63
/* using multiplier_type = conditional<
treat_as_floating_point<c_rep_type>,
// ensure that the multiplier is also floating-point
conditional<std::is_arithmetic_v<value_type_t<c_rep_type>>,
// reuse user's type if possible
std::common_type_t<c_mag_type, value_type_t<c_rep_type>>, std::common_type_t<c_mag_type, double>>,
c_mag_type>;*/
Copy link
Owner

Choose a reason for hiding this comment

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

Why to comment this code instead of removing it?

src/core/include/mp-units/bits/sudo_cast.h Outdated Show resolved Hide resolved
src/core/include/mp-units/bits/sudo_cast.h Outdated Show resolved Hide resolved
@burnpanck
Copy link
Contributor Author

Most of the review concerns should be fixed now. Also, I "fixed" the CI failure in the trigonometry tests by allowing the affected test-cases to deviate by two instead of one epsilon. I believe this appears because now, for double quantities, the conversion factor is now a double instead of a long double - an intentional change of making the conversion accuracy just "good enough" for the values involved. In fact, that test previously failed on my mac M1 anyway - because on my platform, long double and double are the same thing. In general, guaranteeing one epsilon accuracy is probably unrealistic for reasonable computations anyway (my previous discussions on the topic used the term "of the order of the epsilon" on purpose).

What I did not yet address are the discussions about making the implementation details private. The aim was to keep the types "literal types", so we could use them in NTTPs if needed (maybe for large point-offsets?). That said, I'm not sure we really need it, and it's easy to change, so whatever you prefer. Also, if we start using such wide integers as repr inside quantities, we may need even wider ones to achieve our guarantees in conversions. The design of the double_width_int here could be relatively easily extended into a compile-time arbitrary-width integer if that need arose. But probably, that's a task for another PR.

@burnpanck
Copy link
Contributor Author

That CI failure for GCC 12 and GCC 13 error: extra ‘;’ [-Werror=pedantic] after a class template partial specialisation is obviously wrong. Should I re-write this as three separate classes and use something like std::conditional_t to get those compilers to accept?

@mpusz
Copy link
Owner

mpusz commented Sep 16, 2024

The aim was to keep the types "literal types", so we could use them in NTTPs if needed (maybe for large point-offsets?)

I was not aware of this. This makes sense... But in such a case, I would obfuscate the names much more (similar to what we have in quantity) so users will not depend on those, as we might hide them if the language rules for literal types will change in the future.

@mpusz
Copy link
Owner

mpusz commented Sep 16, 2024

That CI failure for GCC 12 and GCC 13 error: extra ‘;’ [-Werror=pedantic] after a class template partial specialisation is obviously wrong. Should I re-write this as three separate classes and use something like std::conditional_t to get those compilers to accept?

I think you did not check if properly. GCC complains about ; put after a function definition (not class).

@@ -36,6 +36,7 @@ add_library(
custom_rep_test_min_impl.cpp
dimension_test.cpp
dimension_symbol_test.cpp
fixed_point.cpp
Copy link
Owner

Choose a reason for hiding this comment

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

Please postfix the file name with _test

@burnpanck
Copy link
Contributor Author

I think you did not check if properly. GCC complains about ; put after a function definition (not class).

You are right of course. It just happens that sudo_cast.h had a template class specialisation end on the same line number, and I never properly checked the file name (it's fixed_point.h).

@burnpanck
Copy link
Contributor Author

I should probably increase the test coverage for that double_width_int quite a bit more. The value_cast use-case actually only uses the multiplication operator and constructor from long double now (though I should still change that for purely rational conversion factors), but once it's in the code-base, people might get tempted to use it for something else. Also, testing is easier if a more complete set of arithmetic is available.

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.

2 participants