Skip to content

Latest commit

 

History

History
280 lines (220 loc) · 10.5 KB

File metadata and controls

280 lines (220 loc) · 10.5 KB

quant

quant is a small, type-safe Go library for physical unit conversions using generics.

It uses phantom types to model dimensions like length, area, mass, volume, flow, time, temperature, electrical values, and more, so incompatible units cannot be mixed by accident at compile time.

Features

  • Compile-time dimension safety with Quantity[D]
  • Zero-dependency, idiomatic Go API
  • Internal storage in base units for predictable conversions
  • Fluent construction with Amount[D](v).From(unit)
  • Convenience constructors like quant.Pounds(10) and quant.Kilometers(5)
  • Safe arithmetic on matching dimensions only
  • Base-unit String() formatting and explicit unit-aware Format(...)
  • Derived dimensions with type-safe division for built-in combinations like length/time
  • JSON marshaling based on the scalar base-unit value
  • Text marshaling and database/sql interop helpers
  • Support for affine temperature conversions

Installation

	go get github.com/nodivbyzero/quant

Quick Start

package main

import (
	"fmt"

	"github.com/nodivbyzero/quant"
)

func main() {
	kg := quant.Amount[quant.Mass](10).From(quant.Pound).To(quant.Kilogram)
	fmt.Printf("%.6f\n", kg)
}

Output:

4.535924

Core API

type Quantity[D any] struct

func New[D any, U Unit[D]](v float64, u U) Quantity[D]

func (q Quantity[D]) To(u Unit[D]) float64
func (q Quantity[D]) Value(u Unit[D]) float64
func (q Quantity[D]) Format(u Unit[D]) string
func (q Quantity[D]) MarshalJSON() ([]byte, error)
func (q *Quantity[D]) UnmarshalJSON(data []byte) error
func (q Quantity[D]) MarshalText() ([]byte, error)
func (q *Quantity[D]) UnmarshalText(text []byte) error
func (q Quantity[D]) Add(other Quantity[D]) Quantity[D]
func (q Quantity[D]) Sub(other Quantity[D]) Quantity[D]

Amount[D](v).From(unit) is also available as a fluent constructor.

Convenience constructors are also available for direct creation from specific units, for example:

mass := quant.Pounds(10)
distance := quant.Kilometers(5)
temp := quant.DegreesCelsius(21)

Quantity[D] also implements fmt.Stringer in the base unit for its dimension, and supports explicit formatting in a chosen unit:

fmt.Println(quant.Pounds(10))                  // 4.5359237 kg
fmt.Println(quant.Pounds(10).Format(quant.Pound)) // 10 lb

JSON marshaling stores the scalar value in the base unit for the dimension:

data, _ := json.Marshal(quant.Pounds(10))
fmt.Println(string(data)) // 4.535923700000001

Text marshaling follows the same base-unit rule:

text, _ := quant.Pounds(10).MarshalText()
fmt.Println(string(text)) // 4.5359237

For database/sql, use the adapter helper:

value, _ := quant.SQL(quant.Kilograms(5)).Value()
fmt.Println(value) // 5

All quantities are stored internally in a base unit for their dimension:

  • Mass: kilogram
  • Length: meter
  • Area: square meter
  • Volume: cubic meter
  • Volume flow rate: cubic meter per second
  • Time: second
  • Frequency: hertz
  • Speed: meter per second
  • Torque: newton-meter
  • Pace: second per meter
  • Pressure: pascal
  • Digital: bit
  • Illuminance: lux
  • Parts-per: ratio
  • Voltage: volt
  • Current: ampere
  • Power: watt
  • Apparent power: volt-ampere
  • Reactive power: volt-ampere reactive
  • Energy: joule
  • Reactive energy: volt-ampere reactive hour
  • Angle: radian
  • Charge: coulomb
  • Force: newton
  • Acceleration: meter per second squared
  • Pieces: piece
  • Temperature: kelvin

