unitsofmeasurement / unit-api

Units of Measurement API
http://unitsofmeasurement.github.io/unit-api/
Other
182 stars 42 forks source link

Convert Quantity to most fitting Prefix #236

Closed puigru closed 2 years ago

puigru commented 2 years ago

Is there a way to convert a Quantity to the most fitting Prefix? e.g. given MetricPrefix: 1000 m to 1 km, 0.1 m to 1 cm

keilw commented 2 years ago

Converting not really, but isEquivalentTo() (since 2.1) returns true if you compare 1000 m and 1km. I don't really see a need for a special convert() method, but using isEquivalentTo() you could easily write some kind of loop or Lambda to find such a fitting target quantity. I hope this helps, otherwise feel free to reopen or create another ticket.

puigru commented 2 years ago

Well, I meant it for formatting purposes, isEquivalentTo() is not very useful in that case. One may want to display 4500 g as 4.5 kg, for example. Is there a way to accomplish this with this library?

keilw commented 2 years ago

Ok so if it's about formatting (potentially with confersion) then please refer to indriya#189. We start exploring these ideas with implementations of UnitFormat or QuantityFormat. If something like isEquivalentTo() later turns out to be very generic we might propose changes to the API but for now if you're interested in this kind of feature, happy about help or PRs for indriya#189. I won't reopen this but please share your ideas in the RI ticket or create another one if you think a MultiFormat is too far from what you'd like to archive.

puigru commented 2 years ago

I wrote a small piece of code to better illustrate what I'm trying to do:

public static Quantity<?> unsafeConvert(Quantity<?> quantity, Unit<?> unit) {
    Number value = quantity.getValue();
    UnitConverter converter;
    try {
        converter = quantity.getUnit().getConverterToAny(unit);
    } catch (IncommensurableException e) {
        throw new RuntimeException(e);
    }
    return Quantities.getQuantity(converter.convert(value), unit);
}

public static Quantity<?> simplifyUnits(Quantity<?> quantity) {
    Prefix[] large = new Prefix[] { KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA };
    Prefix[] small = new Prefix[] { MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO };
    Unit<?> baseUnit = quantity.getUnit().getSystemUnit();
    if (baseUnit == Units.KILOGRAM) baseUnit = Units.GRAM; // special case for kg
    quantity = unsafeConvert(quantity, baseUnit);
    double value = quantity.getValue().doubleValue();
    int log = (int)Math.floor(Math.log(value) / Math.log(1000));
    if (log == 0) return quantity;
    Prefix prefix;
    if (log < 0) {
        log = Math.min(-log, small.length);
        prefix = small[log-1];
    } else {
        log = Math.min(log, large.length);
        prefix = large[log-1];
    }
    Unit<?> targetUnit = baseUnit.prefix(prefix);
    return unsafeConvert(quantity, targetUnit);
}

public static void main(String[] args) {
    double[] test = new double[] { 42*Math.pow(10, -6), 0.1, 1, 4500, Math.pow(10, 6) };
    for (double t : test) {
        Quantity<?> result = simplifyUnits(Quantities.getQuantity(t, Units.GRAM));
        System.out.println(result);
    }
}

Outputs:

42 µg
100 mg
1 g
4.5 kg
1 Mg

Perhaps a solution would be to integrate something like this inside a custom QuantityFormatter as you suggest. I was just wondering if there was a built-in way before rolling my own, as this seems like something that would come up often.

Edit: Ironed out some kinks in the code.

keilw commented 2 years ago

I think I only half-guess how these values are related (not quite MultiFormat because there it would have to be "1000mg, 1g" ;-) but maybe the function package of Indriya could offer a similar function in places like QuantityStreams or another class. Or uom-lib-common in https://github.com/unitsofmeasurement/uom-lib. @andi-huber What do you think about it?

puigru commented 2 years ago

It's just a few random numbers to show how you could simplify the value to use more precise units instead of showing a very large (or very small) value. Similarly to how your computer doesn't tell you a file weighs 44500 bytes but may instead say 44.5 kb, you may want the Quantity object to print the latter.

keilw commented 2 years ago

I will transfer this to Indriya to explore possible functions like that. It did not work for some strange reason, so referenced instead.