package com.lemenzo.calculator.engine import java.math.BigDecimal import java.math.MathContext import java.math.RoundingMode object ConversionEngine { private val mc = MathContext(34, RoundingMode.HALF_EVEN) data class UnitDef( val name: String, val symbol: String, val toBase: (BigDecimal) -> BigDecimal, val fromBase: (BigDecimal) -> BigDecimal ) private fun bd(v: String) = BigDecimal(v, mc) private fun linear(vararg values: Triple) = values.map { (n, s, factor) -> val f = bd(factor) UnitDef(n, s, { it.multiply(f, mc) }, { it.divide(f, mc) }) } val categories: LinkedHashMap> = linkedMapOf( "Length" to linear(Triple("Meter","m","1"), Triple("Kilometer","km","1000"), Triple("Centimeter","cm","0.01"), Triple("Millimeter","mm","0.001"), Triple("Mile","mi","1609.344"), Triple("Yard","yd","0.9144"), Triple("Foot","ft","0.3048"), Triple("Inch","in","0.0254"), Triple("Nautical mile","nmi","1852")), "Area" to linear(Triple("Square meter","m²","1"), Triple("Square kilometer","km²","1000000"), Triple("Square foot","ft²","0.09290304"), Triple("Square yard","yd²","0.83612736"), Triple("Acre","ac","4046.8564224"), Triple("Hectare","ha","10000")), "Volume" to linear(Triple("Liter","L","1"), Triple("Milliliter","mL","0.001"), Triple("Cubic meter","m³","1000"), Triple("US gallon","gal","3.785411784"), Triple("US quart","qt","0.946352946"), Triple("US pint","pt","0.473176473"), Triple("US cup","cup","0.2365882365")), "Mass" to linear(Triple("Kilogram","kg","1"), Triple("Gram","g","0.001"), Triple("Milligram","mg","0.000001"), Triple("Metric ton","t","1000"), Triple("Pound","lb","0.45359237"), Triple("Ounce","oz","0.028349523125"), Triple("Stone","st","6.35029318")), "Speed" to linear(Triple("Meter/second","m/s","1"), Triple("Kilometer/hour","km/h","0.2777777777777777777777777777777778"), Triple("Mile/hour","mph","0.44704"), Triple("Knot","kn","0.5144444444444444444444444444444444")), "Time" to linear(Triple("Second","s","1"), Triple("Millisecond","ms","0.001"), Triple("Minute","min","60"), Triple("Hour","h","3600"), Triple("Day","d","86400"), Triple("Week","wk","604800")), "Pressure" to linear(Triple("Pascal","Pa","1"), Triple("Kilopascal","kPa","1000"), Triple("Bar","bar","100000"), Triple("Atmosphere","atm","101325"), Triple("PSI","psi","6894.757293168")), "Energy" to linear(Triple("Joule","J","1"), Triple("Kilojoule","kJ","1000"), Triple("Watt hour","Wh","3600"), Triple("Kilowatt hour","kWh","3600000"), Triple("Calorie","cal","4.184"), Triple("Kilocalorie","kcal","4184")), "Power" to linear(Triple("Watt","W","1"), Triple("Kilowatt","kW","1000"), Triple("Megawatt","MW","1000000"), Triple("Horsepower","hp","745.699871582")), "Force" to linear(Triple("Newton","N","1"), Triple("Kilonewton","kN","1000"), Triple("Pound-force","lbf","4.4482216152605")), "Frequency" to linear(Triple("Hertz","Hz","1"), Triple("Kilohertz","kHz","1000"), Triple("Megahertz","MHz","1000000"), Triple("Gigahertz","GHz","1000000000")), "Angle" to linear(Triple("Radian","rad","1"), Triple("Degree","°","0.01745329251994329576923690768488613"), Triple("Grad","grad","0.01570796326794896619231321691639751")), "Data" to linear(Triple("Byte","B","1"), Triple("Kilobyte","KB","1000"), Triple("Megabyte","MB","1000000"), Triple("Gigabyte","GB","1000000000"), Triple("Terabyte","TB","1000000000000"), Triple("Kibibyte","KiB","1024"), Triple("Mebibyte","MiB","1048576"), Triple("Gibibyte","GiB","1073741824"), Triple("Tebibyte","TiB","1099511627776")), "Temperature" to listOf( UnitDef("Celsius","°C", { it }, { it }), UnitDef("Fahrenheit","°F", { it.subtract(bd("32"), mc).multiply(bd("5"), mc).divide(bd("9"), mc) }, { it.multiply(bd("9"), mc).divide(bd("5"), mc).add(bd("32"), mc) }), UnitDef("Kelvin","K", { it.subtract(bd("273.15"), mc) }, { it.add(bd("273.15"), mc) }) ) ) fun convert(value: BigDecimal, from: UnitDef, to: UnitDef): BigDecimal = to.fromBase(from.toBase(value)).round(mc).stripTrailingZeros() fun format(value: BigDecimal): String { if (value.compareTo(BigDecimal.ZERO) == 0) return "0" val v = value.round(MathContext(15, RoundingMode.HALF_EVEN)).stripTrailingZeros() val a = v.abs() return if (a >= bd("1E15") || a < bd("1E-10")) v.toEngineeringString() else v.toPlainString() } }