
"Beth Jacobson" wrote
Andy Little wrote:
"Beth Jacobson" wrote
[...]
Under/overflow will certainly be an issue for some people if they can't specify their own base units, but I don't know if a tight coupling between units and dimensions is the best solution. Could there be some sort of global setting (a facet, maybe?) where we could specify units for base dimensions for the entire application?
The quantity containers and unit typedefs can be easily customised by the user to whatever they prefer:
namespace my{
typedef boost::pqs::length::mm distance; typedef boost::pqs::time::s time; typedef boost::pqs::velocity::mm_div_s velocity; }
void f() { my:::velocity v = my::distance(1) / my_time(1);
}
That would remove the unit declarations from the code, but the program still wouldn't be unit agnostic. Say I wrote a function like this
my::area GetArea(my::length len, my::length width) { return len*width; }
If I used it in a program using SI units, it would work exactly like what I'm proposing, but if the calling program used imperial units, it would first convert len and width from feet to meters, then multiply them, then convert the result from square meters back to square feet. The results would be the same either way (assuming no rounding issues), but it adds three unnecessary conversions.
You could rewrite the function: #include <boost/pqs/t1_quantity/types/length.hpp> #include <boost/pqs/t1_quantity/types/out/area.hpp> #include <boost/pqs/typeof_register.hpp> #include <boost/type_traits/is_convertible.hpp> #include <boost/utility/enable_if.hpp> template <typename Out, typename In> typename boost::enable_if< boost::mpl::and_< boost::is_convertible<In, boost::pqs::length::m>, boost::is_convertible<Out, boost::pqs::area::m2> >, Out
::type GetArea(In len, In width) { return len*width; }
namespace pqs = boost::pqs; int main() { pqs::length::ft length(2); pqs::length::ft width(1); BOOST_AUTO(result, GetArea<pqs::area::ft2>(length,width)); std::cout << result <<'\n'; } but using SI units is more efficient, sure. The main use of imperial units is for input output AFAICS FWIW you can also use enable_if to prevent unwanted conversions of the arguments if you wish, by using boost::is_same rather than is_convertible.
Both your example and mine have a common element though. They both involve moving between numeric values and dimensions. I can't think of an instance where units would be useful when that isn't the case. If that's true, then it seems like overkill to require bound units throughout the program. A simpler solution would be to prohibit dimension variables from being assigned or returning "raw" numbers. The only way to get or set a value would be through unit functions like this.
PQS quantities can't be converted to raw numbers.
double val = pqs::length::m(1) ;// Error
but a function is provided to get the numeric_value:
double val = pqs::length::m(1).numeric_value() ;// Ok
Its name is quite long so it's easy to spot in code.
Thanks. I didn't see it in the docs, but I figured there had to be some fairly straightforward way to extract the value.
Oops.. Good catch. Its not in the docs!
Since units would be specified at the point of conversion, this method might even be a little safer. In pqs, you might do something like this
pqs::length::m depth;
// lots of code
Its not possible to initialise a double from a quantity in PQS:
double depthAsDouble(depth);
so the the above will not compile.
Then the line would look like this double depthAsDouble = depth.numeric_value();
Yep!
SetFieldValue("DepthInFt", depthAsDouble);
cout << "Enter new depth (in feet): " cin >> depth;
Because depth is declared far from where it's output and reassigned, these mistakes would be easy to miss.
Not so for the above reason!
But when I use numeric_value(), the problem still remains: there's nothing in that line to tell you the units of the extracted value. It's not essential that there should be, but since this is one place where the library can't protect the user against units errors, it would be nice if such errors were easier to recognize.
You can use the units_str(q) function: #include <boost/pqs/t1_quantity/types/out/length.hpp> #include <utility> template <typename Q> std::pair<std::string,double> SetFieldValue(Q q) { std::pair<std::string,double> result(units_str(q),q.numeric_value()); return result; } namespace pqs = boost::pqs; int main() { pqs::length::ft length(1); std::pair<std::string,double> field = SetFieldValue(length); std::cout << field.first << ' ' << field.second <<'\n'; } But sure, if you remove the units then its up to you to deal with what the number means.
In PQS the units are part of the type. IOW the numeric value and its unit are always tightly coupled in code. That is a powerful feature. Once the numeric part of a quantity is disassociated from its unit then manual checking and external documentation is required, which is often the current situation wherever doubles are used to represent quantities. Manual checking doesnt always work as well as intended... That of course is why the Mars lander crashed!
I agree that unit checking can be an extremely useful feature, I'm just not convinced that tight coupling is necessary to achieve that.
There is planned another quantity where you can change the units at runtime FWIW.
If my assumption that unit conversions are only needed when numeric values are assigned to or extracted from dimensions is correct, then dimensions don't really need to know about units. The units functions would need to have a concept of default dimension units so they'd know what they were converting from or to, but the dimensions themselves wouldn't know or care.
That would be the real benefit of this system. Reducing the number of conversions and making units explicit at the point of conversion may have some small benefit, but making the dimensions library essentially unitless seems like a major advantage.
It depends what unitless means. If you want to just use base_units then you can. The input output of units is a separate component, so you don't get it unless you want it.
Of course it's your library, and I'm not trying to dictate design. But if you're looking for a way to make the dimensions library essentially independent of units while retaining the unit safety of the current system, this might be a direction to consider.
Its difficult to see what you mean without a specific problem to consider. regards Andy Little