001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.math;
020
021import java.util.concurrent.TimeUnit;
022
023import icy.util.StringUtil;
024
025/**
026 * Unit conversion utilities class.
027 * 
028 * @author Thomas Provoost & Stephane Dallongeville
029 */
030public class UnitUtil
031{
032    /**
033     * Constants for special characters
034     */
035    public static final char MICRO_CHAR = '\u00B5';
036    public static final String MICRO_STRING = "\u00B5";
037
038    public static enum UnitPrefix
039    {
040        GIGA, MEGA, KILO, NONE, MILLI, MICRO, NANO, PICO;
041
042        @Override
043        public String toString()
044        {
045            switch (this)
046            {
047                case GIGA:
048                    return "G";
049                case KILO:
050                    return "k";
051                case MEGA:
052                    return "M";
053                case MILLI:
054                    return "m";
055                case MICRO:
056                    return MICRO_STRING;
057                case NANO:
058                    return "n";
059                case PICO:
060                    return "p";
061                case NONE:
062                    return "";
063                default:
064                    return "x";
065            }
066        }
067    };
068
069    /**
070     * Return the specified value as "bytes" string :<br>
071     * 1024 --> "1 KB"<br>
072     * 1024*1000 --> "1 MB"<br>
073     * 1024*1000*1000 --> "1 GB"<br>
074     * ...<br>
075     */
076    public static String getBytesString(double value)
077    {
078        final double absValue = Math.abs(value);
079
080        // TB
081        if (absValue > (512d * 1024d * 1000d * 1000d))
082            return Double.toString(MathUtil.round(value / (1024d * 1000d * 1000d * 1000d), 1)) + " TB";
083        // GB
084        else if (absValue > (512d * 1024d * 1000d))
085            return Double.toString(MathUtil.round(value / (1024d * 1000d * 1000d), 1)) + " GB";
086        // MB
087        else if (absValue > (512d * 1024d))
088            return Double.toString(MathUtil.round(value / (1024d * 1000d), 1)) + " MB";
089        // KB
090        else if (absValue > 512d)
091            return Double.toString(MathUtil.round(value / 1024d, 1)) + " KB";
092        // B
093        return Double.toString(MathUtil.round(value, 1)) + " B";
094    }
095
096    /**
097     * Get the best unit with the given value and {@link UnitPrefix}.<br>
098     * By best unit we adapt the output unit so the value stay between 0.1 --> 100 range (for
099     * dimension 1).<br>
100     * Be careful, this method is supposed to be used with unit in <b>decimal</b>
101     * system. For sexagesimal system, please use {@link #getBestTimeUnit(double)} or {@link TimeUnit} methods.<br/>
102     * <br/>
103     * Example: <code>getBestUnit(0.01, UnitPrefix.MILLI, 1)</code> will return <code>UnitPrefix.MICRO</code><br/>
104     * 
105     * @param value
106     *        : value used to get the best unit.
107     * @param currentUnit
108     *        : current unit of the value.
109     * @param dimension
110     *        : current unit dimension.
111     * @return Return the best unit
112     * @see #getValueInUnit(double, UnitPrefix, UnitPrefix)
113     */
114    public static UnitPrefix getBestUnit(double value, UnitPrefix currentUnit, int dimension)
115    {
116        // special case
117        if (value == 0d)
118            return currentUnit;
119
120        int typeInd = currentUnit.ordinal();
121        double v = value;
122        final int maxInd = UnitPrefix.values().length - 1;
123        final double factor = Math.pow(1000d, dimension);
124        final double midFactor = Math.pow(100d, dimension);
125
126        while (((int) v == 0) && (typeInd < maxInd))
127        {
128            v *= factor;
129            typeInd++;
130        }
131        while (((int) (v / midFactor) != 0) && (typeInd > 0))
132        {
133            v /= factor;
134            typeInd--;
135        }
136
137        return UnitPrefix.values()[typeInd];
138    }
139
140    /**
141     * Get the best unit with the given value and {@link UnitPrefix}. By best unit we adapt the
142     * output unit so the value stay between 0.1 --> 100 range (for dimension 1).<br>
143     * Be careful, this method is supposed to be used with unit in <b>decimal</b>
144     * system. For sexagesimal system, please use {@link #getBestTimeUnit(double)} or {@link TimeUnit} methods.<br/>
145     * Example: <code>getBestUnit(0.01, UnitPrefix.MILLI, 1)</code> will return <code>UnitPrefix.MICRO</code><br/>
146     * 
147     * @param value
148     *        : value used to get the best unit.
149     * @param currentUnit
150     *        : current unit of the value.
151     * @return Return the best unit
152     * @see #getValueInUnit(double, UnitPrefix, UnitPrefix)
153     */
154    public static UnitPrefix getBestUnit(double value, UnitPrefix currentUnit)
155    {
156        return getBestUnit(value, currentUnit, 1);
157    }
158
159    /**
160     * Return the value from a specific unit to another unit.<br/>
161     * Be careful, this method is supposed to be used with unit in <b>decimal</b>
162     * system. For sexagesimal system, please use {@link #getBestTimeUnit(double)} or {@link TimeUnit} methods.<br/>
163     * <b>Example:</b><br/>
164     * <ul>
165     * <li>value = 0.01</li>
166     * <li>currentUnit = {@link UnitPrefix#MILLI}</li>
167     * <li>wantedUnit = {@link UnitPrefix#MICRO}</li>
168     * <li>returns: 10</li>
169     * </ul>
170     * 
171     * @param value
172     *        : Original value.
173     * @param currentUnit
174     *        : current unit
175     * @param wantedUnit
176     *        : wanted unit
177     * @param dimension
178     *        : unit dimension.
179     * @return Return a double value in the <code>wantedUnit</code> unit.
180     * @see #getBestUnit(double, UnitPrefix)
181     */
182    public static double getValueInUnit(double value, UnitPrefix currentUnit, UnitPrefix wantedUnit, int dimension)
183    {
184        int currentOrdinal = currentUnit.ordinal();
185        int wantedOrdinal = wantedUnit.ordinal();
186        double result = value;
187        final double factor = Math.pow(1000d, dimension);
188
189        while (currentOrdinal < wantedOrdinal)
190        {
191            result *= factor;
192            currentOrdinal++;
193        }
194        while (currentOrdinal > wantedOrdinal)
195        {
196            result /= factor;
197            currentOrdinal--;
198        }
199
200        return result;
201    }
202
203    /**
204     * Return the value from a specific unit to another unit.<br/>
205     * Be careful, this method is supposed to be used with unit in <b>decimal</b>
206     * system. For sexagesimal system, please use {@link #getBestTimeUnit(double)} or {@link TimeUnit} methods.<br/>
207     * <b>Example:</b><br/>
208     * <ul>
209     * <li>value = 0.01</li>
210     * <li>currentUnit = {@link UnitPrefix#MILLI}</li>
211     * <li>wantedUnit = {@link UnitPrefix#MICRO}</li>
212     * <li>returns: 10</li>
213     * </ul>
214     * 
215     * @param value
216     *        : Original value.
217     * @param currentUnit
218     *        : current unit
219     * @param wantedUnit
220     *        : wanted unit
221     * @return Return a double value in the <code>wantedUnit</code> unit.
222     * @see #getBestUnit(double, UnitPrefix)
223     */
224    public static double getValueInUnit(double value, UnitPrefix currentUnit, UnitPrefix wantedUnit)
225    {
226        return getValueInUnit(value, currentUnit, wantedUnit, 1);
227    }
228
229    /**
230     * This method returns a string containing the value rounded to a specified
231     * number of decimals and its best unit prefix. This method is supposed to
232     * be used with meters only.
233     * 
234     * @param value
235     *        : value to display
236     * @param decimals
237     *        : number of decimals to keep
238     * @param currentUnit
239     *        : current unit prefix (Ex: {@link UnitPrefix#MILLI})
240     */
241    public static String getBestUnitInMeters(double value, int decimals, UnitPrefix currentUnit)
242    {
243        UnitPrefix unitPxSize = getBestUnit(value, currentUnit);
244        double distanceMeters = getValueInUnit(value, currentUnit, unitPxSize);
245
246        return StringUtil.toString(distanceMeters, decimals) + unitPxSize + "m";
247    }
248
249    /**
250     * Return the best unit to display the value. The best unit is chosen
251     * according to the precision. <br/>
252     * <b>Example:</b>
253     * <ul>
254     * <li>62001 ms -> {@link TimeUnit#MILLISECONDS}</li>
255     * <li>62000 ms -> {@link TimeUnit#SECONDS}</li>
256     * <li>60000 ms -> {@link TimeUnit#MINUTES}</li>
257     * </ul>
258     * 
259     * @param valueInMs
260     *        : value in milliseconds.
261     * @return Return a {@link TimeUnit} enumeration value.
262     */
263    public static TimeUnit getBestTimeUnit(double valueInMs)
264    {
265        if (valueInMs % 1000 != 0)
266            return TimeUnit.MILLISECONDS;
267        if (valueInMs % 60000 != 0)
268            return TimeUnit.SECONDS;
269        if (valueInMs % 3600000 != 0)
270            return TimeUnit.MINUTES;
271
272        return TimeUnit.HOURS;
273    }
274
275    /**
276     * @deprecated Use {@link #getBestTimeUnit(double)} instead.
277     */
278    @Deprecated
279    public static TimeUnit getBestUnit(double valueInMs)
280    {
281        return getBestTimeUnit(valueInMs);
282    }
283
284    /**
285     * Display the time with a comma and a given precision.
286     * 
287     * @param valueInMs
288     *        : value in milliseconds
289     * @param precision
290     *        : number of decimals after comma
291     * @return <b>Example:</b> "2.5 h", "1.543 min", "15 ms".
292     */
293    public static String displayTimeAsStringWithComma(double valueInMs, int precision, TimeUnit unit)
294    {
295        String result;
296        double v = valueInMs;
297
298        switch (unit)
299        {
300            case DAYS:
301                v /= 24d * 60d * 60d * 1000d;
302                result = StringUtil.toString(v, precision) + " d";
303                break;
304
305            case HOURS:
306                v /= 60d * 60d * 1000d;
307                result = StringUtil.toString(v, precision) + " h";
308                break;
309
310            default:
311            case MINUTES:
312                v /= 60d * 1000d;
313                result = StringUtil.toString(v, precision) + " min";
314                break;
315
316            case SECONDS:
317                v /= 1000d;
318                result = StringUtil.toString(v, precision) + " sec";
319                break;
320
321            case MILLISECONDS:
322                result = StringUtil.toString(v, precision) + " ms";
323                break;
324
325            case NANOSECONDS:
326                v *= 1000d;
327                result = StringUtil.toString(v, precision) + " ns";
328                break;
329        }
330
331        return result;
332    }
333
334    /**
335     * Display the time with a comma and a given precision.
336     * 
337     * @param valueInMs
338     *        : value in milliseconds
339     * @param precision
340     *        : number of decimals after comma
341     * @return <b>Example:</b> "2.5 h", "1.543 min", "15 ms".
342     */
343    public static String displayTimeAsStringWithComma(double valueInMs, int precision)
344    {
345        double v = Math.abs(valueInMs);
346
347        if (v >= 24d * 60d * 60d * 1000d)
348            return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.DAYS);
349        if (v >= 60d * 60d * 1000d)
350            return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.HOURS);
351        else if (v >= 60d * 1000d)
352            return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.MINUTES);
353        else if (v >= 1000d)
354            return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.SECONDS);
355        else if (v < 1d)
356            return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.MILLISECONDS);
357        else
358            return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.NANOSECONDS);
359    }
360
361    /**
362     * Display the time with all the units.
363     * 
364     * @param valueInMs
365     *        : value in milliseconds
366     * @param displayZero
367     *        : Even if a unit is not relevant (equals to zero), it will be displayed.
368     * @return <b>Example:</b> "2h 3min 40sec 350ms".
369     */
370    public static String displayTimeAsStringWithUnits(double valueInMs, boolean displayZero)
371    {
372        String result = "";
373        double v = Math.abs(valueInMs);
374
375        if (v >= 24d * 60d * 60d * 1000d)
376        {
377            result += (int) (v / (24d * 60d * 60d * 1000d)) + "d ";
378            v %= 24d * 60d * 60d * 1000d;
379        }
380        else if (displayZero)
381            result += "0d ";
382        if (v >= 60d * 60d * 1000d)
383        {
384            result += (int) (v / (60d * 60d * 1000d)) + "h ";
385            v %= 60d * 60d * 1000d;
386        }
387        else if (displayZero)
388            result += "00h ";
389        if (v >= 60d * 1000d)
390        {
391            result += (int) (v / (60d * 1000d)) + "min ";
392            v %= 60d * 1000d;
393        }
394        else if (displayZero)
395            result += "00min ";
396        if (v >= 1000d)
397        {
398            result += (int) (v / 1000d) + "sec ";
399            v %= 1000d;
400        }
401        else if (displayZero)
402            result += "00sec ";
403        if (v >= 0d)
404            result += StringUtil.toString(v, 3) + "ms";
405        else if (displayZero)
406            result += "000ms";
407
408        return result;
409    }
410}