Examples By Dimension

  • Mass: kg := quant.Pounds(10).To(quant.Kilogram)
  • Length: km := quant.Miles(3).To(quant.Kilometer)
  • Area: m2 := quant.Acres(1).To(quant.SquareMeter)
  • Volume: l := quant.Gallons(1).To(quant.Liter)
  • Volume flow rate: ls := quant.LitersPerMinute(60).To(quant.LiterPerSecond)
  • Temperature: k := quant.DegreesCelsius(25).To(quant.Kelvin)
  • Time: minutes := quant.Hours(2).To(quant.Minute)
  • Frequency: hz := quant.RevolutionsPerMinute(60).To(quant.Hertz)
  • Speed: kmh := quant.MetersPerSecond(10).To(quant.KilometerPerHour)
  • Torque: nm := quant.PoundForceFeet(1).To(quant.NewtonMeter)
  • Pace: spm := quant.MinutesPerKilometer(5).To(quant.SecondPerMeter)
  • Pressure: pa := quant.Bars(1).To(quant.Pascal)
  • Digital: bytes := quant.Kibibytes(1).To(quant.Byte)
  • Illuminance: lx := quant.FootCandles(1).To(quant.Lux)
  • Parts-per: ppb := quant.PartsPerMillion(1).To(quant.PPB)
  • Voltage: v := quant.Kilovolts(1).To(quant.Volt)
  • Current: a := quant.Kiloamperes(1).To(quant.Ampere)
  • Power: w := quant.HorsepowerValues(1).To(quant.Watt)
  • Apparent power: va := quant.MegaVoltAmperes(1).To(quant.VoltAmpere)
  • Reactive power: vars := quant.MegaVoltAmpereReactives(1).To(quant.KiloVoltAmpereReactive)
  • Energy: j := quant.KilowattHours(1).To(quant.Joule)
  • Reactive energy: varh := quant.MegaVoltAmpereReactiveHours(1).To(quant.KiloVoltAmpereReactiveHour)
  • Angle: rad := quant.Degrees(180).To(quant.Radian)
  • Charge: mc := quant.Coulombs(1).To(quant.Millicoulomb)
  • Force: n := quant.KilogramsForce(1).To(quant.Newton)
  • Acceleration: ms2 := quant.GForces(1).To(quant.MeterPerSecondSquared)
  • Pieces: pcs := quant.Dozens(1).To(quant.Piece)

Arithmetic

total := quant.Meters(750).Add(quant.Kilometers(1.25))
fmt.Println(total.To(quant.Meter)) // 2000

Derived units

distance := quant.Kilometers(5)
duration := quant.Minutes(30)
speed := distance.Div(duration)

fmt.Println(speed.To(quant.KilometerPerHour)) // 10

