### Documentation

- API documentation for
`coulomb`

`coulomb`

tutorial- Your Data Type is a Unit
- Why Your Data Schema Should Include Units
- Preventing Configuration Errors With Unit Types
- Unit Types for Avro Schema: Integrating Avro with Coulomb
- Algorithmic Unit Analysis

### Scala Requirements

The `coulomb`

libraries currently require scala 2.13.0, or higher.

`coulomb`

in your project

How to include The core `coulomb`

package can be included by adding the dependencies shown below. Note that its two 3rd-party dependencies -- `spire`

and `singleton-ops`

-- are `%Provided`

, and so you must also include them, if your project does not already do so. The `shapeless`

package is also a dependency, but is included transitively via `spire`

.

```
libraryDependencies ++= Seq(
"com.manyangled" %% "coulomb" % "0.5.0",
"org.typelevel" %% "spire" % "0.17.0-RC1",
"eu.timepit" %% "singleton-ops" % "0.5.0"
)
```

The `coulomb`

project also provides a selection of predefined units, which are available as separate sub-packages.

```
libraryDependencies ++= Seq(
"com.manyangled" %% "coulomb-si-units" % "0.5.0", // The seven SI units: meter, second, kilogram, etc
"com.manyangled" %% "coulomb-accepted-units" % "0.5.0", // Common non-SI metric: liter, centimeter, gram, etc
"com.manyangled" %% "coulomb-time-units" % "0.5.0", // minute, hour, day, week
"com.manyangled" %% "coulomb-info-units" % "0.5.0", // bit, byte, nat
"com.manyangled" %% "coulomb-mks-units" % "0.5.0", // MKS units: Joule, Newton, Watt, Volt, etc
"com.manyangled" %% "coulomb-customary-units" % "0.5.0", // non-metric units: foot, mile, pound, gallon, pint, etc
"com.manyangled" %% "coulomb-temp-units" % "0.5.0" // Celsius and Fahrenheit temperature scales
)
```

#### Other coulomb packages

In addition to core functionality and fundamental units, coulomb provides the following packages.

- coulomb-cats - define some cats typeclass integrations for Quantity
- coulomb-refined - integrates coulomb Quantity with Refined values
- coulomb-scalacheck - scalacheck Arbitrary and Cogen for Quantity
- coulomb-parser - parsing a DSL for unit expressions into typed unit Quantity
- coulomb-avro - an integration package with Apache Avro schema and i/o
- coulomb-pureconfig - extends the pureconfig with awareness of unit Quantity
- coulomb-pureconfig-refined - integrate coulomb + pureconfig + refined
- coulomb-typesafe-config - unit awareness for the typesafe config library

#### scala.js support

The core coulomb package and several other sub-packages are cross-published to scala.js

- coulomb
- coulomb-cats
- coulomb-scalacheck
- coulomb-refined
- coulomb-si-units
- coulomb-mks-units
- coulomb-accepted-units
- coulomb-time-units
- coulomb-temp-units
- coulomb-info-units
- coulomb-customary-units

### Code of Conduct

The `coulomb`

project supports the Scala Code of Conduct; all contributors are expected to respect this code. Any violations of this code of conduct should be reported to the author.

### Tutorial

#### Table of Contents

- Running Tutorial Examples
- Features
`Quantity`

and Unit Expressions- Quantity Values
- String Representations
- Predefined Units
- Unit Types and Convertability
- Unit Conversions
- Unit Operations
- Declaring New Units
- Unitless Quantities
- Unit Prefixes
- Using
`WithUnit`

- Type Safe Configurations
- Absolute Temperature and Time Values
- Working with Type Parameters and Type-Classes
- Compute Model for Quantity Operations
- Unit Conversions for Custom Value Types

#### Running Tutorial Examples

Except where otherwise noted, the following tutorial examples can be run in a scala REPL as follows:

```
% cd /path/to/scala
% sbt coulomb_tests/console
```

`scala> import shapeless._, coulomb._, coulomb.si._, coulomb.siprefix._, coulomb.mks._, coulomb.time._, coulomb.info._, coulomb.binprefix._, coulomb.accepted._, coulomb.us._, coulomb.temp._, coulomb.define._, coulomb.parser._`

