gonum.org/v1/gonum@v0.14.0/unit/unittype.go (about) 1 // Copyright ©2013 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package unit 6 7 import ( 8 "bytes" 9 "fmt" 10 "sort" 11 "sync" 12 "unicode/utf8" 13 ) 14 15 // Uniter is a type that can be converted to a Unit. 16 type Uniter interface { 17 Unit() *Unit 18 } 19 20 // Dimension is a type representing an SI base dimension or a distinct 21 // orthogonal dimension. Non-SI dimensions can be created using the NewDimension 22 // function, typically within an init function. 23 type Dimension int 24 25 // NewDimension creates a new orthogonal dimension with the given symbol, and 26 // returns the value of that dimension. The input symbol must not overlap with 27 // any of the any of the SI base units or other symbols of common use in SI ("kg", 28 // "J", etc.), and must not overlap with any other dimensions created by calls 29 // to NewDimension. The SymbolExists function can check if the symbol exists. 30 // NewDimension will panic if the input symbol matches an existing symbol. 31 // 32 // NewDimension should only be called for unit types that are actually orthogonal 33 // to the base dimensions defined in this package. See the package-level 34 // documentation for further explanation. 35 func NewDimension(symbol string) Dimension { 36 defer mu.Unlock() 37 mu.Lock() 38 _, ok := dimensions[symbol] 39 if ok { 40 panic("unit: dimension string \"" + symbol + "\" already used") 41 } 42 d := Dimension(len(symbols)) 43 symbols = append(symbols, symbol) 44 dimensions[symbol] = d 45 return d 46 } 47 48 // String returns the string for the dimension. 49 func (d Dimension) String() string { 50 if d == reserved { 51 return "reserved" 52 } 53 defer mu.RUnlock() 54 mu.RLock() 55 if int(d) < len(symbols) { 56 return symbols[d] 57 } 58 panic("unit: illegal dimension") 59 } 60 61 // SymbolExists returns whether the given symbol is already in use. 62 func SymbolExists(symbol string) bool { 63 mu.RLock() 64 _, ok := dimensions[symbol] 65 mu.RUnlock() 66 return ok 67 } 68 69 const ( 70 // SI Base Units 71 reserved Dimension = iota 72 CurrentDim 73 LengthDim 74 LuminousIntensityDim 75 MassDim 76 MoleDim 77 TemperatureDim 78 TimeDim 79 // Other common SI Dimensions 80 AngleDim // e.g. radians 81 ) 82 83 var ( 84 // mu protects symbols and dimensions for concurrent use. 85 mu sync.RWMutex 86 symbols = []string{ 87 CurrentDim: "A", 88 LengthDim: "m", 89 LuminousIntensityDim: "cd", 90 MassDim: "kg", 91 MoleDim: "mol", 92 TemperatureDim: "K", 93 TimeDim: "s", 94 AngleDim: "rad", 95 } 96 97 // dimensions guarantees there aren't two identical symbols 98 // SI symbol list from http://lamar.colostate.edu/~hillger/basic.htm 99 dimensions = map[string]Dimension{ 100 "A": CurrentDim, 101 "m": LengthDim, 102 "cd": LuminousIntensityDim, 103 "kg": MassDim, 104 "mol": MoleDim, 105 "K": TemperatureDim, 106 "s": TimeDim, 107 "rad": AngleDim, 108 109 // Reserve common SI symbols 110 // prefixes 111 "Y": reserved, 112 "Z": reserved, 113 "E": reserved, 114 "P": reserved, 115 "T": reserved, 116 "G": reserved, 117 "M": reserved, 118 "k": reserved, 119 "h": reserved, 120 "da": reserved, 121 "d": reserved, 122 "c": reserved, 123 "μ": reserved, 124 "n": reserved, 125 "p": reserved, 126 "f": reserved, 127 "a": reserved, 128 "z": reserved, 129 "y": reserved, 130 // SI Derived units with special symbols 131 "sr": reserved, 132 "F": reserved, 133 "C": reserved, 134 "S": reserved, 135 "H": reserved, 136 "V": reserved, 137 "Ω": reserved, 138 "J": reserved, 139 "N": reserved, 140 "Hz": reserved, 141 "lx": reserved, 142 "lm": reserved, 143 "Wb": reserved, 144 "W": reserved, 145 "Pa": reserved, 146 "Bq": reserved, 147 "Gy": reserved, 148 "Sv": reserved, 149 "kat": reserved, 150 // Units in use with SI 151 "ha": reserved, 152 "L": reserved, 153 "l": reserved, 154 // Units in Use Temporarily with SI 155 "bar": reserved, 156 "b": reserved, 157 "Ci": reserved, 158 "R": reserved, 159 "rd": reserved, 160 "rem": reserved, 161 } 162 ) 163 164 // Dimensions represent the dimensionality of the unit in powers 165 // of that dimension. If a key is not present, the power of that 166 // dimension is zero. Dimensions is used in conjunction with New. 167 type Dimensions map[Dimension]int 168 169 func (d Dimensions) clone() Dimensions { 170 if d == nil { 171 return nil 172 } 173 c := make(Dimensions, len(d)) 174 for dim, pow := range d { 175 if pow != 0 { 176 c[dim] = pow 177 } 178 } 179 return c 180 } 181 182 // matches reports whether the dimensions of d and o match. Zero power 183 // dimensions in d an o must be removed, otherwise matches may incorrectly 184 // report a mismatch. 185 func (d Dimensions) matches(o Dimensions) bool { 186 if len(d) != len(o) { 187 return false 188 } 189 for dim, pow := range d { 190 if o[dim] != pow { 191 return false 192 } 193 } 194 return true 195 } 196 197 func (d Dimensions) String() string { 198 // Map iterates randomly, but print should be in a fixed order. Can't use 199 // dimension number, because for user-defined dimension that number may 200 // not be fixed from run to run. 201 atoms := make(unitPrinters, 0, len(d)) 202 for dimension, power := range d { 203 if power != 0 { 204 atoms = append(atoms, atom{dimension, power}) 205 } 206 } 207 sort.Sort(atoms) 208 var b bytes.Buffer 209 for i, a := range atoms { 210 if i > 0 { 211 b.WriteByte(' ') 212 } 213 fmt.Fprintf(&b, "%s", a.Dimension) 214 if a.pow != 1 { 215 fmt.Fprintf(&b, "^%d", a.pow) 216 } 217 } 218 219 return b.String() 220 } 221 222 type atom struct { 223 Dimension 224 pow int 225 } 226 227 type unitPrinters []atom 228 229 func (u unitPrinters) Len() int { 230 return len(u) 231 } 232 233 func (u unitPrinters) Less(i, j int) bool { 234 // Order first by positive powers, then by name. 235 if u[i].pow*u[j].pow < 0 { 236 return u[i].pow > 0 237 } 238 return u[i].String() < u[j].String() 239 } 240 241 func (u unitPrinters) Swap(i, j int) { 242 u[i], u[j] = u[j], u[i] 243 } 244 245 // Unit represents a dimensional value. The dimensions will typically be in SI 246 // units, but can also include dimensions created with NewDimension. The Unit type 247 // is most useful for ensuring dimensional consistency when manipulating types 248 // with different units, for example, by multiplying an acceleration with a 249 // mass to get a force. See the package documentation for further explanation. 250 type Unit struct { 251 dimensions Dimensions 252 value float64 253 } 254 255 // New creates a new variable of type Unit which has the value and dimensions 256 // specified by the inputs. The built-in dimensions are always in SI units 257 // (metres, kilograms, etc.). 258 func New(value float64, d Dimensions) *Unit { 259 return &Unit{ 260 dimensions: d.clone(), 261 value: value, 262 } 263 } 264 265 // DimensionsMatch checks if the dimensions of two Uniters are the same. 266 func DimensionsMatch(a, b Uniter) bool { 267 return a.Unit().dimensions.matches(b.Unit().dimensions) 268 } 269 270 // Dimensions returns a copy of the dimensions of the unit. 271 func (u *Unit) Dimensions() Dimensions { 272 return u.dimensions.clone() 273 } 274 275 // Add adds the function argument to the receiver. Panics if the units of 276 // the receiver and the argument don't match. 277 func (u *Unit) Add(uniter Uniter) *Unit { 278 a := uniter.Unit() 279 if !DimensionsMatch(u, a) { 280 panic("unit: mismatched dimensions in addition") 281 } 282 u.value += a.value 283 return u 284 } 285 286 // Unit implements the Uniter interface, returning the receiver. If a 287 // copy of the receiver is required, use the Copy method. 288 func (u *Unit) Unit() *Unit { 289 return u 290 } 291 292 // Copy returns a copy of the Unit that can be mutated without the change 293 // being reflected in the original value. 294 func (u *Unit) Copy() *Unit { 295 return &Unit{ 296 dimensions: u.dimensions.clone(), 297 value: u.value, 298 } 299 } 300 301 // Mul multiply the receiver by the input changing the dimensions 302 // of the receiver as appropriate. The input is not changed. 303 func (u *Unit) Mul(uniter Uniter) *Unit { 304 a := uniter.Unit() 305 for key, val := range a.dimensions { 306 if d := u.dimensions[key]; d == -val { 307 delete(u.dimensions, key) 308 } else { 309 u.dimensions[key] = d + val 310 } 311 } 312 u.value *= a.value 313 return u 314 } 315 316 // Div divides the receiver by the argument changing the 317 // dimensions of the receiver as appropriate. 318 func (u *Unit) Div(uniter Uniter) *Unit { 319 a := uniter.Unit() 320 u.value /= a.value 321 for key, val := range a.dimensions { 322 if d := u.dimensions[key]; d == val { 323 delete(u.dimensions, key) 324 } else { 325 u.dimensions[key] = d - val 326 } 327 } 328 return u 329 } 330 331 // Value return the raw value of the unit as a float64. Use of this 332 // method is, in general, not recommended, though it can be useful 333 // for printing. Instead, the From method of a specific dimension 334 // should be used to guarantee dimension consistency. 335 func (u *Unit) Value() float64 { 336 return u.value 337 } 338 339 // SetValue sets the value of the unit. 340 func (u *Unit) SetValue(v float64) { 341 u.value = v 342 } 343 344 // Format makes Unit satisfy the fmt.Formatter interface. The unit is formatted 345 // with dimensions appended. If the power of the dimension is not zero or one, 346 // symbol^power is appended, if the power is one, just the symbol is appended 347 // and if the power is zero, nothing is appended. Dimensions are appended 348 // in order by symbol name with positive powers ahead of negative powers. 349 func (u *Unit) Format(fs fmt.State, c rune) { 350 if u == nil { 351 fmt.Fprint(fs, "<nil>") 352 } 353 switch c { 354 case 'v': 355 if fs.Flag('#') { 356 fmt.Fprintf(fs, "&%#v", *u) 357 return 358 } 359 fallthrough 360 case 'e', 'E', 'f', 'F', 'g', 'G': 361 p, pOk := fs.Precision() 362 w, wOk := fs.Width() 363 units := u.dimensions.String() 364 switch { 365 case pOk && wOk: 366 fmt.Fprintf(fs, "%*.*"+string(c), pos(w-utf8.RuneCount([]byte(units))-1), p, u.value) 367 case pOk: 368 fmt.Fprintf(fs, "%.*"+string(c), p, u.value) 369 case wOk: 370 fmt.Fprintf(fs, "%*"+string(c), pos(w-utf8.RuneCount([]byte(units))-1), u.value) 371 default: 372 fmt.Fprintf(fs, "%"+string(c), u.value) 373 } 374 fmt.Fprintf(fs, " %s", units) 375 default: 376 fmt.Fprintf(fs, "%%!%c(*Unit=%g)", c, u) 377 } 378 } 379 380 func pos(a int) int { 381 if a < 0 { 382 return 0 383 } 384 return a 385 }