Supported Dimensions

  • Length: Nanometer, Micrometer, Millimeter, Centimeter, Meter, Inch, Yard, USFoot, Foot, Fathom, Kilometer, Mile, NauticalMile
  • Area: SquareMillimeter (mm2), SquareCentimeter (cm2), SquareMeter (m2), Hectare (ha), SquareKilometer (km2), SquareInch (in2), SquareFoot (ft2), Acre (ac), SquareMile (mi2)
  • Mass: Microgram (mcg), Milligram (mg), Gram (g), Kilogram (kg), Ounce (oz), Pound (lb), MetricTon (mt), Stone (st), Tonne (t)
  • Volume: CubicMillimeter, CubicCentimeter, Milliliter, Liter, Kiloliter, Megaliter, Gigaliter, CubicMeter, CubicKilometer, Teaspoon, Tablespoon, CubicInch, FluidOunce, Cup, Pint, Quart, Gallon, CubicFoot, CubicYard
  • Volume flow rate: CubicMillimeterPerSecond, CubicCentimeterPerSecond, MilliliterPerSecond, CentiliterPerSecond, DeciliterPerSecond, LiterPerSecond, LiterPerMinute, LiterPerHour, KiloliterPerSecond, KiloliterPerMinute, KiloliterPerHour, CubicMeterPerSecond, CubicMeterPerMinute, CubicMeterPerHour, CubicKilometerPerSecond, TeaspoonPerSecond, TablespoonPerSecond, CubicInchPerSecond, CubicInchPerMinute, CubicInchPerHour, FluidOuncePerSecond, FluidOuncePerMinute, FluidOuncePerHour, CupPerSecond, PintPerSecond, PintPerMinute, PintPerHour, QuartPerSecond, GallonPerSecond, GallonPerMinute, GallonPerHour, CubicFootPerSecond, CubicFootPerMinute, CubicFootPerHour, CubicYardPerSecond, CubicYardPerMinute, CubicYardPerHour
  • Temperature: Celsius, Fahrenheit, Kelvin, Rankine
  • Time: Nanosecond, Microsecond, Millisecond, Second, Minute, Hour, Day, Week, Month, Year
  • Frequency: Hertz, Millihertz, Kilohertz, Megahertz, Gigahertz, Terahertz, RevolutionPerMinute, DegreePerSecond, RadianPerSecond
  • Speed: MeterPerSecond, KilometerPerHour, MilePerHour, MeterPerHour, Knot, FootPerSecond, InchPerHour, MillimeterPerHour
  • Torque: NewtonMeter, PoundForceFoot
  • Pace: SecondPerMeter, MinutePerKilometer, SecondPerFoot, MinutePerMile
  • Pressure: Pascal, Hectopascal, Kilopascal, Megapascal, Bar, Torr, MeterOfWater, MillimeterOfMercury, PSI, KSI
  • Digital: Bit, Kilobit, Megabit, Gigabit, Terabit, Byte, Kilobyte, Megabyte, Gigabyte, Terabyte, Kibibyte, Mebibyte, Gibibyte, Tebibyte
  • Illuminance: Lux, FootCandle
  • Parts-per: PPM, PPB, PPT, PPQ
  • Voltage: Volt, Millivolt, Kilovolt
  • Current: Ampere, Milliampere, Kiloampere
  • Power: Watt, Milliwatt, Kilowatt, Megawatt, Gigawatt, MetricHorsepower, BTUPerSecond, FootPoundForcePerSecond, Horsepower
  • Apparent power: VoltAmpere, MilliVoltAmpere, KiloVoltAmpere, MegaVoltAmpere, GigaVoltAmpere
  • Reactive power: VoltAmpereReactive, MilliVoltAmpereReactive, KiloVoltAmpereReactive, MegaVoltAmpereReactive, GigaVoltAmpereReactive
  • Energy: WattSecond, WattMinute, MilliwattHour, WattHour, KilowattHour, MegawattHour, GigawattHour, Joule, Kilojoule, Megajoule, Gigajoule
  • Reactive energy: VoltAmpereReactiveHour, MilliVoltAmpereReactiveHour, KiloVoltAmpereReactiveHour, MegaVoltAmpereReactiveHour, GigaVoltAmpereReactiveHour
  • Angle: Degree, Radian, Gradian, ArcMinute, ArcSecond
  • Charge: Coulomb, Millicoulomb, Microcoulomb, Nanocoulomb, Picocoulomb
  • Force: Newton, Kilonewton, PoundForce, KilogramForce
  • Acceleration: MeterPerSecondSquared, GForce, StandardGravity
  • Pieces: Piece, BakersDozen, Couple, DozenDozen, Dozen, GreatGross, Gross, HalfDozen, LongHundred, Ream, Score, SmallGross, Trio

Type Safety

Dimensions are enforced by the type system.

These compile:

distance := quant.New[quant.Length](5, quant.Meter)
extra := quant.New[quant.Length](2, quant.Kilometer)
total := distance.Add(extra)
_ = total.To(quant.Mile)

These do not compile:

distance := quant.New[quant.Length](5, quant.Meter)
mass := quant.New[quant.Mass](2, quant.Kilogram)

_ = distance.Add(mass)
_ = distance.To(quant.Pound)

Temperature Conversions

Temperature uses affine conversion rather than a pure scale factor.

k := quant.New[quant.Temperature](25, quant.Celsius).To(quant.Kelvin)
fmt.Printf("%.2f\n", k) // 298.15

Month and Year use average Gregorian durations: 365.25 / 12 days and 365.25 days respectively.

Design Notes

  • No reflection
  • No string parsing
  • No runtime registry
  • No interface{}
  • No external dependencies

The package favors correctness and simplicity first, while keeping the implementation small and allocation-free in normal use.

Development

Run tests with:

go test ./...

Run benchmarks with:

go test -bench . ./...

Recent benchmark results on Apple M4:

BenchmarkNew-10        1000000000   0.7779 ns/op
BenchmarkTo-10          175765758   6.900 ns/op
BenchmarkAdd-10        1000000000   0.2607 ns/op
BenchmarkDiv-10        1000000000   0.2594 ns/op
BenchmarkString-10       17407522  71.04 ns/op
BenchmarkFormat-10       15354643  77.52 ns/op