commonMain.io.nacular.measured.units.Units.kt Maven / Gradle / Ivy
package io.nacular.measured.units
import kotlin.jvm.JvmName
import kotlin.math.roundToInt
/**
* Base class for all types that can represent a unit of measure.
* A Units type can have multiple "members", each being some fraction of the base
* unit for that type. Time for example, might have seconds as the base unit
* and minute as a unit that is 60 times the base unit. This allows for a
* set of different representations of the unit.
*
* @constructor
* @param suffix to use when printing the unit in a human readable way
* @param ratio of this unit relative to the base-unit
*/
abstract class Units(val suffix: String, val ratio: Double = 1.0) {
/**
* Whether there should be a space between the unit's name and the magnitude
* of a value with that unit. Most units are displayed like this: 45 kg. But
* some, like degrees are done w/o the space: 45°
*/
protected open val spaceBetweenMagnitude = true
internal fun measureSuffix() = if (spaceBetweenMagnitude) " $suffix" else suffix
override fun toString() = suffix
override fun hashCode(): Int {
var result = suffix.hashCode()
result = 31 * result + ratio.hashCode()
result = 31 * result + spaceBetweenMagnitude.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Units) return false
if (suffix != other.suffix) return false
if (ratio != other.ratio ) return false
if (spaceBetweenMagnitude != other.spaceBetweenMagnitude) return false
return true
}
}
/**
* Represents the product of two Units: A * B.
*
* @constructor
* @param first unit being multiplied
* @param second unit being multiplied
*/
class UnitsProduct(val first: A, val second: B): Units(if (first==second) "($first)^2" else "$first$second", first.ratio * second.ratio)
typealias Square = UnitsProduct
/**
* Represents the ratio of two Units: A / B.
*
* @constructor
* @param numerator unit being divided
* @param denominator unit dividing numerator
*/
class UnitsRatio(val numerator: A, val denominator: B): Units("$numerator/$denominator", numerator.ratio / denominator.ratio) {
/** The Inverse of this unit. */
val reciprocal by lazy { UnitsRatio(denominator, numerator) }
}
/**
* The inverse of a given Units.
*
* @constructor
* @param unit this is the inverse of
*/
class InverseUnits(val unit: T): Units("1/${unit.suffix}", 1 / unit.ratio)
/**
* Compares two units
* @param other unit to compare
* @return -1 if this unit is smaller, 1 if the other is smaller, and 0 if they are equal
*/
operator fun A.compareTo(other: B): Int = ratio.compareTo(other.ratio)
/**
* @return the smaller of the two Units
*/
fun minOf(first: A, second: B) = if (first < second) first else second
/**
* A quantity with a unit type
*/
class Measure(val amount: Double, val units: T): Comparable> {
/**
* Convert this type into another compatible type.
* Type must share parent
* (eg Mile into Kilometer, because they both are made from Distance)
*/
infix fun `as`(other: A): Measure = if (units == other) this else Measure(this `in` other, other)
infix fun `in`(other: A): Double = if (units == other) amount else amount * (units.ratio / other.ratio)
/**
* Add another compatible quantity to this one
*/
operator fun plus(other: Measure): Measure = minOf(units, other.units).let { Measure((this `in` it) + (other `in` it), it) }
/**
* Subtract a compatible quantity from this one
*/
operator fun minus(other: Measure): Measure = minOf(units, other.units).let { Measure((this `in` it) - (other `in` it), it) }
operator fun unaryMinus(): Measure = Measure(-amount, units)
/**
* Multiply this by a scalar value, used for things like "double this distance",
* "1.5 times the speed", etc
*/
operator fun times(other: Number): Measure = amount * other.toDouble() * units
/**
* Divide this by a scalar, used for things like "halve the speed"
*/
operator fun div(other: Number): Measure = amount / other.toDouble() * units
fun roundToInt(): Measure = amount.roundToInt() * units
/**
* Compare this value with another quantity - which must have the same type
* Units are converted before comparison
*/
override fun compareTo(other: Measure): Int = (this `as` other.units).amount.compareTo(other.amount)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Measure<*>) return false
// if (this.amount == 0.0 && other.amount == 0.0) return true TODO: Should this be true?
val resultUnit = minOf(units, (other as Measure).units)
val a = this `in` resultUnit
val b = other `in` resultUnit
return a == b
}
override fun hashCode(): Int {
return (amount * units.ratio).hashCode()
}
override fun toString(): String = "$amount${units.measureSuffix()}"
}
// region ================ Units * Units Math ============================
/** A * B */ operator fun A. times(other: B ): UnitsProduct = UnitsProduct(this, other)
@JvmName("times7") operator fun A. times(other: InverseUnits ): Double = ratio / other.ratio
/** A * (1 / B) */ operator fun A. times(other: InverseUnits ): UnitsRatio = this / other.unit
/** A * (B / A) */ operator fun A. times(other: UnitsRatio): Measure = ratio / other.denominator.ratio * other.numerator
/** (A / B) * B */ @JvmName("times1") operator fun UnitsRatio. times(other: B ): Measure = other.ratio / denominator.ratio * numerator
/** (A / (B * C)) * B */ @JvmName("times2") operator fun UnitsRatio>.times(other: B ): Measure> = other.ratio / denominator.first.ratio * (numerator / denominator.second)
/** (A / (B * C)) * D) */ @JvmName("times3") operator fun UnitsRatio>.times(other: D ): UnitsRatio, UnitsProduct> = numerator * other / denominator
/** (A / B) * (A / B) */ @JvmName("times4") operator fun UnitsRatio. times(other: UnitsRatio): UnitsRatio, UnitsProduct> = numerator * other.numerator / (denominator * other.denominator)
/** (A / B) * (B / A)) */ @JvmName("times5") operator fun UnitsRatio. times(other: UnitsRatio): Double = numerator * other.numerator / (denominator * other.denominator)
/** (A / B) * (C / D)) */ @JvmName("times6") operator fun UnitsRatio. times(other: UnitsRatio): UnitsRatio, UnitsProduct> = numerator * other.numerator / (denominator * other.denominator)
@JvmName("times8") operator fun InverseUnits. times(other: A ): Double = other.ratio / ratio
@JvmName("times9") operator fun InverseUnits. times(other: B ): UnitsRatio = other * this
// FIXME operator fun UnitsProduct. times(other: InverseUnits ): Measure = units * other
// FIXME operator fun UnitsProduct. times(other: InverseUnits ): A = units * other
// FIXME operator fun UnitsProduct. times(other: UnitsRatio): UnitsRatio = units * other
// FIXME operator fun UnitsProduct. times(other: UnitsRatio): UnitsRatio = units * other
// FIXME operator fun UnitsProduct. times(other: UnitsRatio): UnitsRatio = units * other
// FIXME operator fun UnitsProduct. times(other: UnitsRatio): UnitsRatio = units * other
// FIXME operator fun UnitsProduct. times(other: UnitsRatio): UnitsRatio = units * other
// FIXME operator fun UnitsProduct. times(other: UnitsRatio): UnitsRatio = units * other
// endregion
// region ================ Units / Units Math ============================
// This cannot be defined given the next definition unfortunately
//operator fun A.div(other: A) = this.ratio / other.ratio
/** A / B */ operator fun A.div (other: B ): UnitsRatio = UnitsRatio(this, other)
/** A / (A / B) == A * (B / A) */ operator fun A.div (other: UnitsRatio): Measure = this * other.reciprocal
/** (A * B) / A */ @JvmName("div1") operator fun UnitsProduct.div(other: A ): Measure = first.ratio / other.ratio * second
/** (A * B) / B */ @JvmName("div2") operator fun UnitsProduct.div(other: B ): Measure = second.ratio / other.ratio * first
/** (A * A) / A */ @JvmName("div3") operator fun UnitsProduct.div(other: A ): Measure = first.ratio / other.ratio * second
/** (A * B) / (C * B) */ @JvmName("div1") operator fun UnitsProduct.div(other: UnitsProduct): UnitsRatio = first / other.first
/** (A * B) / (C * A) */ @JvmName("div2") operator fun UnitsProduct.div(other: UnitsProduct): UnitsRatio = second / other.first
/** (A * B) / (B * C) */ @JvmName("div3") operator fun UnitsProduct.div(other: UnitsProduct): UnitsRatio = first / other.second
/** (A * B) / (A * C) */ @JvmName("div4") operator fun UnitsProduct.div(other: UnitsProduct): UnitsRatio = second / other.second
/** (A * B) / (A * A) */ @JvmName("div5") operator fun UnitsProduct.div(other: UnitsProduct): UnitsRatio = second / other.second
/** (A * B) / (A * B) */ @JvmName("div7") operator fun UnitsProduct.div(other: UnitsProduct): Double = ratio / other.ratio
/** (A * B) / (B * A) */ @JvmName("div6") operator fun UnitsProduct.div(other: UnitsProduct): Double = ratio / other.ratio
/** (A * B) / (B * B) */ @JvmName("div8") operator fun UnitsProduct.div(other: UnitsProduct): Measure> = second.ratio / other.second.ratio * (first / other.first)
@JvmName("div1") operator fun UnitsRatio. div(other: UnitsRatio): Double = this * other.reciprocal
@JvmName("div2") operator fun UnitsRatio. div(other: UnitsRatio): UnitsRatio, UnitsProduct> = this * other.reciprocal
@JvmName("div3") operator fun UnitsRatio. div(other: UnitsRatio): UnitsRatio, UnitsProduct> = this * other.reciprocal
operator fun UnitsRatio. div(other: B ): UnitsRatio> = numerator / (denominator * other)
@JvmName("div1") operator fun UnitsRatio, UnitsProduct>.div(other: A ): Measure>> = numerator.first.ratio / other.ratio * (numerator.second / denominator)
@JvmName("div2") operator fun UnitsRatio, UnitsProduct>.div(other: A ): Measure>> = numerator.first.ratio / other.ratio * (numerator.second / denominator)
@JvmName("div3") operator fun UnitsRatio, UnitsProduct>.div(other: A ): Measure>> = numerator.first.ratio / other.ratio * (numerator.second / denominator)
// m/s * (s2/m) => s
operator fun UnitsRatio.div(other: UnitsRatio>): Measure = numerator.ratio / other.numerator.ratio * (other.denominator / denominator)
// endregion
// region ================ Measure * Measure Math ========================
@JvmName("times1") operator fun Measure. times(other: Measure ): Measure> = amount * other.amount * (units * other.units)
@JvmName("times2") operator fun Measure. times(other: Measure>): Measure = amount * other.amount * (units * other.units)
@JvmName("times7") operator fun Measure. times(other: Measure> ): Measure> = amount * other.amount * (units * other.units)
@JvmName("times3") operator fun Measure>. times(other: Measure ): Measure = amount * other.amount * (units * other.units)
@JvmName("times4") operator fun Measure>. times(other: Measure>): Measure, UnitsProduct>> = amount * other.amount * (units * other.units)
@JvmName("times5") operator fun Measure>>.times(other: Measure ): Measure> = amount * other.amount * (units * other.units)
@JvmName("times6") operator fun Measure>>.times(other: Measure ): Measure, UnitsProduct>> = amount * other.amount * (units * other.units)
// endregion
// TODO: Kapt code generation possible?
operator fun Measure.rem(other: Measure): Double = amount % other.amount * (units.ratio % other.units.ratio)
// region ================ Measure / Measure Math ========================
@JvmName("div16") operator fun Measure. div(other: Measure ): Double = amount / other.amount * (units.ratio / other.units.ratio)
@JvmName("div16") operator fun Measure. div(other: Measure ): Measure> = amount / other.amount * (units / other.units)
@JvmName("div1" ) operator fun Measure>.div(other: Measure ): Measure = amount / other.amount * (units / other.units)
@JvmName("div2" ) operator fun Measure>.div(other: Measure ): Measure = amount / other.amount * (units / other.units)
@JvmName("div3" ) operator fun Measure>.div(other: Measure>): Measure> = amount / other.amount * (units / other.units)
@JvmName("div4" ) operator fun Measure>.div(other: Measure>): Measure> = amount / other.amount * (units / other.units)
@JvmName("div5" ) operator fun Measure>.div(other: Measure>): Measure> = amount / other.amount * (units / other.units)
@JvmName("div6" ) operator fun Measure>.div(other: Measure>): Measure> = amount / other.amount * (units / other.units)
@JvmName("div7" ) operator fun Measure>.div(other: Measure>): Measure> = amount / other.amount * (units / other.units)
@JvmName("div8" ) operator fun Measure>.div(other: Measure>): Measure> = amount / other.amount * (units / other.units)
@JvmName("div9" ) operator fun Measure>. div(other: Measure ): Measure>> = amount / other.amount * (units / other.units)
@JvmName("div10") operator fun Measure>. div(other: Measure> ): Measure, UnitsProduct>> = amount / other.amount * (units / other.units)
@JvmName("div11") operator fun Measure>. div(other: Measure>>): Measure = amount / other.amount * (units / other.units)
@JvmName("div12") operator fun Measure, UnitsProduct>>.div(other: Measure ): Measure>> = amount / other.amount * (units / other.units)
@JvmName("div13") operator fun Measure, UnitsProduct>>.div(other: Measure ): Measure>> = amount / other.amount * (units / other.units)
@JvmName("div14") operator fun Measure, UnitsProduct>>.div(other: Measure