Boost logo

Boost :

From: Andy Little (andy_at_[hidden])
Date: 2006-06-24 16:48:02


"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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk