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) { func TestGoStringFormat(t *testing.T) {
expect1 := `&unit.Unit{dimensions:unit.Dimensions{4:2, 7:-1}, 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}, formatted:"", 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 { 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) t.Errorf("Format %q: got: %q expected: %q", "%#v", r, expect1)
} }

View File

@@ -8,6 +8,8 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"sort" "sort"
"sync"
"unicode/utf8"
) )
// Uniter is a type that can be converted to a Unit. // Uniter is a type that can be converted to a Unit.
@@ -20,16 +22,48 @@ type Uniter interface {
// function, typically within an init function. // function, typically within an init function.
type Dimension int 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. // String returns the string for the dimension.
func (d Dimension) String() string { func (d Dimension) String() string {
switch { if d == reserved {
case d == reserved:
return "reserved" return "reserved"
case d < Dimension(len(symbols)): }
defer mu.RUnlock()
mu.RLock()
if int(d) < len(symbols) {
return symbols[d] return symbols[d]
default: }
panic("unit: illegal dimension") 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 ( const (
@@ -47,6 +81,8 @@ const (
) )
var ( var (
// mu protects symbols and dimensions for concurrent use.
mu sync.RWMutex
symbols = []string{ symbols = []string{
CurrentDim: "A", CurrentDim: "A",
LengthDim: "m", LengthDim: "m",
@@ -178,33 +214,6 @@ func (u unitPrinters) Swap(i, j int) {
u[i], u[j] = u[j], u[i] 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 // Unit represents a dimensional value. The dimensions will typically be in SI
// units, but can also include dimensions created with NewDimension. The Unit type // units, but can also include dimensions created with NewDimension. The Unit type
// is most useful for ensuring dimensional consistency when manipulating types // 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. // mass to get a force. See the package documentation for further explanation.
type Unit struct { type Unit struct {
dimensions Dimensions dimensions Dimensions
formatted string
value float64 value float64
} }
@@ -274,7 +282,6 @@ func (u *Unit) Mul(uniter Uniter) *Unit {
u.dimensions[key] = d + val u.dimensions[key] = d + val
} }
} }
u.formatted = ""
u.value *= a.value u.value *= a.value
return u return u
} }
@@ -291,7 +298,6 @@ func (u *Unit) Div(uniter Uniter) *Unit {
u.dimensions[key] = d - val u.dimensions[key] = d - val
} }
} }
u.formatted = ""
return u return u
} }
@@ -322,20 +328,18 @@ func (u *Unit) Format(fs fmt.State, c rune) {
case 'e', 'E', 'f', 'F', 'g', 'G': case 'e', 'E', 'f', 'F', 'g', 'G':
p, pOk := fs.Precision() p, pOk := fs.Precision()
w, wOk := fs.Width() w, wOk := fs.Width()
if u.formatted == "" && len(u.dimensions) > 0 { units := u.dimensions.String()
u.formatted = u.dimensions.String()
}
switch { switch {
case pOk && wOk: 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: case pOk:
fmt.Fprintf(fs, "%.*"+string(c), p, u.value) fmt.Fprintf(fs, "%.*"+string(c), p, u.value)
case wOk: 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: default:
fmt.Fprintf(fs, "%"+string(c), u.value) fmt.Fprintf(fs, "%"+string(c), u.value)
} }
fmt.Fprintf(fs, " %s", u.formatted) fmt.Fprintf(fs, " %s", units)
default: default:
fmt.Fprintf(fs, "%%!%c(*Unit=%g)", c, u) fmt.Fprintf(fs, "%%!%c(*Unit=%g)", c, u)
} }