Examples making use of numeric quantity operations depend on corresponding typeclasses for numeric algebras. Alebras for the common scala and spire numeric types can be obtained this way:

```
scala> import spire.std.any._ // import algebras for common numeric types
scala> import spire.std.double._ // import algebras for Double
```

#### Features

The `coulomb`

libraries provide the following features:

Allow a programmer to associate unit analysis with values, in the form of static types

```
val length = 10.withUnit[Meter]
val duration = (30.0).withUnit[Second]
val mass = Quantity[Float, Kilogram](100)
```

Express those types with arbitrary and natural static type expressions

```
val speed = (100.0).withUnit[(Kilo %* Meter) %/ Hour]
val acceleration = (9.8).withUnit[Meter %/ (Second %^ 2)]
```

Let the compiler determine which unit expressions are equivalent (aka *convertable*) and transparently convert between them

`val mps: Quantity[Double, Meter %/ Second] = (60.0).withUnit[Mile %/ Hour]`

Cause a compile-time error when operations are attempted with *non-convertable* unit types

`val mps: Quantity[Double, Meter %/ Second] = (60.0).withUnit[Mile] // compile-time type error!`

Automatically determine correct output unit types for operations on unit quantities

`val mps: Quantity[Double, Meter %/ Second] = 60D.withUnit[Mile] / 1D.withUnit[Hour]`

Allow a programmer to easily declare new units that will work seamlessly with existing units

```
// a new unit of length:
trait Smoot
implicit val defineUnitSmoot = DerivedUnit[Smoot, Inch](67, name = "Smoot", abbv = "Smt")
// a unit of acceleration:
trait EarthGravity
implicit val defineUnitEG = DerivedUnit[EarthGravity, Meter %/ (Second %^ 2)](9.8, abbv = "g")
```

`Quantity`

and Unit Expressions

`coulomb`

defines the class `Quantity`

for representing values with associated units. Quantities are represented by their two type parameters: A value type `N`

(typically a numeric type such as Int or Double) and a unit type `U`

which represents the unit associated with the value. Here are some simple declarations of `Quantity`

objects:

```
import coulomb._
import coulomb.si._
val length = 10.withUnit[Meter] // An Int value of meters
val duration = (30.0).withUnit[Second] // a Double value in seconds
val mass = Quantity[Float, Kilogram](100) // a Float value in kg
```

Three operator types can be used for building more complex unit types: `%*`

, `%/`

, and `%^`

.

```
import coulomb._
import coulomb.si._
val area = 100.withUnit[Meter %* Meter] // unit product
val speed = 10.withUnit[Meter %/ Second] // unit ratio
val volume = 50.withUnit[Meter %^ 3] // unit power
```

Using these operators, units can be composed into unit type expressions of arbitrary complexity.

```
val acceleration = (9.8).withUnit[Meter %/ (Second %^ 2)]
val ohms = (0.01).withUnit[(Kilogram %* (Meter %^ 2)) %/ ((Second %^ 3) %* (Ampere %^ 2))]
```

#### Quantity Values

The internal representation type of a `Quantity`

is given by its first type parameter. Each quantity's value is accessible via the `value`

field

```
import coulomb._, coulomb.info._, coulomb.siprefix._
val memory = 100.withUnit[Giga %* Byte] // type is: Quantity[Int, Giga %* Byte]
val raw: Int = memory.value // memory's raw integer value
```

Standard Scala types `Float`

, `Double`

, `Int`

and `Long`

are supported, as well as any other numeric type `N`

for which `spire`

algebra typeclasses are defined, for example `BigDecimal`

or `spire`

`Rational`

. Algebra typeclasses for standard value types may be imported via `import spire.std.any._`

Operations on coulomb Quantity objects require only the typeclasses they need to operate. If you wish to work with operations that do not require algebras, then these typeclasses do not need to exist:

```
scala> import coulomb._, coulomb.si._
scala> case class Foo(foo: String) // no algebras are defined for this type
defined class Foo
scala> Foo("goo").withUnit[Meter].show // 'show' requires no algebra typeclass
res0: String = Foo(goo) m
```

#### String representations

The `show`

method can be used to obtain a human-readable string that represents a quantity's type and value using standard unit abbreviations. The `showFull`

method uses full unit names. The methods `showUnit`

and `showUnitFull`

