/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx import scala.concurrent.duration.{FiniteDuration => ScalaDuration} object Measure{ private val prefix:Map[Char,Double] = Map( 'E' -> 1E18, //exa 'P' -> 1E15, //peta 'T' -> 1E12, //tera 'G' -> 1E9, //giga 'M' -> 1E6, //mega 'k' -> 1E3, //kilo // 'h' -> 1E2, //hecto // 'da' -> 1E1, //deca // 'd' -> 1E-1, //deci // 'c' -> 1E-2, //centi 'm' -> 1E-3, //milli 'u' -> 1E-6, //micro 'μ' -> 1E-6, //micro 'n' -> 1E-9, //nano 'p' -> 1E-12, //pico // 'f' -> 1E-15, //femto //FIXME turn this back after moving to try prefix (the measure) 'a' -> 1E-18 //atto ) //private def singularize(str:String):String = if((str.size > 1) && (str endsWith "s")) str.dropRight(1) else str private val decDigits = ".-+0123456789Ee*".toSet def apply(str:String):Measure = { val (num,unitTemp) = str span {decDigits contains _} val unit = unitTemp.trim val value = if(num == "") 1d else num.split('*').foldLeft{1d}{case (v, n) => v*n.toDouble} val (scale, baseUnit) = unit.size match { case 0 => (1.0,"") //case 1 => (prefix get unit(0)) map { _ -> ""} getOrElse (1.0,unit) case 2 => (prefix get unit(0)) map { _ -> unit(1).toString } getOrElse Tuple2(1.0,unit) case _ => (1.0,unit) } //println(s"----\nunit:$unit\nscale:$scale\nbaseUnit:$baseUnit") Measure(value*scale, baseUnit) } } case class Measure(value:Double,unit:String){ def base(units:Map[String,Double]):Double = if(unit=="") value else value*units(unit) } trait Units{ val units:Map[String,Double] def parseToBaseValue(str:String):Double = Measure(str) base units /**A simple string test to see if the string is defined with valid units*/ def isDefined(str:String):Boolean = units.keys.exists{str endsWith _} } //TODO it feels like there are to many redundant typeclasses around here; Meausre, BaseValue, Unit, BaseValueBuilder; all to support anyval wrapping? // maybe make the trait Units into the a typeclass like UnitBuilder /**typeclass for generic building/(and extracting) basevalues for unit types (essentially a generic way to wrap doubles) */ trait BaseValueBuilder[A]{ //trait BaseValueBuilder[A] extends Boundable[A] /*because doubles are boundable*/ with Parsable[A] /*with required unit string map*/ //-- required def apply(baseValue:Double):A def baseValueOf(unit:A):Double //-- derived //TODO deprecate maybe since use cases should just use the functions directly to remove the expense of creating a function object final def map(x:A)(f:Double => Double):A = apply(f(baseValueOf(x))) } trait BoundableBaseValue[A <: BaseValue[A]] extends Bound.Boundable[A]{ def lessThan(a:A,b:A):Boolean = a.baseValue < b.baseValue def interpolate(min:A,max:A, ratio:Double):A = (min lerp max)(ratio) def ratioOf(min:A,max:A, x:A):Double = (x.baseValue-min.baseValue)/(max.baseValue-min.baseValue) override def dist(min:A,max:A):Double = (max.baseValue - min.baseValue).abs override def gain(min:A,max:A):Double = (max.baseValue/min.baseValue).abs } //TODO remove this since it is not needed // trait BaseValueOrdering[A <: BaseValue[A]] extends scala.math.Ordering[A]{ // } //--- the following provide prepended scaler values but requires the use of // import scala.language.implicitConversions // at the call site, therefor consider removing // note these behave similar to DrxDouble and DrxInt but prevent the need to import the implicit conversionis, but rather simple require the sin tax of language imports /* class ScalarDouble(val v:Double){ def *[A](other:BaseValue[A]):A = other*v } object ScalarDouble{ implicit def fromDouble(v:Double) = new ScalarDouble(v) } class ScalarInt(val v:Int){ def *[A](other:BaseValue[A]):A = other*v } object ScalarInt{ implicit def fromInt(v:Int) = new ScalarInt(v) } */ /**primary base trait for all measurment unit types*/ trait BaseValue[A] extends Any with scala.math.Ordered[A] { //---required traits def baseValue:Double // def baseSymbol:String def apply(v:Double):A //---derived traits def ~(that:A)(implicit b:Bound.Boundable[A]):Bound[A] = Bound(apply(baseValue),that) def ~>(that:BaseValue[A])(implicit b:Bound.Boundable[A]):Bound[A] = Bound(apply(this.baseValue),apply(this.baseValue+that.baseValue)) def +-(eps:BaseValue[A])(implicit b:Bound.Boundable[A]):Bound[A] = Bound(apply(baseValue-eps.baseValue) , apply(baseValue+eps.baseValue)) def ~=(that:BaseValue[A])(implicit eps:Eps):Boolean = math.abs(baseValue-that.baseValue) < eps.v def +(that:BaseValue[A])(implicit d1: DummyImplicit):A = apply(this.baseValue + that.baseValue) def -(that:BaseValue[A])(implicit d1: DummyImplicit):A = apply(this.baseValue - that.baseValue) def /(that:BaseValue[A])(implicit d1: DummyImplicit):Double = this.baseValue/that.baseValue //cancelation def *(scale:Double)(implicit d1: DummyImplicit):A = apply(this.baseValue*scale) def *(scale:Int)(implicit d1: DummyImplicit):A = apply(this.baseValue*scale.toDouble) def *(scale:Long)(implicit d1: DummyImplicit):A = apply(this.baseValue*scale.toDouble) def /(scale:Double)(implicit d1: DummyImplicit):A = apply(this.baseValue/scale) def /(scale:Int)(implicit d1: DummyImplicit):A = apply(this.baseValue/scale.toDouble) def /(scale:Long)(implicit d1: DummyImplicit):A = apply(this.baseValue/scale.toDouble) def unary_- :A = apply(-this.baseValue) def abs:A = apply(this.baseValue.abs) def compare(that:BaseValue[A]):Int = this.baseValue compare that.baseValue def lerp(that:BaseValue[A])(ratio:Double):A = apply(this.baseValue + ratio*(that.baseValue-this.baseValue) ) //TODO 2017-07-30("should be format instead","v0.2.15") def nice:String } object Time extends Units{ private val sPerHr:Double = 3600 private val sPerDay:Double = 3600*24 private val sPerYr:Double = 3600*24*365.242198781 //this is the definition of a true year averaged over time private val sPerWk:Double = sPerDay*7 private val sPerMo:Double = sPerYr/12 //val NANOSECONDS = java.util.concurrent.TimeUnit.NANOSECONDS private lazy val timer = new java.util.Timer(true) private val ms0 = Date.now.ms private[drx] def initMs = Date.now.ms - ms0 val units:Map[String,Double] = Map( "s" -> 1, "sec" -> 1, "second" -> 1, "ms" -> 1E-3, "us" -> 1E-6, "μs" -> 1E-6, "ns" -> 1E-9, "ps" -> 1E-12, "fs" -> 1E-15, "m" -> 60, "min" -> 60, "minute" -> 60, "h" -> 3600, "hr" -> 3600, "hour" -> 3600, "d" -> sPerDay, "day" -> sPerDay, "w" -> sPerWk, "wk" -> sPerWk, "week" -> sPerWk, "mo" -> sPerMo, "mon" -> sPerMo, "month" -> sPerMo, "y" -> sPerYr, "yr" -> sPerYr, "year" -> sPerYr, //tropical year (vernal equinox mean) "siderealyear" -> sPerDay*365.256360417, //1 time around the sun "commonyear" -> sPerDay*365, "leapyear" -> sPerDay*366, "decade" -> sPerYr*10, "century" -> sPerYr*100, "kyr" -> sPerYr*1000, "millenium" -> sPerYr*1000, "Gyr" -> sPerYr*1E9, "Gy" -> sPerYr*1E9 ) private val formatLookup:Array[(Time,String)] = "Gyr kyr century yr mon wk day hr min s ms us ns ps fs".split(' ').map{k => (Time(units(k)),k)} def apply(str:String):Time = Time(parseToBaseValue(str)) def apply(value:Double,unit:String):Time = Time(value*parseToBaseValue(unit)) def apply(value:Long,unit:String):Time = Time(value.toDouble*parseToBaseValue(unit)) def apply(bound:Bound[Date]):Time = Time(math.abs(bound.max.ms - bound.min.ms)*1E-3) def apply(duration:ScalaDuration):Time = Time(duration.toNanos/1E9) implicit object BoundableTime extends BoundableBaseValue[Time] implicit object ParsableTime extends Parsable[Time]{ def apply(v:String):Time = Time(v) } implicit object BaseValueBuilderTime extends BaseValueBuilder[Time]{ def apply(baseValue:Double):Time = Time(baseValue) def baseValueOf(unit:Time):Double = unit.baseValue } // TODO remove this implict conversion and put this in the package like predef import scala.language.implicitConversions implicit def drxTime2ScalaDuration(time:Time):ScalaDuration = time.asScala implicit def scalaDuration2DrxTime(duration:ScalaDuration):Time = Time(duration) def format(dt:Time,numDecimals:Int):Format[Time] = new Format[Time]{ val dt0 = dt.abs val (base,unit) = Time.formatLookup.find(dt0 >= _._1) getOrElse Time.formatLookup.last val fmt = s"%.${numDecimals}f${unit}" override def apply(t:Time):String = fmt.format(t/base) } val ms = Time(1.0/1000) val min = Time(60) val hr = Time(3600) val day = Time(3600*24) val yr = Time(sPerYr) val month = Time(sPerMo) } case class Time(s:Double) extends AnyVal with BaseValue[Time]{ def as(unit:String):Double = s/Time.parseToBaseValue(unit) def baseValue = s // def baseSymbol = "s" def apply(v:Double):Time = Time(v) //why is this needed ??? when it already lives in the BaseValue trait? def compare(that:Time):Int = this.baseValue compare that.baseValue //**convenience function for milliseconds*/ def ms = s*1E3 def us = s*1E6 def ns = s*1E9 def min = s/60 def minute = s/60 def hr = s/3600 def day = s/(3600*24) def month = s/(3600*24*365.242198781/12) def yr = s/(3600*24*365.242198781) //easy date construction from runtime date with rounding (like rails) def ago:Date = (Date.now - this)//TODO add rounding 2017-07-30("use `asScala` instead","dk") def toDuration:ScalaDuration = asScala import java.util.concurrent.TimeUnit.NANOSECONDS def asScala:ScalaDuration = new ScalaDuration(ns.toLong,NANOSECONDS) //use nanos for more precision //note: the cc.drx.ScheduledContext is used here instead the aliased java.util.concurrent.ScheduledThreadPoolExecutor to provide a more drx focused implicit not found message provided in the cc.drx package object import cc.drx.{ScheduledContext => SC} //using this alias for schedule executor provides better missing implicit warning messagse than the one for java def delay[A](f: => A)(implicit ex:SC):Future[A] = Repeater.delay(this, f) def repeat(f: => Unit)(implicit ex:SC) = Repeater.repeat(this, None, f) def repeat(timeout:Time)(f: => Unit)(implicit ex:SC) = Repeater.repeat(this, Some(timeout), f) def repeatSpaced(f: => Unit)(implicit ex:SC) = Repeater.repeatSpaced(this,None, f) override def toString = s"Time($format)" def inv:Frequency = Frequency(1.0/s) /** t*f = [s][hz] = [s][1/s] = [1] */ def *(freq:Frequency):Double = s*freq.hz //TODO maybe deprecate with round(dt) and format(dt) 2017-07-30("use format instead","v0.2.15") def nice = format //use the current time as a scale for the time rounding like nice but as time // TODO remove def round:Time = round(Tick.TickTime.delta(this)) def round(dt:Time):Time = dt.abs*(this/dt.abs).round //it is important to make sure signs are conserved correctly def format:String = format(this,1) def format(numDecimals:Int):String = format(this,numDecimals) def format(dt:Time,numDecimals:Int):String = Time.format(dt,numDecimals)(this) /**triangular wave from 0 to 1*/ def ramp:Double = (Time.initMs % ms)/ms /**sinusoidal wave from 0 to 1*/ def cycle:Double = ((ramp*tau).sin+1)/2 /**An easy to use sleep function directly on the time object but always prints a warning suggesting not to use this method*/ def sleep:Unit = sleepWithOptionalWarning(true) /**A sleep function directly on the time that looks ugly in code but doesn't look ugly at run time (assuming you meant this)*/ def sleepWithoutWarning():Unit = sleepWithOptionalWarning(false) private def sleepWithOptionalWarning(showWarning:Boolean):Unit = if(s <= 0d) () else { //skip any thread generation if sleep time is zero val thread = Thread.currentThread val group = thread.getThreadGroup if(showWarning) Console.err.println(s"Warning: $format.sleep is blocking thread:${thread.getName} in group:${group.getName}") Thread.sleep(ms.round.toLong) } //TODO replace the following sleep based loops with this schedule based delays from the Repeater //experimental features, may offer better return options than Unit, but safely wrap some while loops for world time def loop(f: => Unit):Unit = { val ns0 = System.nanoTime val nsEnd = ns.toLong while(System.nanoTime - ns0 < nsEnd){ f } } def loopWithIndex(f: Long => Unit):Unit = { val ns0 = System.nanoTime val nsEnd = ns.toLong var i = 0L while(System.nanoTime - ns0 < nsEnd){ f(i) i += 1 } } def iterate[A](init:A)(f:A => A):A = { val ns0 = System.nanoTime val nsEnd = ns.toLong var acc = init while(System.nanoTime - ns0 < nsEnd){ acc = f(acc) } acc } /**bandwidth frequency, based on the first order filter based rise time-constant, * the bandwith of a time-constant rise time is roughly: * ω = 2πf = 4/tr * tr = 4/ω = 4/(2πf) * f = 4/(2π*tr) */ def bandwidth:Frequency = Frequency(4d / (2*pi*s) ) } object Angle extends Units{ val units:Map[String,Double] = Map( //"r" -> 1, "rad" -> 1, "radian" -> 1, "π" -> math.Pi, "Pi" -> math.Pi, "τ" -> 2*math.Pi, "tau" -> 2*math.Pi, //"d" -> math.Pi/180, "deg" -> math.Pi/180, "°" -> math.Pi/180, //"m" -> math.Pi/180/60, "min" -> math.Pi/180/60, "'" -> math.Pi/180/60, "s" -> math.Pi/180/3600, "sec" -> math.Pi/180/3600, "\"" -> math.Pi/180/3600 ) def apply(str:String):Angle = Angle(parseToBaseValue(str)) def apply(value:Double,unit:String):Angle = Angle(value*parseToBaseValue(unit)) def apply(deg:Double,min:Double,sec:Double):Angle = { val sgn = if(deg != 0) deg.sgn else if(min != 0) min.sgn else sec.sgn Angle(sgn*(deg.abs + min.abs/60.0 + sec.abs/3600.0)*math.Pi/180.0) } lazy val full = Angle(2*math.Pi) lazy val half = Angle(math.Pi) lazy val quarter = Angle(math.Pi/2) //def apply(rad:Double):Angle = new Angle(rad) //math.atan2(math.sin(rad), math.cos(rad)) //def apply(rad:Double):Angle = new Angle(math.cos(rad), math.sin(rad)) //math.atan2(math.sin(rad), math.cos(rad)) //def apply(x:Double,y:Double):Angle = new Angle(x,y) //def apply(v:Vec):Angle = new Angle(v.x,v.y) implicit object BoundableAngle extends BoundableBaseValue[Angle]{ /* TODO remove the following if they are not needed (in the past Arc used these special cases for a test override def norm(b:Bound[Angle]):Bound[Angle] = { val m = b.min.norm val M = b.max.norm if(m > M) Bound(m-full,M) else Bound(m,M) } override def containsNorm(b:Bound[Angle],x:Angle):Boolean = { val xnorm = x.norm (b.norm.min < xnorm && xnorm < b.norm.max) || {val xadj = xnorm-full; (b.norm.min < xadj && xadj < b.norm.max)} } */ } implicit object ParsableAngle extends Parsable[Angle]{ def apply(v:String):Angle = Angle(v) } } case class Angle(rad:Double) extends AnyVal with BaseValue[Angle]{ def as(unit:String):Double = rad/Angle.parseToBaseValue(unit) def deg = rad*rad2deg 2017-07-30("use norm instead","0.2.5") def pos = if(rad < 0) Angle(rad + tau) else this def norm = {val r = rad % tau; if(r < 0) Angle(r + tau) else Angle(r)} def baseValue = rad // def baseSymbol = "rad" //why is this needed ??? when it already lives in the BaseValue trait? def compare(that:Angle):Int = this.baseValue compare that.baseValue def apply(v:Double) = Angle(v) override def toString = s"Angle($nice)" def degMinSec:(Int,Int,Double) = { val dd = deg.abs val d = dd.toInt val t1 = (dd - d)*60 val m = t1.toInt val s = (t1 - m) * 60 (deg.sgn.toInt*d, m, s) } def degMinSecString:String = { val (d,m,s) = degMinSec s"$d°$m\'${s.round.toInt}" + '"' } def delta(that:Angle):Angle = Vec.angle(this).rz(-that).heading def rzDelta(that:Angle):Angle = { val r = this-that if (r < Angle(0)) (that-this-Angle.full).abs else r } //def ciѕ:Vec = Vec(cos,sin) //cis and unit should be equvilante //def unit:Vec = Vec.angle(rad) def cos:Double = math.cos(rad) def sin:Double = math.sin(rad) def tan:Double = math.tan(rad) def sin2 = sin.sq//{val t = math.sin(rad); t*t} def cos2 = cos.sq//{val t = math.cos(rad); t*t} def tan2 = tan.sq//{val t = math.tan(rad); t*t} def nice = s"${deg}deg" } object Frequency extends Units{ val units:Map[String,Double] = Map( "Hz" -> 1, "hz" -> 1, "rpm" -> 60, //rev per minute "dpm" -> 360*60 //deg per minute ) def apply(str:String):Frequency = Frequency(parseToBaseValue(str)) def apply(value:Double,unit:String):Frequency = Frequency(value*parseToBaseValue(unit)) implicit object BoundableFrequency extends BoundableBaseValue[Frequency] implicit object ParsableFrequency extends Parsable[Frequency]{ def apply(v:String):Frequency = Frequency(v) } } case class Frequency(hz:Double) extends AnyVal with BaseValue[Frequency]{ def as(unit:String):Double = hz/Frequency.parseToBaseValue(unit) def baseValue = hz // def baseSymbol = "Hz" //why is this needed ??? when it already lives in the BaseValue trait? def compare(that:Frequency):Int = this.baseValue compare that.baseValue def apply(v:Double) = Frequency(v) override def toString = s"Frequency($nice)" def inv:Time = Time(1.0/hz) /** f*t = [hz][s] = [1/s][s] = [1] */ def *(time:Time):Double = hz*time.s /**angular frequency omega = ω = 2πf */ def omega = 2*pi*hz def nice = s"${hz}Hz" } object Length extends Units{ val units:Map[String,Double] = Map( "m" -> 1, "meter" -> 1, "nmi" -> 1852, "nauticalmile" -> 1852, "mile" -> 1609.344, "yd" -> 0.9144, "yard" -> 0.9144, "ft" -> 0.3048, "feet" -> 0.3048, "foot" -> 0.3048, "in" -> 0.0254, "inch" -> 0.0254 ) def apply(str:String):Length = Length(parseToBaseValue(str)) def apply(value:Double,unit:String):Length = Length(value*parseToBaseValue(unit)) implicit object BoundableLength extends BoundableBaseValue[Length] implicit object ParsableLength extends Parsable[Length]{def apply(v:String):Length = Length(v) } } case class Length(m:Double) extends AnyVal with BaseValue[Length]{ def as(unit:String):Double = m/Length.parseToBaseValue(unit) def baseValue = m // def baseSymbol = "m" //why is this needed ??? when it already lives in the BaseValue trait? def compare(that:Length):Int = this.baseValue compare that.baseValue def apply(v:Double) = Length(v) override def toString = s"Length($nice)" def /(t:Time):Velocity = Velocity(m/t.s) def /(v:Velocity)(implicit d1: DummyImplicit, d2: DummyImplicit):Time = Time(m/v.base) //DummyImplicit trick to get around similar method signatures for Time&Velocity value types def ft = m/0.3048 def nice = s"${m}m" def north = Ned(this, 0.m, 0.m) def east = Ned(0.m, this, 0.m) def down = Ned(0.m, 0.m, this) } object Mass extends Units{ val units:Map[String,Double] = Map( "kg" -> 1, "g" -> 1E-3, "gram" -> 1E-3, "pound" -> 0.45359237, "lb" -> 0.45359237, "slug" -> 0.06852178, //14.59390 kg "ounce" -> 0.0283495, "oz" -> 0.0283495, "tonne" -> 0.0001, //metric ton "ton" -> 0.0011023 // us-ton is 2000lb //short ton //FYI lots of confusion for these terms of ton ) def apply(str:String):Mass = Mass(parseToBaseValue(str)) def apply(value:Double,unit:String):Mass = Mass(value*parseToBaseValue(unit)) implicit object BoundableMass extends BoundableBaseValue[Mass] implicit object ParsableMass extends Parsable[Mass]{def apply(v:String):Mass = Mass(v)} } case class Mass(kg:Double) extends AnyVal with BaseValue[Mass]{ def as(unit:String):Double = kg/Mass.parseToBaseValue(unit) def baseValue = kg // def baseSymbol = "kg" //why is this needed ??? when it already lives in the BaseValue trait? def compare(that:Mass):Int = this.baseValue compare that.baseValue def apply(v:Double) = Mass(v) override def toString = s"Mass($nice)" def nice = s"${kg}kg" } object Velocity extends Units{ val units:Map[String,Double] = Map( "m/s" -> 1, "knot" -> 0.514444, "kt" -> 0.514444, "mph" -> 0.44704, "fps" -> 0.3048, "ft/s" -> 0.3048, "fpm" -> 0.00508, "kph" -> 0.277778 ) def apply(str:String):Velocity = Velocity(parseToBaseValue(str)) def apply(value:Double,unit:String):Velocity = Velocity(value*parseToBaseValue(unit)) implicit object BoundableVelocity extends BoundableBaseValue[Velocity] implicit object ParsableVelocity extends Parsable[Velocity]{ def apply(v:String):Velocity = Velocity(v) } } case class Velocity(base:Double) extends AnyVal with BaseValue[Velocity]{ def as(unit:String):Double = base/Velocity.parseToBaseValue(unit) def baseValue = base // def baseSymbol = "m/s" //why is this needed ??? when it already lives in the BaseValue trait? def compare(that:Velocity):Int = this.baseValue compare that.baseValue def apply(v:Double) = Velocity(v) override def toString = s"Velocity($nice)" def *(t:Time):Length = Length(base*t.s) def /(t:Time):Acceleration = Acceleration(base/t.s) def nice = s"${base}m/s" } object Acceleration extends Units{ val gravity = Acceleration(0.10197162129) val units:Map[String,Double] = Map( "m/s^2" -> 1.0, "m/s/2" -> 1.0, "ft/s^2" -> 0.3048, "ft/s/s" -> 0.3048, "g0" -> 0.10197162129, "g's" -> 0.10197162129, "gravity" -> 0.10197162129 ) def apply(str:String):Acceleration = Acceleration(parseToBaseValue(str)) def apply(value:Double,unit:String):Acceleration = Acceleration(value*parseToBaseValue(unit)) implicit object BoundableAcceleration extends BoundableBaseValue[Acceleration] implicit object ParsableAcceleration extends Parsable[Acceleration]{ def apply(v:String):Acceleration = Acceleration(v) } } case class Acceleration(base:Double) extends AnyVal with BaseValue[Acceleration]{ def as(unit:String):Double = base/Acceleration.parseToBaseValue(unit) def baseValue = base // def baseSymbol = "m/s^2" //why is this needed ??? when it already lives in the BaseValue trait? def compare(that:Acceleration):Int = this.baseValue compare that.baseValue def apply(v:Double) = Acceleration(v) override def toString = s"Acceleration($nice)" def *(t:Time):Velocity = Velocity(base*t.s) def *(m:Mass)(implicit d1: DummyImplicit, d2: DummyImplicit):Force = Force(base*m.kg) //DummyImplicit trick to get around similar method signatures for Time&Velocity value types def nice = s"${base}m/s" } object Force extends Units{ val units:Map[String,Double] = Map( "N" -> 1.0, "kg*m/s^2" -> 1.0, "lbf" -> 0.224809 ) def apply(str:String):Force = Force(parseToBaseValue(str)) def apply(value:Double,unit:String):Force = Force(value*parseToBaseValue(unit)) implicit object BoundableForce extends BoundableBaseValue[Force] implicit object ParsableForce extends Parsable[Force]{ def apply(v:String):Force = Force(v) } } case class Force(base:Double) extends AnyVal with BaseValue[Force]{ def as(unit:String):Double = base/Force.parseToBaseValue(unit) def baseValue = base // def baseSymbol = "N" //"kg*m/s^2" //why is this needed ??? when it already lives in the BaseValue trait? def compare(that:Force):Int = this.baseValue compare that.baseValue def apply(v:Double) = Force(v) override def toString = s"Force($nice)" def /(m:Mass):Acceleration = Acceleration(base/m.kg) def nice = s"${base}m/s" } object Energy extends Units{ val units:Map[String,Double] = Map( "J" -> 1.0, "kg*m^2/s^2" -> 1.0, "kg*m*m/s/s" -> 1.0, "kWh" -> 2.77778e-7, //kWh = 3.6E6J "hph" -> 3.72508847e-7, //horsepower hour = 2.6845 MJ "eV" -> 6.2415095e+18 //ev = 1.60217653E-19J ) def apply(str:String):Energy = Energy(parseToBaseValue(str)) def apply(value:Double,unit:String):Energy = Energy(value*parseToBaseValue(unit)) implicit object BoundableEnergy extends BoundableBaseValue[Energy] implicit object ParsableEnergy extends Parsable[Energy]{ def apply(v:String):Energy = Energy(v) } } case class Energy(base:Double) extends AnyVal with BaseValue[Energy]{ def as(unit:String):Double = base/Energy.parseToBaseValue(unit) def baseValue = base // def baseSymbol = "N" //"kg*m/s^2" //why is this needed ??? when it already lives in the BaseValue trait? def compare(that:Energy):Int = this.baseValue compare that.baseValue def apply(v:Double) = Energy(v) override def toString = s"Energy($nice)" def nice = s"${base}m/s" } //TODO add,,, this shows a better mechanism is required... //Area //Volume //Density object Bytes{ def bits(bitCount:Long) = Bytes(bitCount divCeil 8) } case class Bytes(byteCount:Long) extends AnyVal with Ordered[Bytes]{ // def B = size 2017-07-30("use `byteCount` instead","v0.2.15") def size = byteCount def bits = byteCount*8 override def toString = if(byteCount < 1024) s"$byteCount B" else { val exp = (byteCount.log/1024.log).toInt //exp index trick hint from aioobe at stackoverflow f"${byteCount/(1024d**exp)}%.1f ${"kMGTPE"(exp-1)}B" } // import scala.math.Ordered.orderingToOrdered def compare(that: Bytes):Int = this.byteCount compare that.byteCount def +(that:Bytes):Bytes = Bytes(this.byteCount + that.byteCount) def -(that:Bytes):Bytes = Bytes(this.byteCount - that.byteCount) def *(x:Int):Bytes = Bytes(this.byteCount*x) def /(x:Int):Bytes = Bytes(this.byteCount/x) }