Вопрос

I would like to implement a percent unit with Boost.Units so that a dimensionless quantity (like a ratio) can be represented as a percentage. I have successfully implemented a conversion between mass density units, but the same does not work for dimensionless units. Here is my code (assuming using namespace boost::units;):

//
// gram per milliliter (g mL^-1)
//
namespace my {
        struct gram_per_milliliter_base_unit :
                base_unit<gram_per_milliliter_base_unit, mass_density_dimension, 1>
        {
                static std::string name() {return "gram per milliliter";}
                static std::string symbol() {return "g mL^-1";}
        };
        typedef gram_per_milliliter_base_unit::unit_type gram_per_milliliter_unit;
        BOOST_UNITS_STATIC_CONSTANT(gram_per_milliliter, gram_per_milliliter_unit);
        BOOST_UNITS_STATIC_CONSTANT(grams_per_milliliter, gram_per_milliliter_unit);
}
BOOST_UNITS_DEFINE_CONVERSION_FACTOR(
        my::gram_per_milliliter_base_unit, si::mass_density, double, 1.0e3
); // 1 g mL^-1 == 1e3 kg m^-3 (SI unit)
BOOST_UNITS_DEFAULT_CONVERSION(my::gram_per_milliliter_base_unit, si::mass_density);

//
// percentage (%)
//
namespace my {
        struct percent_base_unit :
                base_unit<percent_base_unit, dimensionless_type, 2>
        {
                static std::string name() {return "percent";}
                static std::string symbol() {return "%";}
        };
        typedef percent_base_unit::unit_type percent_unit;
        BOOST_UNITS_STATIC_CONSTANT(percent, percent_unit);
}
BOOST_UNITS_DEFINE_CONVERSION_FACTOR(
        my::percent_base_unit, si::dimensionless, double, 1.0e-2
); // 1 % == 1e-2 (SI dimensionless unit)
BOOST_UNITS_DEFAULT_CONVERSION(my::percent_base_unit, si::dimensionless);

The "gram per milliliter" section works as expected: I can compile this code (assuming using namespace my; as well):

quantity<gram_per_milliliter_unit> q1my(3*grams_per_milliliter);
quantity<si::mass_density> q1si(q1my);
quantity<gram_per_milliliter_unit> q1back(q1si);

But the following fails to compile on both conversions:

quantity<percent_unit> q2my(3*percent);
quantity<si::dimensionless> q2si(q2my);
quantity<percent_unit> q2back(q2si);

G++ outputs: no matching function for call to 'conversion_factor(..., ...)'.

Is this related to the fact that dimensionless_type seems to be a marker for end of typelists?

Any help or suggestion would be greatly appreciated. Thank you.

Это было полезно?

Решение

Is this related to the fact that dimensionless_type seems to be a marker for end of typelists?

Sort of. dimensionless_type is included in every measurement system implicitly, and extracted from them all the same way, see boost/units/dimensionless_units.hpp.

In your "percentage" example, think what your new measurement system is going to be, and how you'd specify it according to the usual boost units rules:

namespace my {
    ... // define your own unit tag types
    typedef make_system</* your system's units with dimensions*/>::type system;
    ... // unit typedefs, consts, etc.
}

So if you say your percentage is going to be dimensionless, but different from the original dimensionless, you're defying the above concept. So, you can't define 2 dimensionless dimensions within a system.

Any help or suggestion would be greatly appreciated.

I see 3 options here:

  1. Override the underlying quantity type and accept either percentage or regular number in ctor. See http://www.boost.org/doc/libs/release/doc/html/boost_units/Examples.html#boost_units.Examples.UDTExample

  2. If you want to display things in percentages, you can try using the autoscale feature (never done it myself, but there's an example as well -- http://www.boost.org/doc/libs/release/doc/html/boost_units/Examples.html#boost_units.Examples.autoscale).

  3. You could create a special custom dimension, "percent", and explicitly convert to/from percentage quantities. This is probably closest to your original intent, but automatic conversions won't always happen as the library wasn't designed for "dimensional analysis of dimensionless quantities". You can see how ugly the result is if you try to force the system into automatic conversions:

    //
    // percentage (%)
    //
    namespace my {
        struct percent_base_dimension : 
            base_dimension<percent_base_dimension, 1> {};
        typedef percent_base_dimension::dimension_type percent_type;
    
        struct percent_base_unit :
            base_unit<percent_base_unit, percent_type, 1>
        {
            static std::string name() {return "percent";}
            static std::string symbol() {return "%";}
        };
        typedef make_system<percent_base_unit>::type system;
        typedef percent_base_unit::unit_type percent_unit;
        BOOST_UNITS_STATIC_CONSTANT(percent, percent_unit);
    }
    
    namespace boost { namespace units {
    
    template<class T0, class T1>
    struct conversion_helper<quantity<my::percent_unit, T0>, quantity<si::dimensionless, T1> >
    {
        static quantity<si::dimensionless, T1> convert(const quantity<my::percent_unit, T0>& source)
        {
            return(quantity<si::dimensionless, T1>::from_value(1e-2 * source.value()));
        }
    };
    
    template<class T0, class T1>
    struct conversion_helper<quantity<si::dimensionless, T0>, quantity<my::percent_unit, T1> >
    {
        static quantity<my::percent_unit, T1> convert(const quantity<si::dimensionless, T0>& source)
        {
            return(quantity<my::percent_unit, T1>::from_value(1e+2 * source.value()));
        }
    };
    
    } }
    
    int main()
    {
        using namespace my;
    
        quantity<percent_unit> q2my(3*percent);
    
        //quantity<si::dimensionless> q2si(q2my);
        //The converter won't be picked up due to an explicit disable_if in quantity.hpp:
        // typename boost::disable_if<detail::is_dimensionless_system<System2> >::type* = 0
        //so we manually force the conversion here:
        auto conv = conversion_helper<quantity<percent_unit>, quantity<si::dimensionless> >::convert;
        quantity<si::dimensionless> q2si(conv(q2my));
    
        quantity<percent_unit> q2back(q2si);
    
        std::cout 
            << "q2my: " << q2my << std::endl
            << "q2si: " << q2si << std::endl
            << "q2back: " << q2back << std::endl
            ;
    }
    

    So it's a good idea to do it manually, something like

    namespace my {
        template<class Y>
        quantity<si::dimensionless, Y> units(const quantity<percent_unit, Y>& source)
        {
            return(quantity<si::dimensionless, Y>::from_value(1e-2 * source.value()));
        }
    
        template<class Y>
        quantity<percent_unit, Y> percentage(const quantity<si::dimensionless, Y>& source)
        {
            return(quantity<percent_unit, Y>::from_value(1e+2 * source.value()));
        }
    
    }
    
    int main()
    {
        using namespace my;
    
        quantity<percent_unit> q2my(3*percent);
        quantity<si::dimensionless> q2si(my::units(q2my));
        quantity<percent_unit> q2back(my::percentage(q2si));
    
        std::cout 
            << "q2my: " << q2my << std::endl
            << "q2si: " << q2si << std::endl
            << "q2back: " << q2back << std::endl
            ;
    }
    

    or, better yet, using the benefit of type checking (so you can only make a mistake in the conversion factor here):

    template<class Y>
    quantity<si::dimensionless, Y> units(const quantity<percent_unit, Y>& source)
    {
        return quantity<si::dimensionless, Y>(1e-2 * source / percent);
    }
    
    template<class Y>
    quantity<percent_unit, Y> percentage(const quantity<si::dimensionless, Y>& source)
    {
        return quantity<percent_unit, Y>(1e+2 * source * percent);
    }
    
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top