output only the unit without the value.

```
scala> val bandwidth = 10.withUnit[(Giga %* Bit) %/ Second]
bandwidth: coulomb.Quantity[...] = coulomb.Quantity@40240000
scala> bandwidth.show
res1: String = 10 Gb/s
scala> bandwidth.showFull
res2: String = 10 gigabit/second
scala> bandwidth.showUnit
res3: String = Gb/s
scala> bandwidth.showUnitFull
res4: String = gigabit/second
```

#### Predefined Units

A variety of units and prefixes are predefined by several `coulomb`

sub-packages, which are summarized here. The relation between the packages below and maven packages is at the top of this page.

- coulomb.si: The Standard International base units Meter, Kilogram, Second, Ampere, Kelvin, Mole and Candela.
- coulomb.siprefix: Standard International prefixes Kilo, Mega, Milli, Micro, etc.
- coulomb.mks: The common "Meter-Kilogram-Second" derived units.
- coulomb.accepted: A selection of units accepted by the Standard International system.
- coulomb.time: time units minute, hour, day, week
- coulomb.info: Units of information: Byte, Bit and Nat.
- coulomb.us: Some customary non-SI units commonly used in the United States.
- coulomb.binprefix: The binary prefixes Kibi, Mebi, Gibi, etc.
- coulomb.temp: Temperature units and scales for Fahrenheit and Celsius

#### Unit Types and Convertability

The concept of unit *convertability* is fundamental to the `coulomb`

library and its implementation of unit analysis. Two unit type expressions are *convertable* if they encode an equivalent "abstract quantity." For example, `Meter`

and `Mile`

are convertable because they both encode the abstract quantity of `length`

. `Foot %^ 3`

and `Liter`

are convertable because they both encode a volume, or `length^3`

. `Kilo %* Meter %/ Hour`

and `Foot %* (Second %^ -1)`

are convertable because they encode a velocity, or `length / time`

.

In `coulomb`

, abstract quantities like `length`

are represented by a unique `BaseUnit`

. For example the base unit for length is the type `coulomb.si.Meter`

. Compound abstract quantities such as `length / time`

or `length ^ 3`

are internally represented by pairs of base units with exponents:

`velocity`

<=>`length / time`

<=>`(Meter ^ 1)(Second ^ -1)`

`volume`

<=>`length ^ 3`

<=>`(Meter ^ 3)`

`acceleration`

<=>`length / time^2`

<=>`(Meter ^ 1)(Second ^ -2)`

`bandwidth`

<=>`information / time`

<=>`(Byte ^ 1)(Second ^ -1)`

In `coulomb`

, a unit quantity will be implicitly converted into a quantity of a different unit type whenever those types are convertable. Any attempt to convert between *non-convertable* unit types results in a compile-time type error.

```
scala> def foo(q: Quantity[Double, Meter %/ Second]) = q.showFull
scala> foo(60f.withUnit[Mile %/ Hour])
res5: String = 26.8224 meter/second
scala> foo(1f.withUnit[Mile %/ Minute])
res6: String = 26.8224 meter/second
scala> foo(1f.withUnit[Foot %/ Day])
res7: String = 3.5277778E-6 meter/second
scala> foo(1f.withUnit[Foot %* Day])
error: type mismatch;
```

#### Unit Conversions

As described in the previous section, unit quantities can be converted from one unit type to another when the two types are convertable. Unit conversions come in a couple different forms:

```
// Implicit conversion
scala> val vol: Quantity[Double, Meter %^ 3] = 4000D.withUnit[Liter]
vol: coulomb.Quantity[Double,coulomb.si.Meter %^ 3] = Quantity(4.0)
scala> vol.showFull
res2: String = 4.0 meter^3
// Explicit conversion using the `toUnit` method
scala> 4000D.withUnit[Liter].toUnit[Meter %^ 3].showFull
res3: String = 4.0 meter^3
```

#### Unit Operations

Unit quantities support math operations `+`

, `-`

, `*`

, `/`

, and `pow`

. Quantities must be of convertable unit types to be added or subtracted. The type of the left-hand argument is taken as the type of the output:

```
scala> (1.withUnit[Foot] + 1.withUnit[Yard]).show
res4: String = 4 ft
scala> (4.withUnit[Foot] - 1.withUnit[Yard]).show
res5: String = 1 ft
```

