All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.icu.impl.units.ComplexUnitsConverter Maven / Gradle / Ivy

Go to download

International Component for Unicode for Java (ICU4J) is a mature, widely used Java library providing Unicode and Globalization support

There is a newer version: 76.1
Show newest version
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.impl.units;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.number.Precision;
import com.ibm.icu.util.Measure;

/**
 * Converts from single or compound unit to single, compound or mixed units. For example, from {@code meter}
 * to {@code foot+inch}.
 * 

* DESIGN: This class uses UnitsConverter in order to perform the single converter (i.e. converters from * a single unit to another single unit). Therefore, ComplexUnitsConverter class contains multiple * instances of the UnitsConverter to perform the conversion. */ public class ComplexUnitsConverter { public static final BigDecimal EPSILON = BigDecimal.valueOf(Math.ulp(1.0)); public static final BigDecimal EPSILON_MULTIPLIER = BigDecimal.valueOf(1).add(EPSILON); // TODO(ICU-21937): Make it private after submitting the public units conversion API. public ArrayList unitsConverters_; /** * Individual units of mixed units, sorted big to small, with indices * indicating the requested output mixed unit order. */ // TODO(ICU-21937): Make it private after submitting the public units conversion API. public List units_; private MeasureUnitImpl inputUnit_; /** * Constructs ComplexUnitsConverter for an inputUnit that could be Single, Compound or * Mixed. In case of: 1- Single and Compound units, the conversion will not perform anything, the input will be * equal to the output. 2- Mixed Unit the conversion will consider the input in the biggest unit. and will convert * it to be spread throw the input units. For example: if input unit is "inch-and-foot", and the input is 2.5. The * converter will consider the input value in "foot", because foot is the biggest unit. Then, it will convert 2.5 * feet to "inch-and-foot". * * @param targetUnit * represents the input unit. could be any type. (single, compound or mixed). */ public ComplexUnitsConverter(MeasureUnitImpl targetUnit, ConversionRates conversionRates) { this.units_ = targetUnit.extractIndividualUnitsWithIndices(); assert (!this.units_.isEmpty()); // Assign the biggest unit to inputUnit_. this.inputUnit_ = this.units_.get(0).unitImpl; MeasureUnitImpl.MeasureUnitImplComparator comparator = new MeasureUnitImpl.MeasureUnitImplComparator( conversionRates); for (MeasureUnitImpl.MeasureUnitImplWithIndex unitWithIndex : this.units_) { if (comparator.compare(unitWithIndex.unitImpl, this.inputUnit_) > 0) { this.inputUnit_ = unitWithIndex.unitImpl; } } this.init(conversionRates); } /** * Constructs ComplexUnitsConverter NOTE: - inputUnit and outputUnits must be under the same category - * e.g. meter to feet and inches --> all of them are length units. * * @param inputUnitIdentifier * represents the source unit identifier. (should be single or compound unit). * @param outputUnitsIdentifier * represents the output unit identifier. could be any type. (single, compound or mixed). */ public ComplexUnitsConverter(String inputUnitIdentifier, String outputUnitsIdentifier) { this( MeasureUnitImpl.forIdentifier(inputUnitIdentifier), MeasureUnitImpl.forIdentifier(outputUnitsIdentifier), new ConversionRates() ); } /** * Constructs ComplexUnitsConverter NOTE: - inputUnit and outputUnits must be under the same category - * e.g. meter to feet and inches --> all of them are length units. * * @param inputUnit * represents the source unit. (should be single or compound unit). * @param outputUnits * represents the output unit. could be any type. (single, compound or mixed). * @param conversionRates * a ConversionRates instance containing the unit conversion rates. */ public ComplexUnitsConverter(MeasureUnitImpl inputUnit, MeasureUnitImpl outputUnits, ConversionRates conversionRates) { this.inputUnit_ = inputUnit; this.units_ = outputUnits.extractIndividualUnitsWithIndices(); assert (!this.units_.isEmpty()); this.init(conversionRates); } /** * Sorts units_, which must be populated before calling this, and populates * unitsConverters_. */ private void init(ConversionRates conversionRates) { // Sort the units in a descending order. Collections.sort(this.units_, Collections.reverseOrder(new MeasureUnitImpl.MeasureUnitImplWithIndexComparator(conversionRates))); // If the `outputUnits` is `UMEASURE_UNIT_MIXED` such as `foot+inch`. Thus means there is more than one unit // and In this case we need more converters to convert from the `inputUnit` to the first unit in the // `outputUnits`. Then, a converter from the first unit in the `outputUnits` to the second unit and so on. // For Example: // - inputUnit is `meter` // - outputUnits is `foot+inch` // - Therefore, we need to have two converters: // 1. a converter from `meter` to `foot` // 2. a converter from `foot` to `inch` // - Therefore, if the input is `2 meter`: // 1. convert `meter` to `foot` --> 2 meter to 6.56168 feet // 2. convert the residual of 6.56168 feet (0.56168) to inches, which will be (6.74016 // inches) // 3. then, the final result will be (6 feet and 6.74016 inches) unitsConverters_ = new ArrayList<>(); for (int i = 0, n = units_.size(); i < n; i++) { if (i == 0) { // first element unitsConverters_.add(new UnitsConverter(this.inputUnit_, units_.get(i).unitImpl, conversionRates)); } else { unitsConverters_ .add(new UnitsConverter(units_.get(i - 1).unitImpl, units_.get(i).unitImpl, conversionRates)); } } } /** * Returns true if the specified {@code quantity} of the {@code inputUnit}, expressed in terms of the biggest * unit in the MeasureUnit {@code outputUnit}, is greater than or equal to {@code limit}. *

* For example, if the input unit is {@code meter} and the target unit is {@code foot+inch}. Therefore, * this function will convert the {@code quantity} from {@code meter} to {@code foot}, then, it will * compare the value in {@code foot} with the {@code limit}. */ public boolean greaterThanOrEqual(BigDecimal quantity, BigDecimal limit) { assert !units_.isEmpty(); // NOTE: First converter converts to the biggest quantity. return unitsConverters_.get(0).convert(quantity).multiply(EPSILON_MULTIPLIER).compareTo(limit) >= 0; } public static class ComplexConverterResult { public final int indexOfQuantity; public final List measures; ComplexConverterResult(int indexOfQuantity, List measures) { this.indexOfQuantity = indexOfQuantity; this.measures = measures; } } /** * Returns outputMeasures which is an array with the corresponding values. * - E.g. converting meters to feet and inches. * 1 meter --> 3 feet, 3.3701 inches * NOTE: * the smallest element is the only element that could have fractional values. And all * other elements are floored to the nearest integer */ public ComplexConverterResult convert(BigDecimal quantity, Precision rounder) { BigInteger sign = BigInteger.ONE; if (quantity.compareTo(BigDecimal.ZERO) < 0 && unitsConverters_.size() > 1) { quantity = quantity.abs(); sign = sign.negate(); } // For N converters: // - the first converter converts from the input unit to the largest // unit, // - N-1 converters convert to bigger units for which we want integers, // - the Nth converter (index N-1) converts to the smallest unit, which // isn't (necessarily) an integer. List intValues = new ArrayList<>(unitsConverters_.size() - 1); for (int i = 0, n = unitsConverters_.size(); i < n; ++i) { quantity = (unitsConverters_.get(i)).convert(quantity); if (i < n - 1) { // The double type has 15 decimal digits of precision. For choosing // whether to use the current unit or the next smaller unit, we // therefore nudge up the number with which the thresholding // decision is made. However after the thresholding, we use the // original values to ensure unbiased accuracy (to the extent of // double's capabilities). BigInteger flooredQuantity = quantity.multiply(EPSILON_MULTIPLIER).setScale(0, RoundingMode.FLOOR).toBigInteger(); intValues.add(flooredQuantity); // Keep the residual of the quantity. // For example: `3.6 feet`, keep only `0.6 feet` BigDecimal remainder = quantity.subtract(BigDecimal.valueOf(flooredQuantity.longValue())); if (remainder.compareTo(BigDecimal.ZERO) == -1) { quantity = BigDecimal.ZERO; } else { quantity = remainder; } } } quantity = applyRounder(intValues, quantity, rounder); // Initialize empty measures. List measures = new ArrayList<>(unitsConverters_.size()); for (int i = 0; i < unitsConverters_.size(); i++) { measures.add(null); } // Package values into Measure instances in measures: int indexOfQuantity = -1; for (int i = 0, n = unitsConverters_.size(); i < n; ++i) { if (i < n - 1) { Measure measure = new Measure(intValues.get(i).multiply(sign), units_.get(i).unitImpl.build()); measures.set(units_.get(i).index, measure); } else { indexOfQuantity = units_.get(i).index; Measure measure = new Measure(quantity.multiply(BigDecimal.valueOf(sign.longValue())), units_.get(i).unitImpl.build()); measures.set(indexOfQuantity, measure); } } return new ComplexConverterResult(indexOfQuantity , measures); } /** * Applies the rounder to the quantity (last element) and bubble up any carried value to all the intValues. * * @return the rounded quantity */ private BigDecimal applyRounder(List intValues, BigDecimal quantity, Precision rounder) { if (rounder == null) { return quantity; } DecimalQuantity quantityBCD = new DecimalQuantity_DualStorageBCD(quantity); rounder.apply(quantityBCD); quantity = quantityBCD.toBigDecimal(); if (intValues.size() == 0) { // There is only one element, Therefore, nothing to be done return quantity; } // Check if there's a carry, and bubble it back up the resulting intValues. int lastIndex = unitsConverters_.size() - 1; BigDecimal carry = unitsConverters_.get(lastIndex).convertInverse(quantity).multiply(EPSILON_MULTIPLIER) .setScale(0, RoundingMode.FLOOR); if (carry.compareTo(BigDecimal.ZERO) <= 0) { // carry is not greater than zero return quantity; } quantity = quantity.subtract(unitsConverters_.get(lastIndex).convert(carry)); intValues.set(lastIndex - 1, intValues.get(lastIndex - 1).add(carry.toBigInteger())); // We don't use the first converter: that one is for the input unit for (int j = lastIndex - 1; j > 0; j--) { carry = unitsConverters_.get(j) .convertInverse(BigDecimal.valueOf(intValues.get(j).longValue())) .multiply(EPSILON_MULTIPLIER) .setScale(0, RoundingMode.FLOOR); if (carry.compareTo(BigDecimal.ZERO) <= 0) { // carry is not greater than zero break; } intValues.set(j, intValues.get(j).subtract(unitsConverters_.get(j).convert(carry).toBigInteger())); intValues.set(j - 1, intValues.get(j - 1).add(carry.toBigInteger())); } return quantity; } @Override public String toString() { return "ComplexUnitsConverter [unitsConverters_=" + unitsConverters_ + ", units_=" + units_ + "]"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy