Fix units::modf() for scaled quantities with a frac part#312
Open
ts826848 wants to merge 1 commit intonholthaus:v3.xfrom
Open
Fix units::modf() for scaled quantities with a frac part#312ts826848 wants to merge 1 commit intonholthaus:v3.xfrom
ts826848 wants to merge 1 commit intonholthaus:v3.xfrom
Conversation
units::modf() appears to add a spurious scaling factor to the fractional
result it returns. For example, this test code:
percent<> pval{202.5};
double pmodfr1;
decltype(pval) pmodfr2;
EXPECT_EQ(std::modf(pval.to<double>(), &pmodfr1), units::modf(pval, &pmodfr2));
EXPECT_EQ(pmodfr1, pmodfr2);
Fails with this message:
units/unitTests/main.cpp:4649: Failure
Expected equality of these values:
std::modf(pval.to<double>(), &pmodfr1)
Which is: 0.025
units::modf(pval, &pmodfr2)
Which is: 0.00025
[ FAILED ] UnitMath.modf (0 ms)
Not 100% confident I understand the specifics of the C++ rules here, but
I think this line from units::modf() is the culprit:
dimensionlessUnit fracpart = std::modf(x.template to<decltype(intp)>(), &intp);
I think this uses uses copy-initialization to initialize fracpart by
using the implicit converting constructor for linear dimensionless units
to create a dimensionlessUnit from the std::modf() result, then using
that to direct-initialize fracpart.
The problem is that because this uses the implicit converting
constructor for linear dimensionless units, the scaling done to produce
a value to pass to std::modf is not "undone" when reconstituting
fracpart. Instead, the std::modf() result is taken as-is to produce a
dimensionlessUnit value, effectively resulting in the scaling factor
applying twice by the time the units::modf() result is turned back into
a double.
For example, passing percent<>{202.5} to units::modf() results in 2.025
being passed to std::modf(), which returns 0.025. This is passed to the
converting constructor, resulting in percent<>{0.025} (!), which is then
used to direct-initialize fracpart, which is then returned. This is then
turned into a double in EXPECT_EQ(), applying the percent<> scaling
factor once again to get a final value of 0.00025.
This can be fixed by first making a dimensionless<> from the std::modf()
result, which results in the unit converting constructor being called,
which ensures the scaling factor is reapplied when initializing
fracpart.
intpart is not affected by this issue because operator=() more or less
already does this by making a dimensionless<> from its argument before
changing *this.
Contributor
Author
|
I suppose I have a related question - the percent<> p1{5.0};
percent<> p2;
p2 = 5.0;
EXPECT_EQ(p1, p2);Fails with this message: Is this difference intended? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
units::modf() appears to add a spurious scaling factor to the fractional result it returns. For example, this test code:
Fails with this message:
Not 100% confident I understand the specifics of the C++ rules here, but I think this line from units::modf() is the culprit:
I think this uses uses copy-initialization to initialize fracpart by using the implicit converting constructor for linear dimensionless units to create a dimensionlessUnit from the std::modf() result, then using that to direct-initialize fracpart.
The problem is that because this uses the implicit converting constructor for linear dimensionless units, the scaling done to produce a value to pass to std::modf is not "undone" when reconstituting fracpart. Instead, the std::modf() result is taken as-is to produce a dimensionlessUnit value, effectively resulting in the scaling factor applying twice by the time the units::modf() result is turned back into a double.
For example, passing percent<>{202.5} to units::modf() results in 2.025 being passed to std::modf(), which returns 0.025. This is passed to the converting constructor, resulting in percent<>{0.025} (!), which is then used to direct-initialize fracpart, which is then returned. This is then turned into a double in EXPECT_EQ(), applying the percent<> scaling factor once again to get a final value of 0.00025.
This can be fixed by first making a dimensionless<> from the std::modf() result, which results in the unit converting constructor being called, which ensures the scaling factor is reapplied when initializing fracpart.
intpart is not affected by this issue because operator=() more or less already does this by making a dimensionless<> from its argument before changing *this.