Quantities of any unit types may be multiplied or divided. Result types are different than either argument:

```
scala> (60.withUnit[Mile] / 1.withUnit[Hour]).show
res6: String = 60 mi/h
scala> (1.withUnit[Yard] * 1.withUnit[Yard]).show
res7: String = 1 yd^2
scala> (1.withUnit[Yard] / 1.withUnit[Inch]).toUnit[Percent].show
res8: String = 3600 %
```

When raising a unit to a power, the exponent is given as a literal type:

```
scala> 3D.withUnit[Meter].pow[2].show
res13: String = 9.0 m^2
scala> Rational(3).withUnit[Meter].pow[-1].show
res14: String = 1/3 m^(-1)
scala> 3.withUnit[Meter].pow[0].show
res15: String = 1 unitless
```

#### Declaring New Units

The `coulomb`

library strives to make it easy to add new units which work seamlessly with the unit analysis type system. There are two varieties of unit declaration: *base units* and *derived units*.

A base unit, as its name suggests, is not defined in terms of any other unit; it is axiomatic. The Standard International Base Units are all declared as base units in the `coulomb.si`

subpackage. In the `coulomb.info`

sub-package, `Byte`

is declared as the base unit of information.

Declaring a base unit is special in the sense that it also defines a new kind of fundamental *abstract quantity*. For example, by declaring `coulomb.si.Meter`

as a base unit, `coulomb`

establishes `Meter`

as the canonical representation of the abstract quantity of Length. Any other unit of length must be declared as a *derived unit* of `Meter`

, or it would be considered *non-convertable* with other lengths.

Here is an example of defining a new base unit `Scoville`

, representing an abstract quantity of Spicy Heat. The `BaseUnit`

value must be defined as an implicit value:

```
import coulomb._
import coulomb.define._ // BaseUnit and DerivedUnit
object SpiceUnits {
trait Scoville
implicit val defineUnitScoville = BaseUnit[Scoville](name = "scoville", abbv = "sco")
}
```

The second variety of unit declarations is the *derived* unit, which is defined in terms of some unit expression involving previously-defined units. Derived units do *not* define new kinds of abstract quantity, and are generally more common than base units:

```
object NewUnits {
import coulomb._, coulomb.define._, coulomb.si._, coulomb.us._
// a furlong is 660 feet
trait Furlong
implicit val defineUnitFurlong = DerivedUnit[Furlong, Foot](coef = 660, abbv = "flg")
// speed of sound is 1130 feet/second (at sea level, 20C)
trait Mach
implicit val defineUnitMach = DerivedUnit[Mach, Foot %/ Second](coef = 1130, abbv = "mach")
// a standard earth gravity is 9.807 meters per second-squared
// Define an abbreviation "g"
trait EarthGravity
implicit val defineUnitEG = DerivedUnit[EarthGravity, Meter %/ (Second %^ 2)](coef = 9.807, abbv = "g")
// The maximum ping time to the moon
// https://twitter.com/cmuratori/status/1219847348433481729
trait MoonUnit
implicit val defineUnitMoonUnit = DerivedUnit[MoonUnit, Second](coef = 2.71321035034, abbv = "moo")
}
```

Notice that there are no constraints or requirements associated with the unit types `Scoville`

, `Furlong`

, etc. These may simply be declared, as shown above, however they *may also be pre-existing types*. In other words, you may define any type, pre-existing or otherwise, to be a `coulomb`

unit by declaring the appropriate implicit value.

Newer versions of coulomb allow Base Units to be implicitly inferred for any type, even if no BaseUnit object has been specifically declared, by importing the `undeclaredBaseUnits`

policy:

```
scala> import coulomb.policy.undeclaredBaseUnits._
import coulomb.policy.undeclaredBaseUnits._
scala> case class Foo(goo: String)
defined class Foo
scala> (1.withUnit[Foo] + 1.withUnit[Kilo %* Foo]).show
res1: String = 1001 Foo
scala> (10.withUnit[Seq[Int]] / 5.withUnit[Second]).show
res2: String = 2 Seq[Int]/s
```

#### Unitless Quantities

When units in an expression all cancel out -- for example, a ratio of quantities with convertable units -- the value is said to be "unitless". In `coulomb`

