/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Icy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.math;
import java.util.concurrent.TimeUnit;
import icy.util.StringUtil;
/**
* Unit conversion utilities class.
*
* @author Thomas Provoost & Stephane Dallongeville
*/
public class UnitUtil
{
/**
* Constants for special characters
*/
public static final char MICRO_CHAR = '\u00B5';
public static final String MICRO_STRING = "\u00B5";
public static enum UnitPrefix
{
GIGA, MEGA, KILO, NONE, MILLI, MICRO, NANO, PICO;
@Override
public String toString()
{
switch (this)
{
case GIGA:
return "G";
case KILO:
return "k";
case MEGA:
return "M";
case MILLI:
return "m";
case MICRO:
return MICRO_STRING;
case NANO:
return "n";
case PICO:
return "p";
case NONE:
return "";
default:
return "x";
}
}
};
/**
* Return the specified value as "bytes" string :<br>
* 1024 --> "1 KB"<br>
* 1048576 --> "1 MB"<br>
* ...<br>
*/
public static String getBytesString(double value)
{
final double absValue = Math.abs(value);
// TB
if (absValue > 549755813888d)
return Double.toString(MathUtil.round(value / 1099511627776d, 1)) + " TB";
// GB
else if (absValue > 536870912d)
return Double.toString(MathUtil.round(value / 1073741824d, 1)) + " GB";
// MB
else if (absValue > 524288d)
return Double.toString(MathUtil.round(value / 1048576d, 1)) + " MB";
// KB
else if (absValue > 512d)
return Double.toString(MathUtil.round(value / 1024d, 1)) + " KB";
// B
return Double.toString(MathUtil.round(value, 1)) + " B";
}
/**
* Get the best unit with the given value and {@link UnitPrefix}.<br>
* By best unit we adapt the output unit so the value stay between 0.1 --> 100 range (for
* dimension 1).<br>
* Be careful, this method is supposed to be used with unit in <b>decimal</b>
* system. For sexagesimal system, please use {@link #getBestTimeUnit(double)} or {@link TimeUnit} methods.<br/>
* <br/>
* Example: <code>getBestUnit(0.01, UnitPrefix.MILLI, 1)</code> will return <code>UnitPrefix.MICRO</code><br/>
*
* @param value
* : value used to get the best unit.
* @param currentUnit
* : current unit of the value.
* @param dimension
* : current unit dimension.
* @return Return the best unit
* @see #getValueInUnit(double, UnitPrefix, UnitPrefix)
*/
public static UnitPrefix getBestUnit(double value, UnitPrefix currentUnit, int dimension)
{
// special case
if (value == 0d)
return currentUnit;
int typeInd = currentUnit.ordinal();
double v = value;
final int maxInd = UnitPrefix.values().length - 1;
final double factor = Math.pow(1000d, dimension);
final double midFactor = Math.pow(100d, dimension);
while (((int) v == 0) && (typeInd < maxInd))
{
v *= factor;
typeInd++;
}
while (((int) (v / midFactor) != 0) && (typeInd > 0))
{
v /= factor;
typeInd--;
}
return UnitPrefix.values()[typeInd];
}
/**
* Get the best unit with the given value and {@link UnitPrefix}. By best unit we adapt the
* output unit so the value stay between 0.1 --> 100 range (for dimension 1).<br>
* Be careful, this method is supposed to be used with unit in <b>decimal</b>
* system. For sexagesimal system, please use {@link #getBestTimeUnit(double)} or {@link TimeUnit} methods.<br/>
* Example: <code>getBestUnit(0.01, UnitPrefix.MILLI, 1)</code> will return <code>UnitPrefix.MICRO</code><br/>
*
* @param value
* : value used to get the best unit.
* @param currentUnit
* : current unit of the value.
* @return Return the best unit
* @see #getValueInUnit(double, UnitPrefix, UnitPrefix)
*/
public static UnitPrefix getBestUnit(double value, UnitPrefix currentUnit)
{
return getBestUnit(value, currentUnit, 1);
}
/**
* Return the value from a specific unit to another unit.<br/>
* Be careful, this method is supposed to be used with unit in <b>decimal</b>
* system. For sexagesimal system, please use {@link #getBestTimeUnit(double)} or {@link TimeUnit} methods.<br/>
* <b>Example:</b><br/>
* <ul>
* <li>value = 0.01</li>
* <li>currentUnit = {@link UnitPrefix#MILLI}</li>
* <li>wantedUnit = {@link UnitPrefix#MICRO}</li>
* <li>returns: 10</li>
* </ul>
*
* @param value
* : Original value.
* @param currentUnit
* : current unit
* @param wantedUnit
* : wanted unit
* @param dimension
* : unit dimension.
* @return Return a double value in the <code>wantedUnit</code> unit.
* @see #getBestUnit(double, UnitPrefix)
*/
public static double getValueInUnit(double value, UnitPrefix currentUnit, UnitPrefix wantedUnit, int dimension)
{
int currentOrdinal = currentUnit.ordinal();
int wantedOrdinal = wantedUnit.ordinal();
double result = value;
final double factor = Math.pow(1000d, dimension);
while (currentOrdinal < wantedOrdinal)
{
result *= factor;
currentOrdinal++;
}
while (currentOrdinal > wantedOrdinal)
{
result /= factor;
currentOrdinal--;
}
return result;
}
/**
* Return the value from a specific unit to another unit.<br/>
* Be careful, this method is supposed to be used with unit in <b>decimal</b>
* system. For sexagesimal system, please use {@link #getBestTimeUnit(double)} or {@link TimeUnit} methods.<br/>
* <b>Example:</b><br/>
* <ul>
* <li>value = 0.01</li>
* <li>currentUnit = {@link UnitPrefix#MILLI}</li>
* <li>wantedUnit = {@link UnitPrefix#MICRO}</li>
* <li>returns: 10</li>
* </ul>
*
* @param value
* : Original value.
* @param currentUnit
* : current unit
* @param wantedUnit
* : wanted unit
* @return Return a double value in the <code>wantedUnit</code> unit.
* @see #getBestUnit(double, UnitPrefix)
*/
public static double getValueInUnit(double value, UnitPrefix currentUnit, UnitPrefix wantedUnit)
{
return getValueInUnit(value, currentUnit, wantedUnit, 1);
}
/**
* This method returns a string containing the value rounded to a specified
* number of decimals and its best unit prefix. This method is supposed to
* be used with meters only.
*
* @param value
* : value to display
* @param decimals
* : number of decimals to keep
* @param currentUnit
* : current unit prefix (Ex: {@link UnitPrefix#MILLI})
*/
public static String getBestUnitInMeters(double value, int decimals, UnitPrefix currentUnit)
{
UnitPrefix unitPxSize = getBestUnit(value, currentUnit);
double distanceMeters = getValueInUnit(value, currentUnit, unitPxSize);
return StringUtil.toString(distanceMeters, decimals) + unitPxSize + "m";
}
/**
* Return the best unit to display the value. The best unit is chosen
* according to the precision. <br/>
* <b>Example:</b>
* <ul>
* <li>62001 ms -> {@link TimeUnit#MILLISECONDS}</li>
* <li>62000 ms -> {@link TimeUnit#SECONDS}</li>
* <li>60000 ms -> {@link TimeUnit#MINUTES}</li>
* </ul>
*
* @param valueInMs
* : value in milliseconds.
* @return Return a {@link TimeUnit} enumeration value.
*/
public static TimeUnit getBestTimeUnit(double valueInMs)
{
if (valueInMs % 1000 != 0)
return TimeUnit.MILLISECONDS;
if (valueInMs % 60000 != 0)
return TimeUnit.SECONDS;
if (valueInMs % 3600000 != 0)
return TimeUnit.MINUTES;
return TimeUnit.HOURS;
}
/**
* @deprecated Use {@link #getBestTimeUnit(double)} instead.
*/
@Deprecated
public static TimeUnit getBestUnit(double valueInMs)
{
return getBestTimeUnit(valueInMs);
}
/**
* Display the time with a comma and a given precision.
*
* @param valueInMs
* : value in milliseconds
* @param precision
* : number of decimals after comma
* @return <b>Example:</b> "2.5 h", "1.543 min", "15 ms".
*/
public static String displayTimeAsStringWithComma(double valueInMs, int precision, TimeUnit unit)
{
String result;
double v = valueInMs;
switch (unit)
{
case DAYS:
v /= 24d * 60d * 60d * 1000d;
result = StringUtil.toString(v, precision) + " d";
break;
case HOURS:
v /= 60d * 60d * 1000d;
result = StringUtil.toString(v, precision) + " h";
break;
default:
case MINUTES:
v /= 60d * 1000d;
result = StringUtil.toString(v, precision) + " min";
break;
case SECONDS:
v /= 1000d;
result = StringUtil.toString(v, precision) + " sec";
break;
case MILLISECONDS:
result = StringUtil.toString(v, precision) + " ms";
break;
case NANOSECONDS:
v *= 1000d;
result = StringUtil.toString(v, precision) + " ns";
break;
}
return result;
}
/**
* Display the time with a comma and a given precision.
*
* @param valueInMs
* : value in milliseconds
* @param precision
* : number of decimals after comma
* @return <b>Example:</b> "2.5 h", "1.543 min", "15 ms".
*/
public static String displayTimeAsStringWithComma(double valueInMs, int precision)
{
double v = Math.abs(valueInMs);
if (v >= 24d * 60d * 60d * 1000d)
return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.DAYS);
if (v >= 60d * 60d * 1000d)
return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.HOURS);
else if (v >= 60d * 1000d)
return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.MINUTES);
else if (v >= 1000d)
return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.SECONDS);
else if (v < 1d)
return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.MILLISECONDS);
else
return displayTimeAsStringWithComma(valueInMs, precision, TimeUnit.NANOSECONDS);
}
/**
* Display the time with all the units.
*
* @param valueInMs
* : value in milliseconds
* @param displayZero
* : Even if a unit is not relevant (equals to zero), it will be displayed.
* @return <b>Example:</b> "2h 3min 40sec 350ms".
*/
public static String displayTimeAsStringWithUnits(double valueInMs, boolean displayZero)
{
String result = "";
double v = valueInMs;
if (v >= 24d * 60d * 60d * 1000d)
{
result += (int) (v / (24d * 60d * 60d * 1000d)) + "d ";
v %= 24d * 60d * 60d * 1000d;
}
else if (displayZero)
result += "0d ";
if (v >= 60d * 60d * 1000d)
{
result += (int) (v / (60d * 60d * 1000d)) + "h ";
v %= 60d * 60d * 1000d;
}
else if (displayZero)
result += "00h ";
if (v >= 60d * 1000d)
{
result += (int) (v / (60d * 1000d)) + "min ";
v %= 60d * 1000d;
}
else if (displayZero)
result += "00min ";
if (v >= 1000d)
{
result += (int) (v / 1000d) + "sec ";
v %= 1000d;
}
else if (displayZero)
result += "00sec ";
if (v != 0d)
result += StringUtil.toString(v, 2) + "ms";
else if (displayZero)
result += "000ms";
return result;
}
}