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}