the unit expression type `Unitless`

represents this particular state. Here are a few examples of situations when `Unitless`

values arise:

```
// ratios of convertable unit types are always unitless
scala> (1.withUnit[Yard] / 1.withUnit[Foot]).toUnit[Unitless].show
res1: String = 3 unitless
// raising to the zeroth power
scala> 100.withUnit[Second].pow[0].show
res2: String = 1 unitless
// Radians and other angular units are derived from Unitless
scala> math.Pi.withUnit[Radian].toUnit[Unitless].show
res3: String = 3.141592653589793 unitless
// Percentages
scala> 90D.withUnit[Percent].toUnit[Unitless].show
res4: String = 0.9 unitless
```

#### Unit Prefixes

Unit prefixes are a first-class concept in `coulomb`

. In fact, prefixes are derived units of `Unitless`

:

```
scala> 1.withUnit[Kilo].toUnit[Unitless].show
res1: String = 1000 unitless
scala> 1.withUnit[Kibi].toUnit[Unitless].show
res2: String = 1024 unitless
```

Because they are just another kind of unit, prefixes work seamlessly with all other units.

```
scala> 3.withUnit[Meter %^ 3].toUnit[Kilo %* Liter].showFull
res1: String = 3 kiloliter
scala> 3D.withUnit[Meter %^ 3].toUnit[Mega %* Liter].showFull
res2: String = 0.003 megaliter
scala> (1.withUnit[Kilo] * 1.withUnit[Meter]).toUnit[Meter].showFull
res3: String = 1000 meter
scala> (1D.withUnit[Meter] / 1D.withUnit[Mega]).toUnit[Meter].showFull
res4: String = 1.0E-6 meter
```

The `coulomb`

library comes with definitions for the standard SI prefixes, and also standard binary prefixes.

It is also easy to declare new prefix units using `coulomb.define.PrefixUnit`

```
scala> trait Dozen
defined trait Dozen
scala> implicit val defineUnitDozen = PrefixUnit[Dozen](coef = 12, abbv = "doz")
defineUnitDozen: coulomb.define.DerivedUnit[Dozen,coulomb.Unitless] = DerivedUnit(12, dozen, doz)
scala> 1D.withUnit[Dozen %* Inch].toUnit[Foot].show
res1: String = 1.0 ft
```

`WithUnit`

Using The `WithUnit`

type alias can be used to make unit definitions more readable. The following two function definitions are equivalent:

```
def f1(duration: Quantity[Float, Second]) = duration + 1f.withUnit[Minute]
def f2(duration: Float WithUnit Second) = duration + 1f.withUnit[Minute]
```

There is a similar `WithTemperature`

alias for working with `Temperature`

values.

#### Type Safe Configurations

One of the significant use cases for coulomb is adding unit type awareness to software configurations "at the edge." The coulomb libraries include some integrations with popular configuration libraries:

- coulomb-avro - an integration package with Apache Avro schema and i/o
- coulomb-pureconfig - extends the pureconfig with awareness of unit Quantity
- coulomb-typesafe-config - unit awareness for the typesafe config library

#### Absolute Temperature and Time Values

In coulomb, both time and temperature units can serve as units in Quantity values, but they can also serve as measures against an absolute offset.

In the case of temperature units, the `Temperature`

type represents absolute temperature values, with respect to absolute zero. The type `EpochTime`

represents absolute date/time moments, based on the unix epoch: midnight of Jan 1, 1970. EpochTime is similar to `java.time.Instant`

, and can interoperate with it.

Temperature and EpochTime are both specializations of `OffsetQuantity[N, U]`

. These obey somewhat different laws than Quantity:

```
OffsetQuantity - OffsetQuantity => Quantity // e.g. EpochTime - EpochTime => Quantity
OffsetQuantity + Quantity => OffsetQuantity // e.g. Temperature + Quantity => Temperature
OffsetQuantity - Quantity => OffsetQuantity
```

- Temperature units are documented with examples at coulomb-temp-units
- Time unit examples are documented under coulomb-time-units

#### Working with Type Parameters and Type-Classes

Previous topics have focused on how to work with specific `Quantity`

and unit expressions. However, suppose you wish to write your own "generic" functions or classes, where `Quantity`

values have parameterized types? For these situations, `coulomb`

