// 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
)

// MagneticFluxDensity is a measurement of magnetic flux density, stored in Tesla.
//
// The highest representable value is 9.2GT.
type MagneticFluxDensity int64

// String returns the energy formatted as a string in Farad.
func (c MagneticFluxDensity) String() string {
	return nanoAsString(int64(c)) + "T"
}

// Set sets the MagneticFluxDensity to the value represented by s. Units are
// to be provided in "T" with an optional SI prefix: "p", "n", "u", "µ", "m",
// "k", "M", "G" or "T".
func (c *MagneticFluxDensity) 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, "T", "t"); found != "" {
					return err
				}
				return notNumberUnitErr("T")
			case errOverflowsInt64:
				return maxValueErr(maxMagneticFluxDensity.String())
			case errOverflowsInt64Negative:
				return minValueErr(minMagneticFluxDensity.String())
			}
		}
		return err
	}

	switch s[n:] {
	case "T", "t":
		*c = (MagneticFluxDensity)(v)
	case "":
		return noUnitErr("T")
	default:
		if found := hasSuffixes(s[n:], "T", "t"); found != "" {
			return unknownUnitPrefixErr(found, "p,n,u,µ,m,k,M,G or T")
		}
		return incorrectUnitErr("T")
	}

	return nil
}

// Well known MagneticFluxDensity constants.
const (
	// Tesla is a unit of magnetic flux density.
	NanoTesla  MagneticFluxDensity = 1
	MicroTesla MagneticFluxDensity = 1000 * NanoTesla
	MilliTesla MagneticFluxDensity = 1000 * MicroTesla
	Tesla      MagneticFluxDensity = 1000 * MilliTesla
	KiloTesla  MagneticFluxDensity = 1000 * Tesla
	MegaTesla  MagneticFluxDensity = 1000 * KiloTesla
	GigaTesla  MagneticFluxDensity = 1000 * MegaTesla

	maxMagneticFluxDensity = 9223372036854775807 * NanoTesla
	minMagneticFluxDensity = -9223372036854775807 * NanoTesla
)

//

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]
	case mag == 0:
		if u > maxInt64 {
			return 0, true
		}
	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
		}
	}

	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
	}
}
