unit: make printing of units concurrency-safe and unicode-aware

This commit is contained in:
Dan Kortschak
2019-03-20 08:15:03 +10:30
committed by GitHub
parent f1f6e6e7bc
commit ce747f8fb7
2 changed files with 48 additions and 44 deletions

View File

@@ -103,8 +103,8 @@ func TestFormat(t *testing.T) {
}
func TestGoStringFormat(t *testing.T) {
expect1 := `&unit.Unit{dimensions:unit.Dimensions{4:2, 7:-1}, formatted:"", value:6.62606957e-34}`
expect2 := `&unit.Unit{dimensions:unit.Dimensions{7:-1, 4:2}, formatted:"", value:6.62606957e-34}`
expect1 := `&unit.Unit{dimensions:unit.Dimensions{4:2, 7:-1}, value:6.62606957e-34}`
expect2 := `&unit.Unit{dimensions:unit.Dimensions{7:-1, 4:2}, value:6.62606957e-34}`
if r := fmt.Sprintf("%#v", New(6.62606957e-34, Dimensions{MassDim: 2, TimeDim: -1})); r != expect1 && r != expect2 {
t.Errorf("Format %q: got: %q expected: %q", "%#v", r, expect1)
}

View File

@@ -8,6 +8,8 @@ import (
"bytes"
"fmt"
"sort"
"sync"
"unicode/utf8"
)
// Uniter is a type that can be converted to a Unit.
@@ -20,16 +22,48 @@ type Uniter interface {
// function, typically within an init function.
type Dimension int
// NewDimension creates a new orthogonal dimension with the given symbol, and
// returns the value of that dimension. The input symbol must not overlap with
// any of the any of the SI base units or other symbols of common use in SI ("kg",
// "J", etc.), and must not overlap with any other dimensions created by calls
// to NewDimension. The SymbolExists function can check if the symbol exists.
// NewDimension will panic if the input symbol matches an existing symbol.
//
// NewDimension should only be called for unit types that are actually orthogonal
// to the base dimensions defined in this package. See the package-level
// documentation for further explanation.
func NewDimension(symbol string) Dimension {
defer mu.Unlock()
mu.Lock()
_, ok := dimensions[symbol]
if ok {
panic("unit: dimension string \"" + symbol + "\" already used")
}
d := Dimension(len(symbols))
symbols = append(symbols, symbol)
dimensions[symbol] = d
return d
}
// String returns the string for the dimension.
func (d Dimension) String() string {
switch {
case d == reserved:
if d == reserved {
return "reserved"
case d < Dimension(len(symbols)):
return symbols[d]
default:
panic("unit: illegal dimension")
}
defer mu.RUnlock()
mu.RLock()
if int(d) < len(symbols) {
return symbols[d]
}
panic("unit: illegal dimension")
}
// SymbolExists returns whether the given symbol is already in use.
func SymbolExists(symbol string) bool {
mu.RLock()
_, ok := dimensions[symbol]
mu.RUnlock()
return ok
}
const (
@@ -47,6 +81,8 @@ const (
)
var (
// mu protects symbols and dimensions for concurrent use.
mu sync.RWMutex
symbols = []string{
CurrentDim: "A",
LengthDim: "m",
@@ -178,33 +214,6 @@ func (u unitPrinters) Swap(i, j int) {
u[i], u[j] = u[j], u[i]
}
// NewDimension creates a new orthogonal dimension with the given symbol, and
// returns the value of that dimension. The input symbol must not overlap with
// any of the any of the SI base units or other symbols of common use in SI ("kg",
// "J", etc.), and must not overlap with any other dimensions created by calls
// to NewDimension. The SymbolExists function can check if the symbol exists.
// NewDimension will panic if the input symbol matches an existing symbol.
//
// NewDimension should only be called for unit types that are actually orthogonal
// to the base dimensions defined in this package. See the package-level
// documentation for further explanation.
func NewDimension(symbol string) Dimension {
_, ok := dimensions[symbol]
if ok {
panic("unit: dimension string \"" + symbol + "\" already used")
}
d := Dimension(len(symbols))
symbols = append(symbols, symbol)
dimensions[symbol] = d
return d
}
// SymoblExists returns whether the given symbol is already in use.
func SymbolExists(symbol string) bool {
_, ok := dimensions[symbol]
return ok
}
// Unit represents a dimensional value. The dimensions will typically be in SI
// units, but can also include dimensions created with NewDimension. The Unit type
// is most useful for ensuring dimensional consistency when manipulating types
@@ -212,7 +221,6 @@ func SymbolExists(symbol string) bool {
// mass to get a force. See the package documentation for further explanation.
type Unit struct {
dimensions Dimensions
formatted string
value float64
}
@@ -274,7 +282,6 @@ func (u *Unit) Mul(uniter Uniter) *Unit {
u.dimensions[key] = d + val
}
}
u.formatted = ""
u.value *= a.value
return u
}
@@ -291,7 +298,6 @@ func (u *Unit) Div(uniter Uniter) *Unit {
u.dimensions[key] = d - val
}
}
u.formatted = ""
return u
}
@@ -322,20 +328,18 @@ func (u *Unit) Format(fs fmt.State, c rune) {
case 'e', 'E', 'f', 'F', 'g', 'G':
p, pOk := fs.Precision()
w, wOk := fs.Width()
if u.formatted == "" && len(u.dimensions) > 0 {
u.formatted = u.dimensions.String()
}
units := u.dimensions.String()
switch {
case pOk && wOk:
fmt.Fprintf(fs, "%*.*"+string(c), pos(w-len(u.formatted)-1), p, u.value)
fmt.Fprintf(fs, "%*.*"+string(c), pos(w-utf8.RuneCount([]byte(units))-1), p, u.value)
case pOk:
fmt.Fprintf(fs, "%.*"+string(c), p, u.value)
case wOk:
fmt.Fprintf(fs, "%*"+string(c), pos(w-len(u.formatted)-1), u.value)
fmt.Fprintf(fs, "%*"+string(c), pos(w-utf8.RuneCount([]byte(units))-1), u.value)
default:
fmt.Fprintf(fs, "%"+string(c), u.value)
}
fmt.Fprintf(fs, " %s", u.formatted)
fmt.Fprintf(fs, " %s", units)
default:
fmt.Fprintf(fs, "%%!%c(*Unit=%g)", c, u)
}