provides a set of implicit type-classes that allow `Quantity`

operations to be supported with type parameters. These typeclasses can be accessed via `import coulomb.unitops._`

The `UnitString`

type-class supports unit names and abbreviations:

```
scala> import coulomb.unitops._
scala> def uname[N, U](q: Quantity[N, U])(implicit us: UnitString[U]): String = us.full
uname: [N, U](q: coulomb.Quantity[N,U])(implicit us: coulomb.unitops.UnitString[U])String
scala> uname(3.withUnit[Meter %/ Second])
res0: String = meter/second
```

The various numeric operations are supported by a set of typeclasses, summarized in the following table.

operation | implicit class | algebra |
---|---|---|

< <= > >= === =:= | UnitOrd[N1,U1,N2,U2] | Order[N1] |

+ | UnitAdd[N1,U1,N2,U2] | AdditiveSemigroup[N1] |

- | UnitSub[N1,U1,N2,U2] | AdditiveGroup[N1] |

* | UnitMul[N1,U1,N2,U2] | MultiplicativeSemigroup[N1] |

/ | UnitDiv[N1,U1,N2,U2] | MultiplicativeGroup[N1] |

pow | UnitPow[N, U, P] | MultiplicativeSemigroup[N] |

unary - | UnitNeg[N] | AdditiveGroup[N] |

For common numeric types, the various algebras in the table above can be obtained via `import spire.std.any._`

, or individually as in `import spire.std.double._`

. The following code block shows an example of using typeclasses to support some numeric Quantity operations:

```
scala> def operate[N1, U1, N2, U2](q1: Quantity[N1, U1], q2: Quantity[N2, U2])(implicit
add: UnitAdd[N1, U1, N2, U2],
mul: UnitMul[N1, U1, N2, U2],
pow: UnitPow[N1, U1, 3],
ord: UnitOrd[N1, U1, N2, U2]) = {
val r1 = q1 + q2
val r2 = q1 * q2
val r3 = q1.pow[3]
val r4 = q1 < q2
(r1, r2, r3, r4)
}
scala> val (r1, r2, r3, r4) = operate(2f.withUnit[Meter], 3f.withUnit[Meter])
r1: coulomb.Quantity[Float,coulomb.si.Meter] = Quantity(5.0)
r2: coulomb.Quantity[Float,coulomb.si.Meter %^ Int(2)] = Quantity(6.0)
r3: coulomb.Quantity[Float,coulomb.si.Meter %^ Int(3)] = Quantity(8.0)
r4: Boolean = true
scala> List(r1.show, r2.show, r3.show, r4.toString)
res1: List[String] = List(5.0 m, 6.0 m^2, 8.0 m^3, true)
```

The ability to convert between unit quantities is represented by the `UnitConverter`

typeclass. This example illustrates the use of `UnitConverter`

:

```
scala> def pints[U](beer: Quantity[Double, U])(implicit
cnv: UnitConverter[Double, U, Double, Pint]): Unit = {
val pintsOfBeer = beer.toUnit[Pint]
print(s"I have so much beer: ${pintsOfBeer.showFull}")
}
scala> pints(500D.withUnit[Milli %* Liter])
I have so much beer: 1.0566882094325938 pint
```

The `UnitConverter`

typeclass is also used by default typeclasses for `UnitAdd`

, `UnitMul`

and the other numeric operations above. This typeclass can be extended by adding unit converter policies.

#### Compute Model for Quantity Operations

Previous sections have disussed the various operations that can be performed on `Quantity`

objects. As mentioned in the section on unit operations, the quantity result type of binary operations is governed by the left-hand-side of an exression:

```
scala> (1.withUnit[Foot] + 1.withUnit[Yard]).show
res0: String = 4 ft
```

As described in the previous section on operation typeclasses the `UnitAdd`

typeclass implements unit quantity addition. Here is the code for `UnitAdd`

:

```
trait UnitAdd[N1, U1, N2, U2] {
def vadd(v1: N1, v2: N2): N1
}
object UnitAdd {
implicit def evidenceASG0[N1, U1, N2, U2](implicit
as1: AdditiveSemigroup[N1],
uc: UnitConverter[N2, U2, N1, U1]): UnitAdd[N1, U1, N2, U2] =
new UnitAdd[N1, U1, N2, U2] {
def vadd(v1: N1, v2: N2): N1 = as1.plus(v1, uc.vcnv(v2))
}
}
```

