2255 lines
58 KiB
Go
2255 lines
58 KiB
Go
// Copyright 2018 The Periph Authors. All rights reserved.
|
|
// Use of this source code is governed under the Apache License, Version 2.0
|
|
// that can be found in the LICENSE file.
|
|
|
|
package physic
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Angle is the measurement of the difference in orientation between two vectors
|
|
// stored as an int64 nano radian.
|
|
//
|
|
// A negative angle is valid.
|
|
//
|
|
// The highest representable value is a bit over 9.223GRad or 500,000,000,000°.
|
|
type Angle int64
|
|
|
|
// String returns the angle formatted as a string in degree.
|
|
func (a Angle) String() string {
|
|
// Angle is not a S.I. unit, so it must not be prefixed by S.I. prefixes.
|
|
if a == 0 {
|
|
return "0°"
|
|
}
|
|
// Round.
|
|
prefix := ""
|
|
if a < 0 {
|
|
a = -a
|
|
prefix = "-"
|
|
}
|
|
switch {
|
|
case a < Degree:
|
|
v := ((a * 1000) + Degree/2) / Degree
|
|
return prefix + "0." + prefixZeros(3, int(v)) + "°"
|
|
case a < 10*Degree:
|
|
v := ((a * 1000) + Degree/2) / Degree
|
|
i := v / 1000
|
|
v = v - i*1000
|
|
return prefix + strconv.FormatInt(int64(i), 10) + "." + prefixZeros(3, int(v)) + "°"
|
|
case a < 100*Degree:
|
|
v := ((a * 1000) + Degree/2) / Degree
|
|
i := v / 1000
|
|
v = v - i*1000
|
|
return prefix + strconv.FormatInt(int64(i), 10) + "." + prefixZeros(2, int(v)) + "°"
|
|
case a < 1000*Degree:
|
|
v := ((a * 1000) + Degree/2) / Degree
|
|
i := v / 1000
|
|
v = v - i*1000
|
|
return prefix + strconv.FormatInt(int64(i), 10) + "." + prefixZeros(1, int(v)) + "°"
|
|
case a > maxAngle-Degree:
|
|
u := (uint64(a) + uint64(Degree)/2) / uint64(Degree)
|
|
v := int64(u)
|
|
return prefix + strconv.FormatInt(int64(v), 10) + "°"
|
|
default:
|
|
v := (a + Degree/2) / Degree
|
|
return prefix + strconv.FormatInt(int64(v), 10) + "°"
|
|
}
|
|
}
|
|
|
|
// Set sets the Angle to the value represented by s. Units are to be provided in
|
|
// "rad", "deg" or "°" with an optional SI prefix: "p", "n", "u", "µ", "m", "k",
|
|
// "M", "G" or "T".
|
|
func (a *Angle) Set(s string) error {
|
|
d, n, err := atod(s)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s[n:], "Rad", "rad", "Deg", "deg", "°"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("Rad, Deg or °")
|
|
case errOverflowsInt64:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return maxValueErr(maxAngle.String())
|
|
case errOverflowsInt64Negative:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return minValueErr(minAngle.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
var si prefix
|
|
if n != len(s) {
|
|
r, rsize := utf8.DecodeRuneInString(s[n:])
|
|
if r <= 1 || rsize == 0 {
|
|
return errors.New("unexpected end of string")
|
|
}
|
|
var siSize int
|
|
si, siSize = parseSIPrefix(r)
|
|
n += siSize
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "Deg", "deg", "°":
|
|
degreePerRadian := decimal{
|
|
base: 17453293,
|
|
exp: 0,
|
|
neg: false,
|
|
}
|
|
deg, _ := decimalMul(d, degreePerRadian)
|
|
// Impossible for precision loss to exceed 9 since the number of
|
|
// significant figures in degrees per radian is only 8.
|
|
v, overflow := dtoi(deg, int(si))
|
|
if overflow {
|
|
if deg.neg {
|
|
return minValueErr(minAngle.String())
|
|
}
|
|
return maxValueErr(maxAngle.String())
|
|
}
|
|
*a = (Angle)(v)
|
|
case "Rad", "rad":
|
|
v, overflow := dtoi(d, int(si-nano))
|
|
if overflow {
|
|
if d.neg {
|
|
return minValueErr("-9.223G" + s[n:])
|
|
}
|
|
return maxValueErr("9.223G" + s[n:])
|
|
}
|
|
*a = (Angle)(v)
|
|
case "":
|
|
return noUnitErr("Rad, Deg or °")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "Rad", "rad", "Deg", "deg", "°"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("Rad, Deg or °")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Well known Angle constants.
|
|
const (
|
|
NanoRadian Angle = 1
|
|
MicroRadian Angle = 1000 * NanoRadian
|
|
MilliRadian Angle = 1000 * MicroRadian
|
|
Radian Angle = 1000 * MilliRadian
|
|
|
|
// Theta is 2π. This is equivalent to 360°.
|
|
Theta Angle = 6283185307 * NanoRadian
|
|
Pi Angle = 3141592653 * NanoRadian
|
|
Degree Angle = 17453293 * NanoRadian
|
|
|
|
maxAngle Angle = 9223372036854775807
|
|
minAngle Angle = -9223372036854775807
|
|
)
|
|
|
|
// Distance is a measurement of length stored as an int64 nano metre.
|
|
//
|
|
// This is one of the base unit in the International System of Units.
|
|
//
|
|
// The highest representable value is 9.2Gm.
|
|
type Distance int64
|
|
|
|
// String returns the distance formatted as a string in metre.
|
|
func (d Distance) String() string {
|
|
return nanoAsString(int64(d)) + "m"
|
|
}
|
|
|
|
// Set sets the Distance to the value represented by s. Units are to
|
|
// be provided in "m", "Mile", "Yard", "in", or "ft" with an optional SI
|
|
// prefix: "p", "n", "u", "µ", "m", "k", "M", "G" or "T".
|
|
func (d *Distance) Set(s string) error {
|
|
dc, n, err := atod(s)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s[n:], "in", "ft", "Yard", "yard", "Mile", "mile", "m"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("m, Mile, in, ft or Yard")
|
|
case errOverflowsInt64:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return maxValueErr(maxDistance.String())
|
|
case errOverflowsInt64Negative:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return minValueErr(minDistance.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
si := prefix(unit)
|
|
if n != len(s) {
|
|
r, rsize := utf8.DecodeRuneInString(s[n:])
|
|
if r <= 1 || rsize == 0 {
|
|
return errors.New("unexpected end of string")
|
|
}
|
|
var siSize int
|
|
si, siSize = parseSIPrefix(r)
|
|
if si == milli || si == mega {
|
|
switch s[n:] {
|
|
case "m", "Mile", "mile":
|
|
si = unit
|
|
}
|
|
}
|
|
if si != unit {
|
|
n += siSize
|
|
}
|
|
}
|
|
v, overflow := dtoi(dc, int(si-nano))
|
|
if overflow {
|
|
if dc.neg {
|
|
return minValueErr(minDistance.String())
|
|
}
|
|
return maxValueErr(maxDistance.String())
|
|
}
|
|
switch s[n:] {
|
|
case "m":
|
|
*d = (Distance)(v)
|
|
case "Mile", "mile":
|
|
switch {
|
|
case v > maxMiles:
|
|
return maxValueErr("5731Mile")
|
|
case v < minMiles:
|
|
return minValueErr("-5731Mile")
|
|
case v >= 0:
|
|
*d = (Distance)((v*1609344 + 500) / 1000)
|
|
default:
|
|
*d = (Distance)((v*1609344 - 500) / 1000)
|
|
}
|
|
case "Yard", "yard":
|
|
switch {
|
|
case v > maxYards:
|
|
return maxValueErr("1 Million Yard")
|
|
case v < minYards:
|
|
return minValueErr("-1 Million Yard")
|
|
case v >= 0:
|
|
*d = (Distance)((v*9144 + 5000) / 10000)
|
|
default:
|
|
*d = (Distance)((v*9144 - 5000) / 10000)
|
|
}
|
|
case "ft":
|
|
switch {
|
|
case v > maxFeet:
|
|
return maxValueErr("3 Million ft")
|
|
case v < minFeet:
|
|
return minValueErr("-3 Million ft")
|
|
case v >= 0:
|
|
*d = (Distance)((v*3048 + 5000) / 10000)
|
|
default:
|
|
*d = (Distance)((v*3048 - 5000) / 10000)
|
|
}
|
|
case "in":
|
|
switch {
|
|
case v > maxInches:
|
|
return maxValueErr("36 Million inch")
|
|
case v < minInches:
|
|
return minValueErr("-36 Million inch")
|
|
case v >= 0:
|
|
*d = (Distance)((v*254 + 5000) / 10000)
|
|
default:
|
|
*d = (Distance)((v*254 - 5000) / 10000)
|
|
}
|
|
case "":
|
|
return noUnitErr("m, Mile, in, ft or Yard")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "in", "ft", "Yard", "Mile", "m"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("m, Mile, in, ft or Yard")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Well known Distance constants.
|
|
const (
|
|
NanoMetre Distance = 1
|
|
MicroMetre Distance = 1000 * NanoMetre
|
|
MilliMetre Distance = 1000 * MicroMetre
|
|
Metre Distance = 1000 * MilliMetre
|
|
KiloMetre Distance = 1000 * Metre
|
|
MegaMetre Distance = 1000 * KiloMetre
|
|
GigaMetre Distance = 1000 * MegaMetre
|
|
|
|
// Conversion between Metre and imperial units.
|
|
Thou Distance = 25400 * NanoMetre
|
|
Inch Distance = 1000 * Thou
|
|
Foot Distance = 12 * Inch
|
|
Yard Distance = 3 * Foot
|
|
Mile Distance = 1760 * Yard
|
|
|
|
maxDistance = 9223372036854775807 * NanoMetre
|
|
minDistance = -9223372036854775807 * NanoMetre
|
|
maxMiles int64 = (int64(maxDistance) - 500) / int64((Mile)/1000000) // ~Max/1609344
|
|
minMiles int64 = (int64(minDistance) + 500) / int64((Mile)/1000000) // ~Min/1609344
|
|
maxYards int64 = (int64(maxDistance) - 5000) / int64((Yard)/100000) // ~Max/9144
|
|
minYards int64 = (int64(minDistance) + 5000) / int64((Yard)/100000) // ~Min/9144
|
|
maxFeet int64 = (int64(maxDistance) - 5000) / int64((Foot)/100000) // ~Max/3048
|
|
minFeet int64 = (int64(minDistance) + 5000) / int64((Foot)/100000) // ~Min/3048
|
|
maxInches int64 = (int64(maxDistance) - 5000) / int64((Inch)/100000) // ~Max/254
|
|
minInches int64 = (int64(minDistance) + 5000) / int64((Inch)/100000) // ~Min/254
|
|
)
|
|
|
|
// ElectricCurrent is a measurement of a flow of electric charge stored as an
|
|
// int64 nano Ampere.
|
|
//
|
|
// This is one of the base unit in the International System of Units.
|
|
//
|
|
// The highest representable value is 9.2GA.
|
|
type ElectricCurrent int64
|
|
|
|
// String returns the current formatted as a string in Ampere.
|
|
func (c ElectricCurrent) String() string {
|
|
return nanoAsString(int64(c)) + "A"
|
|
}
|
|
|
|
// Set sets the ElectricCurrent to the value represented by s. Units are to
|
|
// be provided in "A" with an optional SI prefix: "p", "n", "u", "µ", "m", "k",
|
|
// "M", "G" or "T".
|
|
func (c *ElectricCurrent) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, nano)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "A", "a"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("A")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxElectricCurrent.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minElectricCurrent.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "A", "a":
|
|
*c = (ElectricCurrent)(v)
|
|
case "":
|
|
return noUnitErr("A")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "A"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("A")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Well known ElectricCurrent constants.
|
|
const (
|
|
NanoAmpere ElectricCurrent = 1
|
|
MicroAmpere ElectricCurrent = 1000 * NanoAmpere
|
|
MilliAmpere ElectricCurrent = 1000 * MicroAmpere
|
|
Ampere ElectricCurrent = 1000 * MilliAmpere
|
|
KiloAmpere ElectricCurrent = 1000 * Ampere
|
|
MegaAmpere ElectricCurrent = 1000 * KiloAmpere
|
|
GigaAmpere ElectricCurrent = 1000 * MegaAmpere
|
|
|
|
maxElectricCurrent = 9223372036854775807 * NanoAmpere
|
|
minElectricCurrent = -9223372036854775807 * NanoAmpere
|
|
)
|
|
|
|
// ElectricPotential is a measurement of electric potential stored as an int64
|
|
// nano Volt.
|
|
//
|
|
// The highest representable value is 9.2GV.
|
|
type ElectricPotential int64
|
|
|
|
// String returns the tension formatted as a string in Volt.
|
|
func (p ElectricPotential) String() string {
|
|
return nanoAsString(int64(p)) + "V"
|
|
}
|
|
|
|
// Set sets the ElectricPotential to the value represented by s. Units are to
|
|
// be provided in "V" with an optional SI prefix: "p", "n", "u", "µ", "m", "k",
|
|
// "M", "G" or "T".
|
|
func (p *ElectricPotential) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, nano)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "V", "v"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("V")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxElectricPotential.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minElectricPotential.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
switch s[n:] {
|
|
case "V", "v":
|
|
*p = (ElectricPotential)(v)
|
|
case "":
|
|
return noUnitErr("V")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "V", "v"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("V")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Well known ElectricPotential constants.
|
|
const (
|
|
// Volt is W/A, kg⋅m²/s³/A.
|
|
NanoVolt ElectricPotential = 1
|
|
MicroVolt ElectricPotential = 1000 * NanoVolt
|
|
MilliVolt ElectricPotential = 1000 * MicroVolt
|
|
Volt ElectricPotential = 1000 * MilliVolt
|
|
KiloVolt ElectricPotential = 1000 * Volt
|
|
MegaVolt ElectricPotential = 1000 * KiloVolt
|
|
GigaVolt ElectricPotential = 1000 * MegaVolt
|
|
|
|
maxElectricPotential = 9223372036854775807 * NanoVolt
|
|
minElectricPotential = -9223372036854775807 * NanoVolt
|
|
)
|
|
|
|
// ElectricResistance is a measurement of the difficulty to pass an electric
|
|
// current through a conductor stored as an int64 nano Ohm.
|
|
//
|
|
// The highest representable value is 9.2GΩ.
|
|
type ElectricResistance int64
|
|
|
|
// String returns the resistance formatted as a string in Ohm.
|
|
func (r ElectricResistance) String() string {
|
|
return nanoAsString(int64(r)) + "Ω"
|
|
}
|
|
|
|
// Set sets the ElectricResistance to the value represented by s. Units are to
|
|
// be provided in "Ohm", or "Ω" with an optional SI prefix: "p", "n", "u", "µ",
|
|
// "m", "k", "M", "G" or "T".
|
|
func (r *ElectricResistance) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, nano)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "Ohm", "ohm", "Ω"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("Ohm or Ω")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxElectricResistance.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minElectricResistance.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "Ohm", "ohm", "Ω":
|
|
*r = (ElectricResistance)(v)
|
|
case "":
|
|
return noUnitErr("Ohm or Ω")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "Ohm", "ohm", "Ω"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("Ohm or Ω")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Well known ElectricResistance constants.
|
|
const (
|
|
// Ohm is V/A, kg⋅m²/s³/A².
|
|
NanoOhm ElectricResistance = 1
|
|
MicroOhm ElectricResistance = 1000 * NanoOhm
|
|
MilliOhm ElectricResistance = 1000 * MicroOhm
|
|
Ohm ElectricResistance = 1000 * MilliOhm
|
|
KiloOhm ElectricResistance = 1000 * Ohm
|
|
MegaOhm ElectricResistance = 1000 * KiloOhm
|
|
GigaOhm ElectricResistance = 1000 * MegaOhm
|
|
|
|
maxElectricResistance = 9223372036854775807 * NanoOhm
|
|
minElectricResistance = -9223372036854775807 * NanoOhm
|
|
)
|
|
|
|
// Force is a measurement of interaction that will change the motion of an
|
|
// object stored as an int64 nano Newton.
|
|
//
|
|
// A measurement of Force is a vector and has a direction but this unit only
|
|
// represents the magnitude. The orientation needs to be stored as a Quaternion
|
|
// independently.
|
|
//
|
|
// The highest representable value is 9.2TN.
|
|
type Force int64
|
|
|
|
// String returns the force formatted as a string in Newton.
|
|
func (f Force) String() string {
|
|
return nanoAsString(int64(f)) + "N"
|
|
}
|
|
|
|
// Set sets the Force to the value represented by s. Units are to
|
|
// be provided in "N", or "lbf" (Pound force) with an optional SI prefix: "p",
|
|
// "n", "u", "µ", "m", "k", "M", "G" or "T".
|
|
func (f *Force) Set(s string) error {
|
|
d, n, err := atod(s)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s[n:], "N", "lbf"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("N or lbf")
|
|
case errOverflowsInt64:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return maxValueErr(maxForce.String())
|
|
case errOverflowsInt64Negative:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return minValueErr(minForce.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
var si prefix
|
|
if n != len(s) {
|
|
r, rsize := utf8.DecodeRuneInString(s[n:])
|
|
if r <= 1 || rsize == 0 {
|
|
return errors.New("unexpected end of string")
|
|
}
|
|
var siSize int
|
|
si, siSize = parseSIPrefix(r)
|
|
|
|
n += siSize
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "lbf":
|
|
poundForce := decimal{
|
|
base: 4448221615261,
|
|
exp: -3,
|
|
neg: false,
|
|
}
|
|
lbf, loss := decimalMul(d, poundForce)
|
|
if loss > 9 {
|
|
return errors.New("converting to nano Newtons would overflow, consider using nN for maximum precision")
|
|
}
|
|
v, overflow := dtoi(lbf, int(si))
|
|
if overflow {
|
|
if lbf.neg {
|
|
return minValueErr("-2.073496519Glbf")
|
|
}
|
|
return maxValueErr("2.073496519Glbf")
|
|
}
|
|
*f = (Force)(v)
|
|
case "N":
|
|
v, overflow := dtoi(d, int(si-nano))
|
|
if overflow {
|
|
if d.neg {
|
|
return minValueErr(minForce.String())
|
|
}
|
|
return maxValueErr(maxForce.String())
|
|
}
|
|
*f = (Force)(v)
|
|
case "":
|
|
return noUnitErr("N or lbf")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "N", "lbf"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("N or lbf")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Well known Force constants.
|
|
const (
|
|
// Newton is kg⋅m/s².
|
|
NanoNewton Force = 1
|
|
MicroNewton Force = 1000 * NanoNewton
|
|
MilliNewton Force = 1000 * MicroNewton
|
|
Newton Force = 1000 * MilliNewton
|
|
KiloNewton Force = 1000 * Newton
|
|
MegaNewton Force = 1000 * KiloNewton
|
|
GigaNewton Force = 1000 * MegaNewton
|
|
|
|
EarthGravity Force = 9806650 * MicroNewton
|
|
|
|
// Conversion between Newton and imperial units.
|
|
// Pound is both a unit of mass and weight (force). The suffix Force is added
|
|
// to disambiguate the measurement it represents.
|
|
PoundForce Force = 4448221615 * NanoNewton
|
|
|
|
maxForce Force = (1 << 63) - 1
|
|
minForce Force = -((1 << 63) - 1)
|
|
)
|
|
|
|
// Frequency is a measurement of cycle per second, stored as an int64 micro
|
|
// Hertz.
|
|
//
|
|
// The highest representable value is 9.2THz.
|
|
type Frequency int64
|
|
|
|
// String returns the frequency formatted as a string in Hertz.
|
|
func (f Frequency) String() string {
|
|
return microAsString(int64(f)) + "Hz"
|
|
}
|
|
|
|
// Set sets the Frequency to the value represented by s. Units are to
|
|
// be provided in "Hz" or "rpm" with an optional SI prefix: "p", "n", "u", "µ",
|
|
// "m", "k", "M", "G" or "T".
|
|
//
|
|
// Unlike most Set() functions, "Hz" is assumed by default.
|
|
func (f *Frequency) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, micro)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "Hz", "hz"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("Hz")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxFrequency.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minFrequency.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "Hz", "hz", "":
|
|
*f = (Frequency)(v)
|
|
default:
|
|
if found := hasSuffixes(s[n:], "Hz", "hz"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("Hz")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Period returns the duration of one cycle at this frequency.
|
|
//
|
|
// Frequency above GigaHertz cannot be represented as Duration.
|
|
//
|
|
// A 0Hz frequency returns a 0s period.
|
|
func (f Frequency) Period() time.Duration {
|
|
if f == 0 {
|
|
return 0
|
|
}
|
|
if f < 0 {
|
|
return (time.Second*time.Duration(Hertz) - time.Duration(f/2)) / time.Duration(f)
|
|
}
|
|
return (time.Second*time.Duration(Hertz) + time.Duration(f/2)) / time.Duration(f)
|
|
}
|
|
|
|
// Duration returns the duration of one cycle at this frequency.
|
|
//
|
|
// Deprecated: This method is removed in v4.0.0. Use Period() instead.
|
|
func (f Frequency) Duration() time.Duration {
|
|
return f.Period()
|
|
}
|
|
|
|
// PeriodToFrequency returns the frequency for a period of this interval.
|
|
//
|
|
// A 0s period returns a 0Hz frequency.
|
|
func PeriodToFrequency(p time.Duration) Frequency {
|
|
if p == 0 {
|
|
return 0
|
|
}
|
|
if p < 0 {
|
|
return (Frequency(time.Second)*Hertz - Frequency(p/2)) / Frequency(p)
|
|
}
|
|
return (Frequency(time.Second)*Hertz + Frequency(p/2)) / Frequency(p)
|
|
}
|
|
|
|
// Well known Frequency constants.
|
|
const (
|
|
// Hertz is 1/s.
|
|
MicroHertz Frequency = 1
|
|
MilliHertz Frequency = 1000 * MicroHertz
|
|
Hertz Frequency = 1000 * MilliHertz
|
|
KiloHertz Frequency = 1000 * Hertz
|
|
MegaHertz Frequency = 1000 * KiloHertz
|
|
GigaHertz Frequency = 1000 * MegaHertz
|
|
TeraHertz Frequency = 1000 * GigaHertz
|
|
|
|
// RPM is revolutions per minute. It is used to quantify angular frequency.
|
|
RPM Frequency = 16667 * MicroHertz
|
|
|
|
maxFrequency = 9223372036854775807 * MicroHertz
|
|
minFrequency = -9223372036854775807 * MicroHertz
|
|
)
|
|
|
|
// Mass is a measurement of mass stored as an int64 nano gram.
|
|
//
|
|
// This is one of the base unit in the International System of Units.
|
|
//
|
|
// The highest representable value is 9.2Gg.
|
|
type Mass int64
|
|
|
|
// String returns the mass formatted as a string in gram.
|
|
func (m Mass) String() string {
|
|
return nanoAsString(int64(m)) + "g"
|
|
}
|
|
|
|
// Set sets the Mass to the value represented by s. Units are to be provided in
|
|
// "g", "lb" or "oz" with an optional SI prefix: "p", "n", "u", "µ", "m", "k",
|
|
// "M", "G" or "T".
|
|
func (m *Mass) Set(s string) error {
|
|
d, n, err := atod(s)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s[n:], "g", "lb", "oz"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("g, lb or oz")
|
|
case errOverflowsInt64:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return maxValueErr(maxMass.String())
|
|
case errOverflowsInt64Negative:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return minValueErr(minMass.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
var si prefix
|
|
if n != len(s) {
|
|
r, rsize := utf8.DecodeRuneInString(s[n:])
|
|
if r <= 1 || rsize == 0 {
|
|
return errors.New("unexpected end of string")
|
|
}
|
|
var siSize int
|
|
si, siSize = parseSIPrefix(r)
|
|
n += siSize
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "g":
|
|
v, overflow := dtoi(d, int(si-nano))
|
|
if overflow {
|
|
if d.neg {
|
|
return minValueErr(minMass.String())
|
|
}
|
|
return maxValueErr(maxMass.String())
|
|
}
|
|
*m = (Mass)(v)
|
|
case "lb":
|
|
gramsPerlb := decimal{
|
|
base: uint64(PoundMass),
|
|
exp: 0,
|
|
neg: false,
|
|
}
|
|
lbs, _ := decimalMul(d, gramsPerlb)
|
|
v, overflow := dtoi(lbs, int(si))
|
|
if overflow {
|
|
if lbs.neg {
|
|
return minValueErr(strconv.FormatInt(int64(minPoundMass), 10) + "lb")
|
|
}
|
|
return maxValueErr(strconv.FormatInt(int64(maxPoundMass), 10) + "lb")
|
|
}
|
|
*m = (Mass)(v)
|
|
case "oz":
|
|
gramsPerOz := decimal{
|
|
base: uint64(OunceMass),
|
|
exp: 0,
|
|
neg: false,
|
|
}
|
|
oz, _ := decimalMul(d, gramsPerOz)
|
|
v, overflow := dtoi(oz, int(si))
|
|
if overflow {
|
|
if oz.neg {
|
|
return minValueErr(strconv.FormatInt(int64(minOunceMass), 10) + "oz")
|
|
}
|
|
return maxValueErr(strconv.FormatInt(int64(maxOunceMass), 10) + "oz")
|
|
}
|
|
*m = (Mass)(v)
|
|
case "":
|
|
return noUnitErr("g, lb or oz")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "g", "lb", "oz"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("g, lb or oz")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Well known Mass constants.
|
|
const (
|
|
NanoGram Mass = 1
|
|
MicroGram Mass = 1000 * NanoGram
|
|
MilliGram Mass = 1000 * MicroGram
|
|
Gram Mass = 1000 * MilliGram
|
|
KiloGram Mass = 1000 * Gram
|
|
MegaGram Mass = 1000 * KiloGram
|
|
GigaGram Mass = 1000 * MegaGram
|
|
Tonne Mass = MegaGram
|
|
|
|
// Conversion between Gram and imperial units.
|
|
// Ounce is both a unit of mass, weight (force) or volume depending on
|
|
// context. The suffix Mass is added to disambiguate the measurement it
|
|
// represents.
|
|
OunceMass Mass = 28349523125 * NanoGram
|
|
// Pound is both a unit of mass and weight (force). The suffix Mass is added
|
|
// to disambiguate the measurement it represents.
|
|
PoundMass Mass = 16 * OunceMass
|
|
|
|
Slug Mass = 14593903 * MilliGram
|
|
|
|
maxMass Mass = (1 << 63) - 1
|
|
minMass Mass = -((1 << 63) - 1)
|
|
|
|
// min and max Pound mass are in lb.
|
|
minPoundMass Mass = -20334054
|
|
maxPoundMass Mass = 20334054
|
|
// min and max Ounce mass are in oz.
|
|
minOunceMass Mass = -325344874
|
|
maxOunceMass Mass = 325344874
|
|
)
|
|
|
|
// Pressure is a measurement of force applied to a surface per unit
|
|
// area (stress) stored as an int64 nano Pascal.
|
|
//
|
|
// The highest representable value is 9.2GPa.
|
|
type Pressure int64
|
|
|
|
// String returns the pressure formatted as a string in Pascal.
|
|
func (p Pressure) String() string {
|
|
return nanoAsString(int64(p)) + "Pa"
|
|
}
|
|
|
|
// Set sets the Pressure to the value represented by s. Units are to
|
|
// be provided in "Pa" with an optional SI prefix: "p", "n", "u", "µ", "m", "k",
|
|
// "M", "G" or "T".
|
|
func (p *Pressure) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, nano)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "Pa"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("Pa")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxPressure.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minPressure.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "Pa":
|
|
*p = (Pressure)(v)
|
|
case "":
|
|
return noUnitErr("Pa")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "Pa"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("Pa")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Well known Pressure constants.
|
|
const (
|
|
// Pascal is N/m², kg/m/s².
|
|
NanoPascal Pressure = 1
|
|
MicroPascal Pressure = 1000 * NanoPascal
|
|
MilliPascal Pressure = 1000 * MicroPascal
|
|
Pascal Pressure = 1000 * MilliPascal
|
|
KiloPascal Pressure = 1000 * Pascal
|
|
MegaPascal Pressure = 1000 * KiloPascal
|
|
GigaPascal Pressure = 1000 * MegaPascal
|
|
|
|
maxPressure = 9223372036854775807 * NanoPascal
|
|
minPressure = -9223372036854775807 * NanoPascal
|
|
)
|
|
|
|
// RelativeHumidity is a humidity level measurement stored as an int32 fixed
|
|
// point integer at a precision of 0.00001%rH.
|
|
//
|
|
// Valid values are between 0% and 100%.
|
|
type RelativeHumidity int32
|
|
|
|
// String returns the humidity formatted as a string.
|
|
func (r RelativeHumidity) String() string {
|
|
r /= MilliRH
|
|
frac := int(r % 10)
|
|
if frac == 0 {
|
|
return strconv.Itoa(int(r)/10) + "%rH"
|
|
}
|
|
if frac < 0 {
|
|
frac = -frac
|
|
}
|
|
return strconv.Itoa(int(r)/10) + "." + strconv.Itoa(frac) + "%rH"
|
|
}
|
|
|
|
// Set sets the RelativeHumidity to the value represented by s. Units are to
|
|
// be provided in "%rH" or "%" with an optional SI prefix: "p", "n", "u", "µ",
|
|
// "m", "k", "M", "G" or "T".
|
|
func (r *RelativeHumidity) Set(s string) error {
|
|
// PercentRH is micro + deca.
|
|
v, n, err := valueOfUnitString(s, micro+deca)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s[n:], "%rH", "%"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("%rH or %")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxRelativeHumidity.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minRelativeHumidity.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "%rH", "%":
|
|
// We need an extra check here to make sure that v will fit inside a
|
|
// int32.
|
|
switch {
|
|
case v > int64(maxRelativeHumidity):
|
|
return maxValueErr(maxRelativeHumidity.String())
|
|
case v < int64(minRelativeHumidity):
|
|
return minValueErr(minRelativeHumidity.String())
|
|
}
|
|
*r = (RelativeHumidity)(v)
|
|
case "":
|
|
return noUnitErr("%rH or %")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "%rH", "%"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("%rH or %")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Well known RelativeHumidity constants.
|
|
const (
|
|
TenthMicroRH RelativeHumidity = 1 // 0.00001%rH
|
|
MicroRH RelativeHumidity = 10 * TenthMicroRH // 0.0001%rH
|
|
MilliRH RelativeHumidity = 1000 * MicroRH // 0.1%rH
|
|
PercentRH RelativeHumidity = 10 * MilliRH // 1%rH
|
|
|
|
maxRelativeHumidity RelativeHumidity = 100 * PercentRH
|
|
minRelativeHumidity RelativeHumidity = 0
|
|
)
|
|
|
|
// Speed is a measurement of magnitude of velocity stored as an int64 nano
|
|
// Metre per Second.
|
|
//
|
|
// The highest representable value is 9.2Gm/s.
|
|
type Speed int64
|
|
|
|
// String returns the speed formatted as a string in m/s.
|
|
func (sp Speed) String() string {
|
|
return nanoAsString(int64(sp)) + "m/s"
|
|
}
|
|
|
|
// Set sets the Speed to the value represented by s. Units are to be provided in
|
|
// "mps"(meters per second), "m/s", "kph", "fps", or "mph" with an optional SI
|
|
// prefix: "p", "n", "u", "µ", "m", "k", "M", "G" or "T".
|
|
func (sp *Speed) Set(s string) error {
|
|
d, n, err := atod(s)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s[n:], "m/s", "mps", "kph", "fps", "mph"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("m/s, mps, kph, fps or mph")
|
|
case errOverflowsInt64:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return maxValueErr(maxSpeed.String())
|
|
case errOverflowsInt64Negative:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return minValueErr(minSpeed.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
var si prefix
|
|
if n != len(s) {
|
|
r, rsize := utf8.DecodeRuneInString(s[n:])
|
|
if r <= 1 || rsize == 0 {
|
|
return errors.New("unexpected end of string")
|
|
}
|
|
var siSize int
|
|
si, siSize = parseSIPrefix(r)
|
|
if si == milli {
|
|
switch s[n:] {
|
|
case "m/s", "mps", "mph":
|
|
si = unit
|
|
siSize = 0
|
|
}
|
|
}
|
|
if si == kilo {
|
|
switch s[n:] {
|
|
case "kph":
|
|
si = unit
|
|
siSize = 0
|
|
}
|
|
}
|
|
n += siSize
|
|
}
|
|
switch s[n:] {
|
|
case "m/s", "mps":
|
|
v, overflow := dtoi(d, int(si-nano))
|
|
if overflow {
|
|
if d.neg {
|
|
return minValueErr(minSpeed.String())
|
|
}
|
|
return maxValueErr(maxSpeed.String())
|
|
}
|
|
*sp = (Speed)(v)
|
|
case "kph":
|
|
mpsPerkph := decimal{
|
|
base: uint64(KilometrePerHour),
|
|
exp: 0,
|
|
neg: false,
|
|
}
|
|
kph, _ := decimalMul(d, mpsPerkph)
|
|
v, overflow := dtoi(kph, int(si))
|
|
if overflow {
|
|
if kph.neg {
|
|
return minValueErr(strconv.FormatInt(int64(minKilometrePerHour), 10) + "kph")
|
|
}
|
|
return maxValueErr(strconv.FormatInt(int64(maxKilometrePerHour), 10) + "kph")
|
|
}
|
|
*sp = (Speed)(v)
|
|
case "fps":
|
|
mpsPerfps := decimal{
|
|
base: uint64(FootPerSecond / 1000),
|
|
exp: 3,
|
|
neg: false,
|
|
}
|
|
oz, _ := decimalMul(d, mpsPerfps)
|
|
v, overflow := dtoi(oz, int(si))
|
|
if overflow {
|
|
if oz.neg {
|
|
return minValueErr(strconv.FormatInt(int64(minFootPerSecond), 10) + "fps")
|
|
}
|
|
return maxValueErr(strconv.FormatInt(int64(maxFootPerSecond), 10) + "fps")
|
|
}
|
|
*sp = (Speed)(v)
|
|
case "mph":
|
|
mpsPermph := decimal{
|
|
base: uint64(MilePerHour / 1000),
|
|
exp: 3,
|
|
neg: false,
|
|
}
|
|
oz, _ := decimalMul(d, mpsPermph)
|
|
v, overflow := dtoi(oz, int(si))
|
|
if overflow {
|
|
if oz.neg {
|
|
return minValueErr(strconv.FormatInt(int64(minMilePerHour), 10) + "mph")
|
|
}
|
|
return maxValueErr(strconv.FormatInt(int64(maxMilePerHour), 10) + "mph")
|
|
}
|
|
*sp = (Speed)(v)
|
|
case "":
|
|
return noUnitErr("m/s, mps, kph, fps or mph")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "m/s", "mps", "kph", "fps", "mph"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("m/s, mps, kph, fps or mph")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Well known Speed constants.
|
|
const (
|
|
// MetrePerSecond is m/s.
|
|
NanoMetrePerSecond Speed = 1
|
|
MicroMetrePerSecond Speed = 1000 * NanoMetrePerSecond
|
|
MilliMetrePerSecond Speed = 1000 * MicroMetrePerSecond
|
|
MetrePerSecond Speed = 1000 * MilliMetrePerSecond
|
|
KiloMetrePerSecond Speed = 1000 * MetrePerSecond
|
|
MegaMetrePerSecond Speed = 1000 * KiloMetrePerSecond
|
|
GigaMetrePerSecond Speed = 1000 * MegaMetrePerSecond
|
|
|
|
LightSpeed Speed = 299792458 * MetrePerSecond
|
|
|
|
KilometrePerHour Speed = 277777778 * NanoMetrePerSecond
|
|
MilePerHour Speed = 447040 * MicroMetrePerSecond
|
|
FootPerSecond Speed = 304800 * MicroMetrePerSecond
|
|
|
|
maxSpeed Speed = (1 << 63) - 1
|
|
minSpeed Speed = -((1 << 63) - 1)
|
|
|
|
// Min Max KilometrePerHour are in kph.
|
|
minKilometrePerHour Speed = -33204139306
|
|
maxKilometrePerHour Speed = 33204139306
|
|
// Min Max MilePerHour are in mph.
|
|
minMilePerHour Speed = -20632095644
|
|
maxMilePerHour Speed = 20632095644
|
|
// Min Max FootPerSecond are in fps.
|
|
minFootPerSecond Speed = -30260406945
|
|
maxFootPerSecond Speed = 30260406945
|
|
)
|
|
|
|
// Temperature is a measurement of hotness stored as a nano kelvin.
|
|
//
|
|
// Negative values are invalid.
|
|
//
|
|
// The highest representable value is 9.2GK.
|
|
type Temperature int64
|
|
|
|
// String returns the temperature formatted as a string in °Celsius.
|
|
func (t Temperature) String() string {
|
|
if t < -ZeroCelsius || t > maxCelsius {
|
|
return nanoAsString(int64(t)) + "K"
|
|
}
|
|
return nanoAsString(int64(t-ZeroCelsius)) + "°C"
|
|
}
|
|
|
|
// Set sets the Temperature to the value represented by s. Units are to be
|
|
// provided in "C", "°C", "F", "°F" or "K" with an optional SI prefix: "p", "n",
|
|
// "u", "µ", "m", "k", "M", "G" or "T".
|
|
func (t *Temperature) Set(s string) error {
|
|
d, n, err := atod(s)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s[n:], "°C", "C", "°F", "F", "K"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("K, °C, C, °F or F")
|
|
case errOverflowsInt64:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return maxValueErr(maxTemperature.String())
|
|
case errOverflowsInt64Negative:
|
|
// TODO(maruel): Look for suffix, and reuse it.
|
|
return minValueErr(minTemperature.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
var si prefix
|
|
if n != len(s) {
|
|
r, rsize := utf8.DecodeRuneInString(s[n:])
|
|
if r <= 1 || rsize == 0 {
|
|
return errors.New("unexpected end of string")
|
|
}
|
|
var siSize int
|
|
si, siSize = parseSIPrefix(r)
|
|
n += siSize
|
|
}
|
|
switch s[n:] {
|
|
case "F", "°F":
|
|
// F to nK nK = 555555555.556*F + 255372222222
|
|
fPerK := decimal{
|
|
base: 555555555556,
|
|
exp: -3,
|
|
neg: false,
|
|
}
|
|
f, _ := decimalMul(d, fPerK)
|
|
v, overflow := dtoi(f, int(si))
|
|
if overflow {
|
|
if f.neg {
|
|
return minValueErr("-459.67F")
|
|
}
|
|
return maxValueErr(strconv.FormatInt(int64(maxFahrenheit), 10) + "F")
|
|
}
|
|
// We need an extra check here to make sure that will not overflow with
|
|
// the addition of ZeroFahrenheit.
|
|
switch {
|
|
case v > int64(maxTemperature-ZeroFahrenheit):
|
|
return maxValueErr(strconv.FormatInt(int64(maxFahrenheit), 10) + "F")
|
|
case v < int64(-ZeroFahrenheit):
|
|
return minValueErr("-459.67F")
|
|
}
|
|
v += int64(ZeroFahrenheit)
|
|
*t = (Temperature)(v)
|
|
case "K":
|
|
v, overflow := dtoi(d, int(si-nano))
|
|
if overflow {
|
|
if d.neg {
|
|
return minValueErr("0K")
|
|
}
|
|
return maxValueErr(strconv.FormatInt(int64(maxTemperature/1000000000), 10) + "K")
|
|
}
|
|
if v < 0 {
|
|
return minValueErr("0K")
|
|
}
|
|
*t = (Temperature)(v)
|
|
case "C", "°C":
|
|
v, overflow := dtoi(d, int(si-nano))
|
|
if overflow {
|
|
if d.neg {
|
|
return minValueErr("-273.15°C")
|
|
}
|
|
return maxValueErr(strconv.FormatInt(int64(maxCelsius/1000000000), 10) + "°C")
|
|
}
|
|
// We need an extra check here to make sure that will not overflow with
|
|
// the addition of ZeroCelsius.
|
|
switch {
|
|
case v > int64(maxCelsius):
|
|
return maxValueErr(strconv.FormatInt(int64(maxCelsius/1000000000), 10) + "°C")
|
|
case v < int64(-ZeroCelsius):
|
|
return minValueErr("-273.15°C")
|
|
}
|
|
v += int64(ZeroCelsius)
|
|
*t = (Temperature)(v)
|
|
case "":
|
|
return noUnitErr("K, °C, C, °F or F")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "°C", "C", "°F", "F", "K"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("K, °C, C, °F or F")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Celsius returns the temperature as a floating number of °Celsius.
|
|
func (t Temperature) Celsius() float64 {
|
|
return float64(t-ZeroCelsius) / float64(Celsius)
|
|
}
|
|
|
|
// Fahrenheit returns the temperature as a floating number of °Fahrenheit.
|
|
func (t Temperature) Fahrenheit() float64 {
|
|
return float64(t-ZeroFahrenheit) / float64(Fahrenheit)
|
|
}
|
|
|
|
// Well known Temperature constants.
|
|
const (
|
|
NanoKelvin Temperature = 1
|
|
MicroKelvin Temperature = 1000 * NanoKelvin
|
|
MilliKelvin Temperature = 1000 * MicroKelvin
|
|
Kelvin Temperature = 1000 * MilliKelvin
|
|
KiloKelvin Temperature = 1000 * Kelvin
|
|
MegaKelvin Temperature = 1000 * KiloKelvin
|
|
GigaKelvin Temperature = 1000 * MegaKelvin
|
|
|
|
// Conversion between Kelvin and Celsius.
|
|
ZeroCelsius Temperature = 273150 * MilliKelvin
|
|
MilliCelsius Temperature = MilliKelvin
|
|
Celsius Temperature = Kelvin
|
|
|
|
// Conversion between Kelvin and Fahrenheit.
|
|
ZeroFahrenheit Temperature = 255372222222 * NanoKelvin
|
|
MilliFahrenheit Temperature = 555555 * NanoKelvin
|
|
Fahrenheit Temperature = 555555555 * NanoKelvin
|
|
|
|
maxTemperature Temperature = (1 << 63) - 1
|
|
minTemperature Temperature = 0
|
|
|
|
// Maximum Celsius is 9223371763704775807°nC.
|
|
maxCelsius Temperature = maxTemperature - ZeroCelsius
|
|
|
|
// Maximum Fahrenheit is 16602069204F
|
|
maxFahrenheit Temperature = 16602069204
|
|
)
|
|
|
|
// Power is a measurement of power stored as a nano watts.
|
|
//
|
|
// The highest representable value is 9.2GW.
|
|
type Power int64
|
|
|
|
// String returns the power formatted as a string in watts.
|
|
func (p Power) String() string {
|
|
return nanoAsString(int64(p)) + "W"
|
|
}
|
|
|
|
// Set sets the Power to the value represented by s. Units are to
|
|
// be provided in "W" with an optional SI prefix: "p", "n", "u", "µ", "m", "k",
|
|
// "M", "G" or "T".
|
|
func (p *Power) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, nano)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "W", "w"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("W")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxPower.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minPower.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "W", "w":
|
|
*p = (Power)(v)
|
|
case "":
|
|
return noUnitErr("W")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "W", "w"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("W")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Well known Power constants.
|
|
const (
|
|
// Watt is unit of power J/s, kg⋅m²⋅s⁻³
|
|
NanoWatt Power = 1
|
|
MicroWatt Power = 1000 * NanoWatt
|
|
MilliWatt Power = 1000 * MicroWatt
|
|
Watt Power = 1000 * MilliWatt
|
|
KiloWatt Power = 1000 * Watt
|
|
MegaWatt Power = 1000 * KiloWatt
|
|
GigaWatt Power = 1000 * MegaWatt
|
|
|
|
maxPower = 9223372036854775807 * NanoWatt
|
|
minPower = -9223372036854775807 * NanoWatt
|
|
)
|
|
|
|
// Energy is a measurement of work stored as a nano joules.
|
|
//
|
|
// The highest representable value is 9.2GJ.
|
|
type Energy int64
|
|
|
|
// String returns the energy formatted as a string in Joules.
|
|
func (e Energy) String() string {
|
|
return nanoAsString(int64(e)) + "J"
|
|
}
|
|
|
|
// Set sets the Energy to the value represented by s. Units are to
|
|
// be provided in "J" with an optional SI prefix: "p", "n", "u", "µ", "m", "k",
|
|
// "M", "G" or "T".
|
|
func (e *Energy) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, nano)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "J", "j"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("J")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxEnergy.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minEnergy.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "J", "j":
|
|
*e = (Energy)(v)
|
|
case "":
|
|
return noUnitErr("J")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "J", "j"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("J")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Well known Energy constants.
|
|
const (
|
|
// Joule is a unit of work. kg⋅m²⋅s⁻²
|
|
NanoJoule Energy = 1
|
|
MicroJoule Energy = 1000 * NanoJoule
|
|
MilliJoule Energy = 1000 * MicroJoule
|
|
Joule Energy = 1000 * MilliJoule
|
|
KiloJoule Energy = 1000 * Joule
|
|
MegaJoule Energy = 1000 * KiloJoule
|
|
GigaJoule Energy = 1000 * MegaJoule
|
|
|
|
// BTU (British thermal unit) is the heat required to raise the temperature
|
|
// of one pound of water by one degree Fahrenheit. This is the ISO value.
|
|
BTU Energy = 1055060 * MilliJoule
|
|
|
|
WattSecond Energy = Joule
|
|
WattHour Energy = 3600 * Joule
|
|
KiloWattHour Energy = 3600 * KiloJoule
|
|
|
|
maxEnergy = 9223372036854775807 * NanoJoule
|
|
minEnergy = -9223372036854775807 * NanoJoule
|
|
)
|
|
|
|
// ElectricalCapacitance is a measurement of capacitance stored as a pico farad.
|
|
//
|
|
// The highest representable value is 9.2MF.
|
|
type ElectricalCapacitance int64
|
|
|
|
// String returns the energy formatted as a string in Farad.
|
|
func (c ElectricalCapacitance) String() string {
|
|
return picoAsString(int64(c)) + "F"
|
|
}
|
|
|
|
// Set sets the ElectricalCapacitance to the value represented by s. Units are
|
|
// to be provided in "F" with an optional SI prefix: "p", "n", "u", "µ", "m",
|
|
// "k", "M", "G" or "T".
|
|
func (c *ElectricalCapacitance) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, pico)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "F", "f"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("F")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxElectricalCapacitance.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minElectricalCapacitance.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "F", "f":
|
|
*c = (ElectricalCapacitance)(v)
|
|
case "":
|
|
return noUnitErr("F")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "F", "f"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("F")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Well known ElectricalCapacitance constants.
|
|
const (
|
|
// Farad is a unit of capacitance. kg⁻¹⋅m⁻²⋅s⁴A²
|
|
PicoFarad ElectricalCapacitance = 1
|
|
NanoFarad ElectricalCapacitance = 1000 * PicoFarad
|
|
MicroFarad ElectricalCapacitance = 1000 * NanoFarad
|
|
MilliFarad ElectricalCapacitance = 1000 * MicroFarad
|
|
Farad ElectricalCapacitance = 1000 * MilliFarad
|
|
KiloFarad ElectricalCapacitance = 1000 * Farad
|
|
MegaFarad ElectricalCapacitance = 1000 * KiloFarad
|
|
|
|
maxElectricalCapacitance = 9223372036854775807 * PicoFarad
|
|
minElectricalCapacitance = -9223372036854775807 * PicoFarad
|
|
)
|
|
|
|
// LuminousIntensity is a measurement of the quantity of visible light energy
|
|
// emitted per unit solid angle with wavelength power weighted by a luminosity
|
|
// function which represents the human eye's response to different wavelengths.
|
|
// The CIE 1931 luminosity function is the SI standard for candela.
|
|
//
|
|
// LuminousIntensity is stored as nano candela.
|
|
//
|
|
// This is one of the base unit in the International System of Units.
|
|
//
|
|
// The highest representable value is 9.2Gcd.
|
|
type LuminousIntensity int64
|
|
|
|
// String returns the energy formatted as a string in Candela.
|
|
func (i LuminousIntensity) String() string {
|
|
return nanoAsString(int64(i)) + "cd"
|
|
}
|
|
|
|
// Set sets the LuminousIntensity to the value represented by s. Units are to
|
|
// be provided in "cd" with an optional SI prefix: "p", "n", "u", "µ", "m", "k",
|
|
// "M", "G" or "T".
|
|
func (i *LuminousIntensity) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, nano)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "cd"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("cd")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxLuminousIntensity.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minLuminousIntensity.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "cd":
|
|
*i = (LuminousIntensity)(v)
|
|
case "":
|
|
return noUnitErr("cd")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "cd"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("cd")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Well known LuminousIntensity constants.
|
|
const (
|
|
// Candela is a unit of luminous intensity. cd
|
|
NanoCandela LuminousIntensity = 1
|
|
MicroCandela LuminousIntensity = 1000 * NanoCandela
|
|
MilliCandela LuminousIntensity = 1000 * MicroCandela
|
|
Candela LuminousIntensity = 1000 * MilliCandela
|
|
KiloCandela LuminousIntensity = 1000 * Candela
|
|
MegaCandela LuminousIntensity = 1000 * KiloCandela
|
|
GigaCandela LuminousIntensity = 1000 * MegaCandela
|
|
|
|
maxLuminousIntensity = 9223372036854775807 * NanoCandela
|
|
minLuminousIntensity = -9223372036854775807 * NanoCandela
|
|
)
|
|
|
|
// LuminousFlux is a measurement of total quantity of visible light energy
|
|
// emitted with wavelength power weighted by a luminosity function which
|
|
// represents a model of the human eye's response to different wavelengths.
|
|
// The CIE 1931 luminosity function is the standard for lumens.
|
|
//
|
|
// LuminousFlux is stored as nano lumens.
|
|
//
|
|
// The highest representable value is 9.2Glm.
|
|
type LuminousFlux int64
|
|
|
|
// String returns the energy formatted as a string in Lumens.
|
|
func (f LuminousFlux) String() string {
|
|
return nanoAsString(int64(f)) + "lm"
|
|
}
|
|
|
|
// Set sets the LuminousFlux to the value represented by s. Units are to
|
|
// be provided in "lm" with an optional SI prefix: "p", "n", "u", "µ", "m", "k",
|
|
// "M", "G" or "T".
|
|
func (f *LuminousFlux) Set(s string) error {
|
|
v, n, err := valueOfUnitString(s, nano)
|
|
if err != nil {
|
|
if e, ok := err.(*parseError); ok {
|
|
switch e.error {
|
|
case errNotANumber:
|
|
if found := hasSuffixes(s, "lm"); found != "" {
|
|
return err
|
|
}
|
|
return notNumberUnitErr("lm")
|
|
case errOverflowsInt64:
|
|
return maxValueErr(maxLuminousFlux.String())
|
|
case errOverflowsInt64Negative:
|
|
return minValueErr(minLuminousFlux.String())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch s[n:] {
|
|
case "lm":
|
|
*f = (LuminousFlux)(v)
|
|
case "":
|
|
return noUnitErr("lm")
|
|
default:
|
|
if found := hasSuffixes(s[n:], "lm"); found != "" {
|
|
return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
|
|
}
|
|
return incorrectUnitErr("lm")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Well known LuminousFlux constants.
|
|
const (
|
|
// Lumen is a unit of luminous flux. cd⋅sr
|
|
NanoLumen LuminousFlux = 1
|
|
MicroLumen LuminousFlux = 1000 * NanoLumen
|
|
MilliLumen LuminousFlux = 1000 * MicroLumen
|
|
Lumen LuminousFlux = 1000 * MilliLumen
|
|
KiloLumen LuminousFlux = 1000 * Lumen
|
|
MegaLumen LuminousFlux = 1000 * KiloLumen
|
|
GigaLumen LuminousFlux = 1000 * MegaLumen
|
|
|
|
maxLuminousFlux = 9223372036854775807 * NanoLumen
|
|
minLuminousFlux = -9223372036854775807 * NanoLumen
|
|
)
|
|
|
|
//
|
|
|
|
func prefixZeros(digits, v int) string {
|
|
// digits is expected to be around 2~3.
|
|
s := strconv.Itoa(v)
|
|
for len(s) < digits {
|
|
// O(n²) but since digits is expected to run 2~3 times at most, it doesn't
|
|
// matter.
|
|
s = "0" + s
|
|
}
|
|
return s
|
|
}
|
|
|
|
// nanoAsString converts a value in S.I. unit in a string with the predefined
|
|
// prefix.
|
|
func nanoAsString(v int64) string {
|
|
sign := ""
|
|
if v < 0 {
|
|
if v == -9223372036854775808 {
|
|
v++
|
|
}
|
|
sign = "-"
|
|
v = -v
|
|
}
|
|
var frac int
|
|
var base int
|
|
var precision int64
|
|
unit := ""
|
|
switch {
|
|
case v >= 999999500000000001:
|
|
precision = v % 1000000000000000
|
|
base = int(v / 1000000000000000)
|
|
if precision > 500000000000000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "G"
|
|
case v >= 999999500000001:
|
|
precision = v % 1000000000000
|
|
base = int(v / 1000000000000)
|
|
if precision > 500000000000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "M"
|
|
case v >= 999999500001:
|
|
precision = v % 1000000000
|
|
base = int(v / 1000000000)
|
|
if precision > 500000000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "k"
|
|
case v >= 999999501:
|
|
precision = v % 1000000
|
|
base = int(v / 1000000)
|
|
if precision > 500000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = ""
|
|
case v >= 1000000:
|
|
precision = v % 1000
|
|
base = int(v / 1000)
|
|
if precision > 500 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "m"
|
|
case v >= 1000:
|
|
frac = int(v) % 1000
|
|
base = int(v) / 1000
|
|
unit = "µ"
|
|
default:
|
|
if v == 0 {
|
|
return "0"
|
|
}
|
|
base = int(v)
|
|
unit = "n"
|
|
}
|
|
if frac == 0 {
|
|
return sign + strconv.Itoa(base) + unit
|
|
}
|
|
return sign + strconv.Itoa(base) + "." + prefixZeros(3, frac) + unit
|
|
}
|
|
|
|
// microAsString converts a value in S.I. unit in a string with the predefined
|
|
// prefix.
|
|
func microAsString(v int64) string {
|
|
sign := ""
|
|
if v < 0 {
|
|
if v == -9223372036854775808 {
|
|
v++
|
|
}
|
|
sign = "-"
|
|
v = -v
|
|
}
|
|
var frac int
|
|
var base int
|
|
var precision int64
|
|
unit := ""
|
|
switch {
|
|
case v >= 999999500000000001:
|
|
precision = v % 1000000000000000
|
|
base = int(v / 1000000000000000)
|
|
if precision > 500000000000000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "T"
|
|
case v >= 999999500000001:
|
|
precision = v % 1000000000000
|
|
base = int(v / 1000000000000)
|
|
if precision > 500000000000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "G"
|
|
case v >= 999999500001:
|
|
precision = v % 1000000000
|
|
base = int(v / 1000000000)
|
|
if precision > 500000000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "M"
|
|
case v >= 999999501:
|
|
precision = v % 1000000
|
|
base = int(v / 1000000)
|
|
if precision > 500000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "k"
|
|
case v >= 1000000:
|
|
precision = v % 1000
|
|
base = int(v / 1000)
|
|
if precision > 500 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = ""
|
|
case v >= 1000:
|
|
frac = int(v) % 1000
|
|
base = int(v) / 1000
|
|
unit = "m"
|
|
default:
|
|
if v == 0 {
|
|
return "0"
|
|
}
|
|
base = int(v)
|
|
unit = "µ"
|
|
}
|
|
if frac == 0 {
|
|
return sign + strconv.Itoa(base) + unit
|
|
}
|
|
return sign + strconv.Itoa(base) + "." + prefixZeros(3, frac) + unit
|
|
}
|
|
|
|
// picoAsString converts a value in S.I. unit in a string with the predefined
|
|
// prefix.
|
|
func picoAsString(v int64) string {
|
|
sign := ""
|
|
if v < 0 {
|
|
if v == -9223372036854775808 {
|
|
v++
|
|
}
|
|
sign = "-"
|
|
v = -v
|
|
}
|
|
var frac int
|
|
var base int
|
|
var precision int64
|
|
unit := ""
|
|
switch {
|
|
case v >= 999999500000000001:
|
|
precision = v % 1000000000000000
|
|
base = int(v / 1000000000000000)
|
|
if precision > 500000000000000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "M"
|
|
case v >= 999999500000001:
|
|
precision = v % 1000000000000
|
|
base = int(v / 1000000000000)
|
|
if precision > 500000000000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "k"
|
|
case v >= 999999500001:
|
|
precision = v % 1000000000
|
|
base = int(v / 1000000000)
|
|
if precision > 500000000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = ""
|
|
case v >= 999999501:
|
|
precision = v % 1000000
|
|
base = int(v / 1000000)
|
|
if precision > 500000 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "m"
|
|
case v >= 1000000:
|
|
precision = v % 1000
|
|
base = int(v / 1000)
|
|
if precision > 500 {
|
|
base++
|
|
}
|
|
frac = (base % 1000)
|
|
base = base / 1000
|
|
unit = "µ"
|
|
case v >= 1000:
|
|
frac = int(v) % 1000
|
|
base = int(v) / 1000
|
|
unit = "n"
|
|
default:
|
|
if v == 0 {
|
|
return "0"
|
|
}
|
|
base = int(v)
|
|
unit = "p"
|
|
}
|
|
if frac == 0 {
|
|
return sign + strconv.Itoa(base) + unit
|
|
}
|
|
return sign + strconv.Itoa(base) + "." + prefixZeros(3, frac) + unit
|
|
}
|
|
|
|
// Decimal is the representation of decimal number.
|
|
type decimal struct {
|
|
// base hold the significant digits.
|
|
base uint64
|
|
// exponent is the left or right decimal shift. (powers of ten).
|
|
exp int
|
|
// neg it true if the number is negative.
|
|
neg bool
|
|
}
|
|
|
|
// Positive powers of 10 in the form such that powerOF10[index] = 10^index.
|
|
var powerOf10 = [...]uint64{
|
|
1,
|
|
10,
|
|
100,
|
|
1000,
|
|
10000,
|
|
100000,
|
|
1000000,
|
|
10000000,
|
|
100000000,
|
|
1000000000,
|
|
10000000000,
|
|
100000000000,
|
|
1000000000000,
|
|
10000000000000,
|
|
100000000000000,
|
|
1000000000000000,
|
|
10000000000000000,
|
|
100000000000000000,
|
|
1000000000000000000,
|
|
}
|
|
|
|
// Maximum value for a int64.
|
|
const maxInt64 = (1 << 63) - 1
|
|
|
|
var maxInt64Str = "9223372036854775807"
|
|
|
|
var (
|
|
errOverflowsInt64 = errors.New("exceeds maximum")
|
|
errOverflowsInt64Negative = errors.New("exceeds minimum")
|
|
errNotANumber = errors.New("not a number")
|
|
)
|
|
|
|
// Converts from decimal to int64.
|
|
//
|
|
// Scale is combined with the decimal exponent to maximise the resolution and is
|
|
// in powers of ten.
|
|
//
|
|
// Returns true if the value overflowed.
|
|
func dtoi(d decimal, scale int) (int64, bool) {
|
|
// Get the total magnitude of the number.
|
|
// a^x * b^y = a*b^(x+y) since scale is of the order unity this becomes
|
|
// 1^x * b^y = b^(x+y).
|
|
// mag must be positive to use as index in to powerOf10 array.
|
|
u := d.base
|
|
mag := d.exp + scale
|
|
if mag < 0 {
|
|
mag = -mag
|
|
}
|
|
var n int64
|
|
if mag > 18 {
|
|
return 0, true
|
|
}
|
|
// Divide is = 10^(-mag)
|
|
switch {
|
|
case d.exp+scale < 0:
|
|
u = (u + powerOf10[mag]/2) / powerOf10[mag]
|
|
break
|
|
case mag == 0:
|
|
if u > maxInt64 {
|
|
return 0, true
|
|
}
|
|
break
|
|
default:
|
|
check := u * powerOf10[mag]
|
|
if check/powerOf10[mag] != u || check > maxInt64 {
|
|
return 0, true
|
|
}
|
|
u *= powerOf10[mag]
|
|
}
|
|
|
|
n = int64(u)
|
|
if d.neg {
|
|
n = -n
|
|
}
|
|
return n, false
|
|
}
|
|
|
|
// Converts a string to a decimal form. The return int is how many bytes of the
|
|
// string are considered numeric. The string may contain +-0 prefixes and
|
|
// arbitrary suffixes as trailing non number characters are ignored.
|
|
// Significant digits are stored without leading or trailing zeros, rather a
|
|
// base and exponent is used. Significant digits are stored as uint64, max size
|
|
// of significant digits is int64
|
|
func atod(s string) (decimal, int, error) {
|
|
var d decimal
|
|
start := 0
|
|
dp := 0
|
|
end := len(s)
|
|
seenDigit := false
|
|
seenZero := false
|
|
isPoint := false
|
|
seenPlus := false
|
|
|
|
// Strip leading zeros, +/- and mark DP.
|
|
for i := 0; i < len(s); i++ {
|
|
switch {
|
|
case s[i] == '-':
|
|
if seenDigit {
|
|
end = i
|
|
break
|
|
}
|
|
if seenPlus {
|
|
return decimal{}, 0, &parseError{
|
|
errors.New("contains both plus and minus symbols"),
|
|
}
|
|
}
|
|
if d.neg {
|
|
return decimal{}, 0, &parseError{
|
|
errors.New("contains multiple minus symbols"),
|
|
}
|
|
}
|
|
d.neg = true
|
|
start++
|
|
case s[i] == '+':
|
|
if seenDigit {
|
|
end = i
|
|
break
|
|
}
|
|
if d.neg {
|
|
return decimal{}, 0, &parseError{
|
|
errors.New("contains both plus and minus symbols"),
|
|
}
|
|
}
|
|
if seenPlus {
|
|
return decimal{}, 0, &parseError{
|
|
errors.New("contains multiple plus symbols"),
|
|
}
|
|
}
|
|
seenPlus = true
|
|
start++
|
|
case s[i] == '.':
|
|
if isPoint {
|
|
return decimal{}, 0, &parseError{
|
|
errors.New("contains multiple decimal points"),
|
|
}
|
|
}
|
|
isPoint = true
|
|
dp = i
|
|
if !seenDigit {
|
|
start++
|
|
}
|
|
case s[i] == '0':
|
|
if !seenDigit {
|
|
start++
|
|
}
|
|
seenZero = true
|
|
case s[i] >= '1' && s[i] <= '9':
|
|
seenDigit = true
|
|
default:
|
|
if !seenDigit && !seenZero {
|
|
return decimal{}, 0, &parseError{errNotANumber}
|
|
}
|
|
end = i
|
|
break
|
|
}
|
|
}
|
|
|
|
last := end
|
|
seenDigit = false
|
|
exp := 0
|
|
// Strip non significant zeros to find base exponent.
|
|
for i := end - 1; i > start-1; i-- {
|
|
switch {
|
|
case s[i] >= '1' && s[i] <= '9':
|
|
seenDigit = true
|
|
case s[i] == '.':
|
|
if !seenDigit {
|
|
end--
|
|
}
|
|
case s[i] == '0':
|
|
if !seenDigit {
|
|
if i > dp {
|
|
end--
|
|
}
|
|
if i <= dp || dp == 0 {
|
|
exp++
|
|
}
|
|
}
|
|
default:
|
|
last--
|
|
end--
|
|
}
|
|
}
|
|
|
|
for i := start; i < end; i++ {
|
|
c := s[i]
|
|
// Check that is is a digit.
|
|
if c >= '0' && c <= '9' {
|
|
// *10 is decimal shift left.
|
|
d.base *= 10
|
|
// Convert ascii digit into number.
|
|
check := d.base + uint64(c-'0')
|
|
// Check should always be larger than u unless we have overflowed.
|
|
// Similarly if check > max it will overflow when converted to int64.
|
|
if check < d.base || check > maxInt64 {
|
|
if d.neg {
|
|
return decimal{}, 0, &parseError{errOverflowsInt64Negative}
|
|
}
|
|
return decimal{}, 0, &parseError{errOverflowsInt64}
|
|
}
|
|
d.base = check
|
|
} else if c != '.' {
|
|
return decimal{}, 0, &parseError{errNotANumber}
|
|
}
|
|
}
|
|
if !isPoint {
|
|
d.exp = exp
|
|
} else {
|
|
if dp > start && dp < end {
|
|
// Decimal Point is in the middle of a number.
|
|
end--
|
|
}
|
|
// Find the exponent based on decimal point distance from left and the
|
|
// length of the number.
|
|
d.exp = (dp - start) - (end - start)
|
|
if dp <= start {
|
|
// Account for numbers of the form 1 > n < -1 eg 0.0001.
|
|
d.exp++
|
|
}
|
|
}
|
|
return d, last, nil
|
|
}
|
|
|
|
// valueOfUnitString is a helper for converting a string and a prefix in to a
|
|
// physic unit. It can be used when characters of the units do not conflict with
|
|
// any of the SI prefixes.
|
|
func valueOfUnitString(s string, base prefix) (int64, int, error) {
|
|
d, n, err := atod(s)
|
|
if err != nil {
|
|
return 0, n, err
|
|
}
|
|
si := prefix(unit)
|
|
if n != len(s) {
|
|
r, rsize := utf8.DecodeRuneInString(s[n:])
|
|
if r <= 1 || rsize == 0 {
|
|
return 0, 0, &parseError{
|
|
errors.New("unexpected end of string"),
|
|
}
|
|
}
|
|
var siSize int
|
|
si, siSize = parseSIPrefix(r)
|
|
n += siSize
|
|
}
|
|
v, overflow := dtoi(d, int(si-base))
|
|
if overflow {
|
|
if d.neg {
|
|
return -maxInt64, 0, &parseError{errOverflowsInt64Negative}
|
|
}
|
|
return maxInt64, 0, &parseError{errOverflowsInt64}
|
|
}
|
|
return v, n, nil
|
|
}
|
|
|
|
// decimalMul calcululates the product of two decimals; a and b, keeping the
|
|
// base less than maxInt64. Returns the number of times a figure was trimmed
|
|
// from either base coefficients. This function is to aid in the multiplication
|
|
// of numbers whose product have more than 18 significant figures. The minimum
|
|
// accuracy of the end product that has been truncated is 9 significant figures.
|
|
func decimalMul(a, b decimal) (decimal, uint) {
|
|
switch {
|
|
case a.base == 0 || b.base == 0:
|
|
// Anything multiplied by zero is zero. Special case to set exponent to
|
|
// zero.
|
|
return decimal{}, 0
|
|
case a.base > (1<<64)-6 || b.base > (1<<64)-6:
|
|
// In normal usage base will never be greater than 1<<63. However since
|
|
// base could be large as (1<<64 -1) this is to prevent an infinite loop
|
|
// when ((1<<64)-6)+5 overflows in the truncate least significant digit
|
|
// loop during rounding without adding addition bounds checking at that
|
|
// point.
|
|
break
|
|
default:
|
|
exp := a.exp + b.exp
|
|
neg := a.neg != b.neg
|
|
ab := a.base
|
|
bb := b.base
|
|
for i := uint(0); i < 21; i++ {
|
|
if ab <= 1 || bb <= 1 {
|
|
// This will always fit inside uint64.
|
|
return decimal{ab * bb, exp, neg}, i
|
|
}
|
|
if base := ab * bb; (base/ab == bb) && base < maxInt64 {
|
|
// Return if product did not overflow or exceed int64.
|
|
return decimal{base, exp, neg}, i
|
|
}
|
|
// Truncate least significant digit in product.
|
|
if bb > ab {
|
|
bb = (bb + 5) / 10
|
|
// Compact trailing zeros if any.
|
|
for bb > 0 && bb%10 == 0 {
|
|
bb /= 10
|
|
exp++
|
|
}
|
|
} else {
|
|
ab = (ab + 5) / 10
|
|
// Compact trailing zeros if any.
|
|
for ab > 0 && ab%10 == 0 {
|
|
ab /= 10
|
|
exp++
|
|
}
|
|
}
|
|
exp++
|
|
}
|
|
}
|
|
return decimal{}, 21
|
|
}
|
|
|
|
// hasSuffixes returns the first suffix found and the prefix content.
|
|
func hasSuffixes(s string, suffixes ...string) string {
|
|
for _, suffix := range suffixes {
|
|
if strings.HasSuffix(s, suffix) {
|
|
return suffix
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type parseError struct {
|
|
error
|
|
}
|
|
|
|
func noUnitErr(valid string) error {
|
|
return errors.New("no unit provided; need " + valid)
|
|
}
|
|
|
|
func incorrectUnitErr(valid string) error {
|
|
return errors.New("unknown unit provided; need " + valid)
|
|
}
|
|
|
|
func unknownUnitPrefixErr(unit, valid string) error {
|
|
return errors.New("unknown unit prefix; valid prefixes for \"" + unit + "\" are " + valid)
|
|
}
|
|
|
|
func maxValueErr(valid string) error {
|
|
return errors.New("maximum value is " + valid)
|
|
}
|
|
|
|
func minValueErr(valid string) error {
|
|
return errors.New("minimum value is " + valid)
|
|
}
|
|
|
|
func notNumberUnitErr(unit string) error {
|
|
return errors.New("does not contain number or unit " + unit)
|
|
}
|
|
|
|
type prefix int
|
|
|
|
const (
|
|
pico prefix = -12
|
|
nano prefix = -9
|
|
micro prefix = -6
|
|
milli prefix = -3
|
|
unit prefix = 0
|
|
deca prefix = 1
|
|
hecto prefix = 2
|
|
kilo prefix = 3
|
|
mega prefix = 6
|
|
giga prefix = 9
|
|
tera prefix = 12
|
|
)
|
|
|
|
func parseSIPrefix(r rune) (prefix, int) {
|
|
switch r {
|
|
case 'p':
|
|
return pico, len("p")
|
|
case 'n':
|
|
return nano, len("n")
|
|
case 'u':
|
|
return micro, len("u")
|
|
case 'µ':
|
|
return micro, len("µ")
|
|
case 'm':
|
|
return milli, len("m")
|
|
case 'k':
|
|
return kilo, len("k")
|
|
case 'M':
|
|
return mega, len("M")
|
|
case 'G':
|
|
return giga, len("G")
|
|
case 'T':
|
|
return tera, len("T")
|
|
default:
|
|
return unit, 0
|
|
}
|
|
}
|