Documentation
¶
Overview ¶
Package quanta is a units-of-measure library for Go: dimensional analysis at run time, with conversion factors held as exact rationals.
A Quantity is a float64 magnitude paired with a Unit. A Unit knows its Dimension — the vector of the seven SI base-quantity exponents — and its exact conversion to the coherent SI unit of that dimension. Adding a length to a time is an error, not a silent absurdity; multiplying metres by metres gives m²; and converting a mile to metres and back returns the number you started with, bit for bit.
si := quanta.SI()
d, _ := si.Parse("26.2 mi")
km, _ := d.In(si.MustUnit("km"))
fmt.Println(km.Format(3)) // 42.165 km
Dimensional analysis ¶
Dimension is a comparable [7]int8 array, so dimension checks are a single machine comparison and a Dimension may be used as a map key. Every operation that combines quantities either verifies dimensions (Quantity.Add, Quantity.Sub, Quantity.Cmp, Quantity.In) or derives the new one (Quantity.Mul, Quantity.Div, Quantity.Pow). Exponents live in an int8; operations that would push one outside [-128, 127] report ErrExponentRange rather than wrapping. The bare Dimension.Mul, Dimension.Div and Dimension.Pow cannot return an error, so they saturate instead — use Dimension.CheckedMul and its siblings when you need to know.
Exact rational factors ¶
Conversion factors are math/big.Rat values, never floats. An inch is exactly 254/10000 metre; a pound is exactly 45359237/100000000 kilogram; a US gallon is exactly 231 cubic inches.
A Quantity carries its magnitude as an exact rational in coherent SI units alongside the float64 you read with Quantity.Value, and Quantity.In passes that rational through untouched — the float64 is only ever the rational rendered in the current unit. Conversion is therefore lossless, and losslessness composes: a chain of a hundred conversions has no more error than one, and converting there and back returns bit-for-bit the value you started with. That is a strictly stronger guarantee than rounding correctly at every hop, which still drifts by an ulp roughly one time in ten.
What this does not do is make the magnitudes exact. Values are float64, and a float64 is what you get back. Arithmetic on magnitudes (Quantity.Add, Quantity.Mul and friends) is ordinary float64 arithmetic, with ordinary float64 rounding, and it discards the exact rational — only the operand conversions it performs along the way are exact. The precise guarantee is: conversions are lossless, arithmetic is float64.
Affine units ¶
Degrees Celsius and Fahrenheit have a zero point of their own, which is why 0 °C is 273.15 K but 0 m/s is 0 mph. quanta models that with an exact rational offset, and then confines it. Affine units convert correctly in both directions, compare, and negate, but they may not be multiplied, divided, raised to a power, prefixed, or written into a compound unit expression; each of those returns an error wrapping ErrAffineUnit. This closes the trap where "°C·m" or "°C²" quietly produces a nonsense factor.
Differences of temperature are a separate idea from temperatures, so they get separate units: Unit.Delta turns °C into Δ°C, the linear unit of temperature difference, and SI registers Δ°C and Δ°F alongside the kelvin. Quantity.Sub applies this automatically — 30 °C minus 10 °C is 20 Δ°C, and 20 °C plus 5 Δ°C is 25 °C — while adding two absolute temperatures is an error.
Registries ¶
There is no fixed enumeration of units. A Registry is a namespace of symbols that callers extend: Registry.DefineBase introduces a coherent unit (or an entirely new dimension of your own), Registry.Define scales an existing one by an exact rational, Registry.DefineAffine adds a zero point, and Registry.Alias adds spellings. Registering a symbol twice is ErrDuplicateUnit, never a silent overwrite. SI returns a fresh registry preloaded with the SI base and derived units plus the common non-SI units; because it is fresh on each call, extending it cannot disturb anyone else.
Registry.ParseUnit parses compound expressions — "m/s^2", "kW·h", "mg/dL", "J/(kg·K)", "1/s", "m⁻²" — including SI decimal prefixes on any prefixable unit and IEC binary prefixes on the information units. An exactly registered symbol always beats a prefixed reading, so "min" is the minute rather than a milli-inch. Multiplication and division bind equally and associate to the left, so "a/b/c" is (a/b)/c; parenthesise when that is not what you mean. Parse failures are a *ParseError carrying the input and a byte offset.
Immutability and concurrency ¶
Unit and Quantity are immutable values. Every method returns a new value and none mutate the receiver, so they are safe to copy and safe to share across goroutines. A Registry is guarded internally and is safe for concurrent definition and lookup. Accessors that would otherwise expose internal state (Unit.Factor, Unit.Offset) return fresh copies.
Non-goals ¶
quanta deliberately leaves these out:
- No contexts. There is no mechanism for domain-specific conversions that are only valid under an assumption, such as trading wavelength for frequency in spectroscopy.
- No uncertainty. Magnitudes carry no error bars and nothing is propagated.
- No currency rates. You can define currency units — nothing stops you from a USD/tonne — but quanta ships no exchange rates and never will.
- No exact magnitudes. Only factors are rational; values are float64.
- No UnmarshalText. Decoding a quantity needs a registry to resolve the symbol, and a method has nowhere to get one; use Registry.Parse.
Example ¶
Example converts a marathon from miles to kilometres.
package main
import (
"fmt"
"github.com/zkrebbekx/quanta"
)
func main() {
si := quanta.SI()
d, _ := si.Parse("26.2 mi")
km, _ := d.In(si.MustUnit("km"))
fmt.Println(km.Format(3))
}
Output: 42.165 km
Example (AffineRejected) ¶
Example_affineRejected shows that an affine unit cannot enter a compound expression.
package main
import (
"errors"
"fmt"
"github.com/zkrebbekx/quanta"
)
func main() {
si := quanta.SI()
_, err := si.ParseUnit("°C/m")
fmt.Println(errors.Is(err, quanta.ErrAffineUnit))
var pe *quanta.ParseError
if errors.As(err, &pe) {
fmt.Println(pe.Offset)
}
}
Output: true 3
Example (BinaryPrefix) ¶
Example_binaryPrefix shows IEC prefixes on the information units.
package main
import (
"fmt"
"github.com/zkrebbekx/quanta"
)
func main() {
si := quanta.SI()
size := si.MustParse("1.5 GiB")
fmt.Println(size.MustIn(si.MustUnit("MiB")))
fmt.Println(size.MustIn(si.MustParseUnit("MB")).Format(1))
}
Output: 1536 MiB 1610.6 MB
Example (CustomUnit) ¶
Example_customUnit defines a unit the library does not ship and parses an expression that uses it.
package main
import (
"fmt"
"math/big"
"github.com/zkrebbekx/quanta"
)
func main() {
si := quanta.SI()
usd, err := si.DefineBase("US dollar", "USD", quanta.Dimensionless)
if err != nil {
panic(err)
}
// A furlong is exactly 660 feet.
if _, err := si.Define("furlong", "fur", big.NewRat(660, 1), si.MustUnit("ft")); err != nil {
panic(err)
}
price, _ := si.Parse("640 USD/tonne")
fmt.Println(price)
fmt.Println(usd.Dimension().IsDimensionless())
fmt.Println(si.MustParse("2 fur").MustIn(si.MustUnit("m")).Format(1))
}
Output: 640 USD/t true 402.3 m
Example (DimensionMismatch) ¶
Example_dimensionMismatch shows the error returned when dimensions disagree.
package main
import (
"errors"
"fmt"
"github.com/zkrebbekx/quanta"
)
func main() {
si := quanta.SI()
_, err := si.MustParse("5 m").Add(si.MustParse("3 s"))
fmt.Println(errors.Is(err, quanta.ErrDimensionMismatch))
fmt.Println(err)
}
Output: true cannot add L and T: quanta: dimension mismatch
Example (DimensionalAnalysis) ¶
Example_dimensionalAnalysis derives a unit by dividing quantities.
package main
import (
"fmt"
"github.com/zkrebbekx/quanta"
)
func main() {
si := quanta.SI()
distance := si.MustParse("100 m")
elapsed := si.MustParse("9.58 s")
speed, _ := distance.Div(elapsed)
fmt.Println(speed.Format(2))
fmt.Println(speed.Dimension())
fmt.Println(speed.MustIn(si.MustParseUnit("km/h")).Format(1))
}
Output: 10.44 m/s L·T⁻¹ 37.6 km/h
Example (ExactRoundTrip) ¶
Example_exactRoundTrip shows that a conversion chain returns the original float64: factors are exact rationals, so nothing drifts.
package main
import (
"fmt"
"github.com/zkrebbekx/quanta"
)
func main() {
si := quanta.SI()
start := quanta.New(3.7, si.MustUnit("mi"))
there := start.
MustIn(si.MustUnit("ft")).
MustIn(si.MustUnit("nmi")).
MustIn(si.MustUnit("m"))
back := there.MustIn(si.MustUnit("mi"))
fmt.Println(back.Value() == start.Value())
}
Output: true
Example (Temperature) ¶
Example_temperature shows affine conversion and the delta unit that subtraction produces.
package main
import (
"fmt"
"github.com/zkrebbekx/quanta"
)
func main() {
si := quanta.SI()
freezing := quanta.New(0, si.MustUnit("°C"))
fmt.Println(freezing.MustIn(si.MustUnit("K")))
fmt.Println(freezing.MustIn(si.MustUnit("°F")))
diff, _ := si.MustParse("30 °C").Sub(si.MustParse("10 °C"))
fmt.Println(diff)
}
Output: 273.15 K 32 °F 20 Δ°C
Index ¶
- Constants
- Variables
- type Dimension
- func (d Dimension) CheckedDiv(o Dimension) (Dimension, bool)
- func (d Dimension) CheckedMul(o Dimension) (Dimension, bool)
- func (d Dimension) CheckedPow(n int) (Dimension, bool)
- func (d Dimension) Div(o Dimension) Dimension
- func (d Dimension) IsDimensionless() bool
- func (d Dimension) Mul(o Dimension) Dimension
- func (d Dimension) Pow(n int) Dimension
- func (d Dimension) String() string
- type Option
- type ParseError
- type Quantity
- func (q Quantity) Abs() Quantity
- func (q Quantity) Add(o Quantity) (Quantity, error)
- func (q Quantity) Cmp(o Quantity) (int, error)
- func (q Quantity) Dimension() Dimension
- func (q Quantity) Div(o Quantity) (Quantity, error)
- func (q Quantity) Equal(o Quantity) bool
- func (q Quantity) Format(prec int) string
- func (q Quantity) In(u Unit) (Quantity, error)
- func (q Quantity) IsZero() bool
- func (q Quantity) MarshalText() ([]byte, error)
- func (q Quantity) Mul(o Quantity) (Quantity, error)
- func (q Quantity) MustIn(u Unit) Quantity
- func (q Quantity) Neg() Quantity
- func (q Quantity) Pow(n int) (Quantity, error)
- func (q Quantity) String() string
- func (q Quantity) Sub(o Quantity) (Quantity, error)
- func (q Quantity) Unit() Unit
- func (q Quantity) Value() float64
- type Registry
- func (r *Registry) Alias(symbol string, u Unit) error
- func (r *Registry) Define(name, symbol string, factor *big.Rat, base Unit, opts ...Option) (Unit, error)
- func (r *Registry) DefineAffine(name, symbol string, factor, offset *big.Rat, base Unit, opts ...Option) (Unit, error)
- func (r *Registry) DefineBase(name, symbol string, d Dimension, opts ...Option) (Unit, error)
- func (r *Registry) MustParse(s string) Quantity
- func (r *Registry) MustParseUnit(s string) Unit
- func (r *Registry) MustUnit(symbol string) Unit
- func (r *Registry) Parse(s string) (Quantity, error)
- func (r *Registry) ParseUnit(s string) (Unit, error)
- func (r *Registry) Unit(symbol string) (Unit, bool)
- func (r *Registry) Units() []Unit
- type Unit
Examples ¶
Constants ¶
const ( IdxLength = iota IdxMass IdxTime IdxCurrent IdxTemperature IdxAmount IdxLuminousIntensity // NumBaseQuantities is the number of SI base quantities. NumBaseQuantities )
Base-quantity indices into a Dimension.
Variables ¶
var ( Dimensionless = Dimension{} Length = Dimension{IdxLength: 1} Mass = Dimension{IdxMass: 1} Time = Dimension{IdxTime: 1} Current = Dimension{IdxCurrent: 1} Temperature = Dimension{IdxTemperature: 1} Amount = Dimension{IdxAmount: 1} LuminousIntensity = Dimension{IdxLuminousIntensity: 1} )
The seven base dimensions, plus the dimensionless (zero) dimension.
var ( // ErrDimensionMismatch is returned when an operation requires two // quantities to share a dimension and they do not — for example adding a // length to a time, or converting metres to seconds. ErrDimensionMismatch = errors.New("quanta: dimension mismatch") // ErrAffineUnit is returned when an affine unit (one with a non-zero zero // point, such as °C or °F) is used where only a linear unit is meaningful: // multiplication, division, exponentiation, or as part of a compound unit // expression. ErrAffineUnit = errors.New("quanta: affine unit not permitted here") // ErrUnknownUnit is returned when a symbol cannot be resolved by a // registry, either directly or as a prefixed form of a known unit. ErrUnknownUnit = errors.New("quanta: unknown unit") // ErrDuplicateUnit is returned when a definition or alias would overwrite // a symbol that is already registered. Registration never silently // replaces an existing unit. ErrDuplicateUnit = errors.New("quanta: duplicate unit symbol") // ErrSyntax is returned for malformed unit or quantity expressions. Such // errors are always also a [*ParseError], which carries the offending // input and the byte offset at which the problem was detected. ErrSyntax = errors.New("quanta: syntax error") // ErrExponentRange is returned when an operation would drive a dimension // exponent outside the representable int8 range [-128, 127]. ErrExponentRange = errors.New("quanta: dimension exponent out of range") // ErrInvalidUnit is returned when a unit definition is malformed: a nil or // zero conversion factor, or an affine unit used as the base of another // definition. ErrInvalidUnit = errors.New("quanta: invalid unit definition") )
Sentinel errors returned (usually wrapped) by this package. Match them with errors.Is rather than by comparing error strings.
var One = Unit{}
One is the dimensionless unit one — the identity for unit multiplication and the unit of a bare number.
Functions ¶
This section is empty.
Types ¶
type Dimension ¶
type Dimension [NumBaseQuantities]int8
Dimension is a vector of the seven SI base-quantity exponents, in the order length, mass, time, electric current, thermodynamic temperature, amount of substance, luminous intensity.
Dimension is a comparable array type: it may be used as a map key and compared with ==. The zero Dimension is Dimensionless.
func (Dimension) CheckedDiv ¶
CheckedDiv is Dimension.Div with an overflow report, as for Dimension.CheckedMul.
func (Dimension) CheckedMul ¶
CheckedMul is Dimension.Mul with an overflow report: ok is false if any resulting exponent would fall outside the int8 range, in which case the returned dimension is saturated.
func (Dimension) CheckedPow ¶
CheckedPow is Dimension.Pow with an overflow report, as for Dimension.CheckedMul.
func (Dimension) Div ¶
Div returns the dimension of a quotient: exponents are subtracted element-wise. Exponents saturate exactly as described for Dimension.Mul.
func (Dimension) IsDimensionless ¶
IsDimensionless reports whether every base exponent is zero.
func (Dimension) Mul ¶
Mul returns the dimension of a product: exponents are added element-wise.
Exponents saturate at the bounds of int8 rather than wrapping. Saturation is silent here because Mul cannot fail; the quantity-level operations (Quantity.Mul, Quantity.Div, Quantity.Pow) detect the same condition and report ErrExponentRange instead. Use Dimension.CheckedMul when you need to know.
func (Dimension) Pow ¶
Pow returns d raised to the integer power n: every exponent is multiplied by n. Exponents saturate exactly as described for Dimension.Mul.
func (Dimension) String ¶
String renders the dimension using the conventional base symbols M, L, T, I, Θ, N and J joined by the middle dot, with Unicode superscript exponents — for example "L·T⁻²" for acceleration and "M·L²·T⁻²" for energy. Exponents of one are omitted and zero exponents are skipped entirely. The dimensionless dimension renders as "1".
The ordering is fixed (mass, length, time, current, temperature, amount, luminous intensity), so the output is deterministic and safe to compare.
type Option ¶
type Option func(*Unit)
Option adjusts how a unit is registered. Options are passed to Registry.DefineBase, Registry.Define and Registry.DefineAffine.
func BinaryPrefix ¶
func BinaryPrefix() Option
BinaryPrefix marks a unit as accepting IEC binary prefixes (Ki, Mi, Gi, …) in addition to the decimal ones. Only information units (bit, byte) are registered this way by SI.
type ParseError ¶
type ParseError struct {
// Input is the complete string that was being parsed.
Input string
// Offset is the byte offset within Input at which the error was detected.
// It is len(Input) when the input ended unexpectedly.
Offset int
// Msg is a short human-readable description of the problem.
Msg string
// Err is the wrapped sentinel error.
Err error
}
ParseError describes a failure to parse a unit or quantity expression. It records the full input and the byte offset at which the parser gave up, and wraps a sentinel error (ErrSyntax, ErrUnknownUnit, ErrAffineUnit or ErrExponentRange) so that errors.Is works as expected.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
Error implements the error interface.
func (*ParseError) Unwrap ¶
func (e *ParseError) Unwrap() error
Unwrap returns the wrapped sentinel error so that errors.Is can match it.
type Quantity ¶
type Quantity struct {
// contains filtered or unexported fields
}
Quantity is a float64 magnitude paired with a Unit.
Quantities are immutable values: every operation returns a new Quantity and none of them mutate the receiver, so a Quantity is safe to copy and safe for concurrent use.
The zero Quantity is the dimensionless number zero.
func (Quantity) Abs ¶
Abs returns |q|, in the same unit, with the same caveat about affine scales as Quantity.Neg.
func (Quantity) Add ¶
Add returns q+o, expressed in q's unit. Both quantities must have the same dimension. o is converted losslessly, but the addition itself is ordinary float64 arithmetic — see the package documentation on what is and is not exact.
Affine units follow the rules of temperature arithmetic: an absolute temperature may not be added to anything, so o must be linear. If q itself is affine, o is treated as a difference and added to q's magnitude — 20 °C plus 5 Δ°C is 25 °C. Otherwise the operation is an ordinary conversion and sum.
func (Quantity) Cmp ¶
Cmp compares two quantities of the same dimension, returning -1 if q sorts before o, 0 if they are equal and +1 if q sorts after o.
o is converted losslessly into q's unit and the two float64 magnitudes are then compared, so 1 km compares greater than 999 m and 0 °C compares equal to 273.15 K. Comparison is deliberately done at float64 precision in q's unit rather than on the exact rationals: the latter would make 273.15 K differ from 0 °C by the ulp that float64 cannot represent, which is true but useless.
It returns an error wrapping ErrDimensionMismatch if the dimensions differ. If either magnitude is NaN the comparison is not meaningful and the result is 0 with no error, matching the behaviour of float64 comparison operators.
func (Quantity) Div ¶
Div returns q/o. The dimensions divide and the units compound, so metres divided by seconds is m/s. Affine units may not be divided; doing so returns an error wrapping ErrAffineUnit.
func (Quantity) Equal ¶
Equal reports whether two quantities describe the same physical value. It is dimension-aware: 1000 m equals 1 km, and a length is never equal to a time. Comparing across dimensions is false rather than an error.
func (Quantity) Format ¶
Format renders the quantity with exactly prec digits after the decimal point, for example Format(2) on 9.80665 m/s² gives "9.81 m/s²". A negative prec means "as many digits as needed", as in Quantity.String.
func (Quantity) In ¶
In converts the quantity to unit u, which must have the same dimension. Offsets are applied in both directions, so New(0, degC).In(kelvin) is 273.15 K and New(0, degC).In(degF) is 32 °F.
Conversion is lossless. The exact magnitude is held as a rational and carried through unchanged; the float64 you read back with Quantity.Value is that rational rendered in the current unit. A conversion therefore never accumulates error, however long the chain, and converting there and back always returns bit-for-bit the value you started with:
q.MustIn(foot).MustIn(nauticalMile).MustIn(metre).MustIn(mile) == q
It returns an error wrapping ErrDimensionMismatch if the dimensions differ.
func (Quantity) IsZero ¶
IsZero reports whether the magnitude is exactly zero. Note that this is a question about the magnitude in the quantity's own unit, which for an affine unit is not the same question as whether the quantity is at the origin of its dimension: 0 °C is not 0 K.
func (Quantity) MarshalText ¶
MarshalText implements encoding.TextMarshaler, producing the same text as Quantity.String.
There is deliberately no UnmarshalText: reading a quantity back requires a Registry to resolve the unit symbol, and a method on Quantity has nowhere to obtain one. Decode with Registry.Parse instead, which makes the choice of registry explicit.
func (Quantity) Mul ¶
Mul returns q·o. The dimensions multiply and the units compound, so metres times metres is m² and newtons times metres is N·m. Affine units may not be multiplied; doing so returns an error wrapping ErrAffineUnit.
func (Quantity) MustIn ¶
MustIn is Quantity.In for cases where the conversion is known to be valid, such as package-level constants and tests. It panics on error.
func (Quantity) Neg ¶
Neg returns -q, in the same unit. For an affine unit this negates the magnitude on that unit's own scale: -q of 5 °C is -5 °C, not -278.15 K.
func (Quantity) Pow ¶
Pow returns q raised to the integer power n. Pow(0) is the dimensionless 1. Affine units may not be raised to a power; doing so returns an error wrapping ErrAffineUnit.
func (Quantity) String ¶
String renders the quantity as its magnitude and symbol, for example "9.81 m/s²". The magnitude uses the shortest representation that round-trips through strconv.ParseFloat. A dimensionless quantity renders as the bare number.
func (Quantity) Sub ¶
Sub returns q-o, expressed in q's unit, with one exception: subtracting one affine quantity from another yields a difference, so the result is expressed in q's delta unit (see Unit.Delta) — 30 °C minus 10 °C is 20 Δ°C, not 20 °C. Subtracting a linear quantity from an affine one shifts it and stays affine. Subtracting an affine quantity from a linear one is meaningless and returns an error wrapping ErrAffineUnit.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is a namespace of unit symbols. It is the extension point of the package: rather than a fixed enumeration of conversions, callers define whatever units they need — including entirely new base dimensions — and parse expressions against their own registry.
A Registry is safe for concurrent use, including concurrent definition and lookup.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty registry. No symbol is known to it until one is defined; only the seven base dimensions themselves are built in.
func SI ¶
func SI() *Registry
SI returns a registry preloaded with the SI base and derived units plus the common non-SI units that appear alongside them: imperial and US customary lengths, masses and volumes, the pressure, energy and information units in everyday use, and the two affine temperature scales.
Each call builds and returns a fresh registry, so callers may extend the result — with Registry.Define and friends — without affecting anyone else. Building costs a few tens of microseconds; hoist the result out of hot loops rather than calling SI repeatedly.
Some notes on the choices made:
- The coherent SI unit of mass is the kilogram, so "kg" is registered as the base unit with factor 1 and is not itself prefixable. The gram is a derived unit of 1/1000 kg, and it is the gram that carries prefixes, so "mg" is 10⁻⁶ kg, exactly as SI intends.
- Information units (bit, byte) have no SI dimension, so they are modelled as dimensionless. They are the only units that accept binary prefixes, so "MiB" resolves but "Mim" does not.
- Temperature differences are expressed with the kelvin, or with the linear delta units "Δ°C" and "Δ°F". See Unit.Delta.
- The US and imperial gallons differ, so neither claims the symbol "gal"; they are "gal_US" and "gal_UK".
func (*Registry) Alias ¶
Alias registers an additional symbol for an already-defined unit — "meter" for "m", say, or "sec" for "s". Aliases resolve to the same unit but do not appear in Registry.Units.
func (*Registry) Define ¶
func (r *Registry) Define(name, symbol string, factor *big.Rat, base Unit, opts ...Option) (Unit, error)
Define registers a unit as an exact rational multiple of an existing one: one unit of the new symbol equals factor units of base. The factor must be non-zero, and base must be linear — an affine unit cannot be scaled.
Because factor is a big.Rat, conversions stay exact: an inch is defined as exactly 254/10000 of a metre, not as the float64 nearest 0.0254.
func (*Registry) DefineAffine ¶
func (r *Registry) DefineAffine(name, symbol string, factor, offset *big.Rat, base Unit, opts ...Option) (Unit, error)
DefineAffine registers a unit with a zero point of its own: a value v in the new unit equals v*factor + offset units of base. The degree Celsius is factor 1 and offset 5463/20 (273.15) relative to the kelvin; the degree Fahrenheit is factor 5/9 and offset 45967/180.
Affine units are deliberately restricted: they may not be prefixed, may not appear in a compound unit expression, and may not be multiplied, divided or raised to a power. See Unit.Delta for expressing differences.
func (*Registry) DefineBase ¶
DefineBase registers a coherent base unit: one whose conversion factor to itself is exactly 1, such as the metre for length. Use it to introduce a dimension's reference unit, or to invent a dimension of your own.
func (*Registry) MustParse ¶
MustParse is Registry.Parse for inputs known to be valid, such as package-level variables and tests. It panics on error.
func (*Registry) MustParseUnit ¶
MustParseUnit is Registry.ParseUnit for expressions known to be valid, such as package-level variables and tests. It panics on error.
func (*Registry) MustUnit ¶
MustUnit is Registry.Unit for symbols known to exist, such as package-level variables and tests. It panics if the symbol is not registered.
func (*Registry) Parse ¶
Parse parses a quantity: a float64 magnitude followed by an optional unit expression, as in "9.81 m/s^2", "1.5 kW·h", "-40 °C" or "1e3" (which is the dimensionless 1000). The space between magnitude and unit is optional.
The unit part is parsed by Registry.ParseUnit and obeys all of its rules.
func (*Registry) ParseUnit ¶
ParseUnit parses a compound unit expression against the registry.
The grammar accepts unit symbols, SI and binary prefixes, parentheses, multiplication (written "·", "*", "×", "." or simply a space), division ("/"), and integer exponents written "^2", "**2" or with Unicode superscripts ("²"). Exponents may be negative, and the numeral 1 may stand alone as a numerator, so "1/s", "s^-1" and "s⁻¹" all denote the hertz.
Multiplication and division bind equally and associate to the left: "J/kg·K" parses as "(J/kg)·K", which is probably not what was meant — write "J/(kg·K)". This is stated rather than guessed at, because unit strings in the wild disagree with each other.
An exact registered symbol always wins over a prefixed reading: "min" is the minute, not a milli-inch, and "cd" is the candela, not a centi-day.
Affine units such as °C may be parsed alone but not as part of a compound expression; that returns an error wrapping ErrAffineUnit. An empty or blank expression denotes the dimensionless unit one.
Errors are always a *ParseError carrying the input and the byte offset of the problem, wrapping ErrSyntax, ErrUnknownUnit, ErrAffineUnit or ErrExponentRange.
func (*Registry) Unit ¶
Unit looks up a single unit symbol, resolving SI and binary prefixes: "m" and "km" both succeed, and so does the alias "metre". An exactly registered symbol always wins over a prefixed reading, so "min" is the minute rather than a milli-inch.
Unit does not parse expressions — there is no "m/s" here. Use Registry.ParseUnit for that.
type Unit ¶
type Unit struct {
// contains filtered or unexported fields
}
Unit is a named scale on a Dimension.
A unit records an exact conversion to the coherent SI unit of its dimension as a rational factor, plus an optional rational offset for affine units such as °C and °F. The relationship is
value_in_coherent_SI = value*factor + offset
Units are immutable values produced by a Registry or by combining other units (see Quantity.Mul and friends). Copying a Unit is free and safe; a Unit is safe for concurrent use.
The zero Unit is the dimensionless unit one: factor 1, no offset, empty symbol. It is fully usable, so New(2, quanta.Unit{}) is a valid count of two.
Unit is not comparable with == (it carries a slice of factorisation terms); use Unit.Equal, or compare Unit.Symbol values when a map key is needed.
func (Unit) Delta ¶
Delta returns the linear "difference" unit that shares this unit's scale but has no zero point — the unit in which differences of affine quantities are expressed. Delta on °C yields Δ°C, one kelvin of temperature difference; on a unit that is already linear it returns the unit unchanged.
func (Unit) Equal ¶
Equal reports whether two units are interchangeable: same dimension, same exact factor, same exact offset and same symbol.
func (Unit) Factor ¶
Factor returns a copy of the exact rational conversion factor from this unit to the coherent SI unit of its dimension. The returned value is a fresh big.Rat the caller may modify freely.
func (Unit) IsAffine ¶
IsAffine reports whether the unit has a non-zero zero point, as °C and °F do. Affine units may be converted to and from, compared, and used with Add and Sub under the rules described on those methods, but they may never take part in multiplication, division, exponentiation, or a compound unit expression.
func (Unit) Name ¶
Name returns the unit's full name, such as "metre". Compound and prefixed units synthesise a name where they can and return the empty string otherwise.
func (Unit) Offset ¶
Offset returns a copy of the exact rational offset to the coherent SI unit, which is zero for every linear unit. The returned value is a fresh big.Rat the caller may modify freely.