As the above code suggests, all quantity operations by convention first convert the RHS value to the value and unit type of the left hand side, and then use the appropriate algebra (in this example `AdditiveSemigroup`

) to perform that operation with respect to the LHS value and unit. Note that this means only the LHS value type requires this algebra to exist.

The full set of Quantity operation typeclasses are defined in unitops.scala.

How do unit conversions operate? Here is the default implementation of `UnitConverter`

:

```
trait UnitConverter[N1, U1, N2, U2] {
def vcnv(v: N1): N2
}
trait UnitConverterDefaultPriority {
implicit def witness[N1, U1, N2, U2](implicit
cu: ConvertableUnits[U1, U2],
cf1: ConvertableFrom[N1],
ct2: ConvertableTo[N2]): UnitConverter[N1, U1, N2, U2] =
new UnitConverter[N1, U1, N2, U2] {
def vcnv(v: N1): N2 = ct2.fromType[Rational](cf1.toType[Rational](v) * cu.coef)
}
}
```

As the above code illustrates, a standard unit conversion proceeds by:

- Converting the input type to
`Rational`

- Multiply by the unit conversion coefficient (also a Rational)
- Converting the result to the output type.

Coulomb uses the `Rational`

type as the intermediary because it can operate lossessly on integer values, and it can accommodate most numeric repesentations with zero or minimal loss. Note that any numeric precision loss in this process is most likely to occur during the final conversion to the LHS value type.

The potential for precision loss is greatest when working with integer value types, and particuarly when the LHS unit is larger than the RHS unit, as in the second conversion below:

```
scala> ((100.withUnit[Meter]) + (1.withUnit[Kilo %* Meter])).show
res0: String = 1100 m
scala> ((1.withUnit[Kilo %* Meter]) + (100.withUnit[Meter])).show
res1: String = 1 km
```

Some unit conversion specializations are applied. For example, in the case that both LHS and RHS units and value types are the same, the fast and lossless identity function is applied:

```
implicit def witnessIdentity[N, U]: UnitConverter[N, U, N, U] = {
new UnitConverter[N, U, N, U] {
@inline def vcnv(v: N): N = v
}
}
```

Another example is the case of `Double`

value types, where preconverting the conversion coefficient to `Double`

is efficient while maintaining `Double`

accuracy:

```
implicit def witnessDouble[U1, U2](implicit
cu: ConvertableUnits[U1, U2]): UnitConverter[Double, U1, Double, U2] = {
val coef = cu.coef.toDouble
new UnitConverter[Double, U1, Double, U2] {
@inline def vcnv(v: Double): Double = v * coef
}
}
```

`UnitConverter`

and its full set of typeclass rules are defined in unitops.scala.

#### Unit Conversions for Custom Value Types

Coulomb's typeclass rules can be extended to support new value types.

There are two components to a custom extension. The first is to define any desired algebras on the new value type. Defining algebras is optional, if no numeric operations need to be supported.

The second customization component is to define what it means to multiply a value by a conversion coefficient. This can be accomplished using the `UnitConverterPolicy`

typeclass, defined in `coulomb.unitops`

.

In the following example, coulomb is extended to support the spire `Complex`

type. In the case of `Complex`

, algebras such as additive and multiplicative (semi)groups are already defined.

```
scala> import coulomb.unitops._, spire.math.Complex, spire.algebra._
// define what it means to apply unit conversion coefficients to Complex
scala> implicit def complexPolicy[U1, U2]: UnitConverterPolicy[Complex[Double], U1, Complex[Double], U2] =
new UnitConverterPolicy[Complex[Double], U1, Complex[Double], U2] {
def convert(v: Complex[Double], cu: ConvertableUnits[U1, U2]): Complex[Double] = v * cu.coef.toDouble
}
scala> q.show
res0: String = (1.0 + 2.0i) m
scala> q.toUnit[Foot].show
res1: String = (3.2808398950131235 + 6.561679790026247i) ft
scala> (q * q).show
res2: String = (-3.0 + 4.0i) m^2
scala> (q + q).show
res3: String = (2.0 + 4.0i) m
```