requires: {#Magnitude}. provides: {#Unit}. "Based on the Squeak Units code at http://home.netsurf.de/helge.horch/squeak/units.html" "SI Prefixes: http://physics.nist.gov/cuu/Units/prefixes.html http://www.bipm.org/en/si/prefixes.html Main SI site: http://www.bipm.org/en/si/ A Standard interchange format for SI units and numbers http://swiss.csail.mit.edu/~jaffer/MIXF/ " lobby ensureDelegatedNamespace: #Units. prototypes addPrototype: #Unit derivedFrom: {Cloneable}. u1@(Unit traits) * u2@(Unit traits) [| unitDict units expons scratch | unitDict: Dictionary newEmpty. u1 unitsAndExponentsDo: [| :unit :expon | (UnitDict includesKey: unit) ifTrue: [unitDict at: unit put: (unitDict at: unit) + expon] ifFalse: [unitDict at: unit put: expon]]. u2 unitsAndExponentsDo: [| :unit :expon | (UnitDict includesKey: unit) ifTrue: [unitDict at: unit put: (unitDict at: unit) + expon] ifFalse: [unitDict at: unit put: expon]]. units: ExtensibleArray newEmpty. expons: ExtensibleArray newEmpty. (unitDict keys sortBy: [| :left :right | left abbrev < right abbrev]) do: [| :unit | scratch: (unitDict: at: unit). scratch isZero ifFalse: [units add: unit. expons add: scratch]]. (units size = 1 and: [(expons at: 0) = 1]) ifTrue: [^ units at: 0]. ComplexUnit units: units exponents: expons ]. u1@(Unit traits) / u2@(Unit traits) [ u1 * u2 reciprocal ]. u1@(Unit traits) per: u2@(Unit traits) [ u1 / u2 ]. u@(Unit traits) raisedTo: expon [ ComplexUnit units: {u} exponents: {expon} ]. u@(Unit traits) reciprocal [ ComplexUnit units: {u} exponents: {-1} ]. u@(Unit traits) squared [ u * u ]. u1@(Unit traits) factor: u2@(Unit traits) [| base1 base2 | base1: u1 bases. base2: u2 bases. u2 * (base1 / base2) ]. u@(Unit traits) unitsAndExponentsDo: block "Evaluate the block once for each unit/exponent pair in u, defaulting to this." [ block applyWith: u with: 1 ]. u@(Unit traits) conversionFactorTo: v@(Unit traits) "Assuming u and v are consistent, return the numerical answer to u*X=v." [ u conversionFactor / v conversionFactor ]. u@(Unit traits) additiveFactor [0]. u@(Unit traits) consistentWith: u@(Unit traits) [False]. prototypes addPrototype: #UnitValue derivedFrom: {Magnitude}. UnitValue addSlot: #unit. UnitValue addSlot: #value. uv@(UnitValue traits) unit: unit value: value [| newUV | newUV: uv clone. newUV unit: unit. newUV value: value. newUV ]. uv@(UnitValue traits) printOn: s@(Stream traits) [ uv value printOn: s. s ; '_'. uv unit printOn: s. uv ]. uv@(UnitValue traits) printFullOn: s@(Stream traits) [ uv value printOn: s. s ; ' '. uv unit printFullOn: s plural: uv value ~= 1. uv ]. x@(Number traits) as: uv@(UnitValue traits) [ UnitValue unit: CompoundUnit null value: x ]. uv@(UnitValue traits) as: x@(Number traits) [ uv unit: CompoundUnit null value: x ]. uv@(UnitValue traits) convertTo: u@(Unit traits) "Convert the value to have the same units as u, using scaling factors." [| cf | (uv unit isConsistentWith: u) ifFalse: [^ Nil]. cf: (uv conversionFactorTo: u). uv unit: u value: uv value * cf ]. uv@(UnitValue traits) bases "Return the reduction of uv to base units." [ uv convertTo: uv unit baseUnits ]. uv@(UnitValue traits) as: u@(Unit traits) [ uv unit ]. x@(Number traits) with: u@(Unit traits) [ UnitValue unit: u value: x ]. x@(UnitValue traits) isConsistentWith: y@(UnitValue traits) [ x unit isConsistentWith: y unit ]. x@(UnitValue traits) + y@(UnitValue traits) [| sum | (x isConsistentWith: y) ifFalse: [^ Nil]. sum: (x as: y). sum value: sum value + y value. sum reduced ]. x@(UnitValue traits) - y@(UnitValue traits) [ x + y negated ]. x@(UnitValue traits) * y@(UnitValue traits) [ (x unit: x unit * y unit value: x value * y value) reduced ]. x@(UnitValue traits) / y@(UnitValue traits) [ (y unit: x unit / y unit value: x value / y value) reduced ]. x@(UnitValue traits) = y@(UnitValue traits) [ (x isConsistentWith: y) and: [y value = (x as: y) value] ]. x@(UnitValue traits) < y@(UnitValue traits) [ (x isConsistentWith: y) and: [x value < (x as: y) value] ]. uv@(UnitValue traits) negated [| newUV | newUV: uv clone. newUV value: uv value negated. newUV ]. uv@(UnitValue traits) reduced "Answer the scalar part if the units have 'vanished'." [ uv unit ifNil: [uv value] ifNotNil: [uv] ]. uv1@(UnitValue traits) factor: uv2@(UnitValue traits) "Factor with respect to another unit or unitValue." [ uv1 factor: uv2 unit ]. uv@(UnitValue traits) factor: u@(Unit traits) [ uv as: (uv unit factor: u). ]. uv@(UnitValue traits) raisedTo: expon [ uv unit: (uv unit raisedTo: expon) value: (uv value raisedTo: expon) ]. uv@(UnitValue traits) sqrt [ uv unit: (uv unit raisedTo: 1 / 2) value: (uv value sqrt) ]. uv@(UnitValue traits) ceiling [ uv clone value: uv value ceiling ]. uv@(UnitValue traits) floor [ uv clone value: uv value floor ]. uv@(UnitValue traits) roundTo: x [ uv clone value: (uv value roundTo: x) ]. uv@(UnitValue traits) roundUpTo: x [ uv clone value: (uv value roundUpTo: x) ]. uv@(UnitValue traits) truncateTo: x [ uv clone value: (uv value truncateTo: x) ]. uv@(UnitValue traits) rounded [ uv clone value: uv value rounded ]. prototypes addPrototype: #CompoundUnit derivedFrom: {Unit}. CompoundUnit addSlot: #bases. CompoundUnit addSlot: #exponents. cu1@(CompoundUnit traits) = cu2@(CompoundUnit traits) [ cu1 bases = cu2 bases and: [cu1 exponents = cu2 exponents] ]. cu@(CompoundUnit traits) hash [ cu bases hash bitXor: cu exponents hash ]. cu@(CompoundUnit traits) reciprocal [ cu units: cu units exponents: (cu exponents collect: [| :each | each negated]) ]. cu@(CompoundUnit traits) raisedTo: expon [ cu units: cu units exponents: (cu exponents collect: [| :each | each * expon]) ]. cu@(CompoundUnit traits) unitsAndExponentsDo: block [ cu units with: cu exponents do: block ]. cu@(CompoundUnit traits) conversionFactor [1]. cu@(CompoundUnit traits) printOn: s@(Stream traits) [| first any count | first: True. any: False. cu unitsAndExponentsDo: [| :unit :expon | exponent isPositive ifTrue: [any: True. first ifFalse: [s nextPut: $*]. first: False. cu unit printOn: s. expon ~= 1 ifTrue: [s nextPut: $^. expon printOn: s]]]. count: (cu exponents inject: 0 into: [| :c :each | each isNegative ifTrue: [c] ifFalse: [c + 1]]). count isPositive ifTrue: [any ifFalse: [s nextPut: $1]. s nextPut: $/. count > 1 ifTrue: [s nextPut: $(]. first: True. cu unitsAndExponentsDo: [| :unit :expon | expon ~= 1 ifTrue: [first ifFalse: [s nextPut: $*]. first: False. unit printOn: s. expon < -1 ifTrue: [s nextPut: $^. expon negated printOn: s]]]. count > 1 ifTrue: [s nextPut: $)]]. cu ]. cu@(CompoundUnit traits) printFullOn: s@(Stream traits) plural: b [| pos neg | pos: (cu exponents anySatisfy: [| :expon | expon isPositive]). neg: (cu exponents anySatisfy: [| :expon | expon isNegative]). pos ifTrue: [cu printUnitsWhereExponent: [| :expon | expon isPositive] on: s plural: b]. neg /\ pos ifTrue: [s nextPut: $\s]. neg ifTrue: [s ; 'per '. cu printUnitsWhereExponent: [| :expon | expon isNegative] on: s plural: b] cu ]. cu@(CompoundUnit traits) printUnitsWhereExponent: block on: s plural: b [| power first count index thisPlural | first: True. count: (cu exponents count: block). index: 0. cu units with: cu exponents do: [| :unit :expon | (block applyWith: expon) ifTrue: [first ifTrue: [first: False] ifFalse: [s nextPut: $\s]. index: index + 1. thisPlural: (plural and: [index = count]). power: expon abs. ((power is: Integer) and: [power <= 3]) ifTrue: [power = 2 ifTrue: [s ; 'square ']. power = 3 ifTrue: [s ; 'cubic ']]. unit printFullOn: s plural: b. (power > 3 or: [(power is: Integer) not]) s nextPut: $^. power printOn: s]]. cu ]. cu1@(CompoundUnit traits) isConsistentWith: cu2@(CompoundUnit traits) [ cu1 units = cu2 units and: [cu1 bases = cu2 bases] ]. prototypes addPrototype: #ComplexUnit derivedFrom: {CompoundUnit}. ComplexUnit addSlot: #conversionFactor. ComplexUnit addSlot: #cachedBases. cu@(ComplexUnit traits) units: units exponents: expons [| newCU | newCU: cu clone. newCU units: units. newCU exponents: expons. newCU conversionFactor: 1. newCU units with: newCU exponents do: [| :unit :expon | newCU conversionFactor: newCU conversionFactor * (unit conversionFactor raisedTo: expon)]. newCU ]. cu@(ComplexUnit traits) bases "Calculating bases is expensive, so the values are cached." [ cu cachedBases ifNil: [cu cachedBases: cu calculateBases] ]. cu@(ComplexUnit traits) calculateBases [| bases unitDict newUs newExpons scratch | cu unitsAndExponentsDo: [| :unit :expon | bases: unit bases. bases unitsAndExponentsDo: [| :subunit :subexpon | scratch: subexpon * expon. (unitDict includesKey: subunit) ifTrue: [unitDict at: subunit put: (unitDict at: subunit) + scratch] ifFalse: [unitDict at: subunit put: scratch]]]. newUs: ExtensibleArray newEmpty. newExpons: ExtensibleArray newEmpty. (unitDict keys sortedBy: [| :l :r | l abbrev < r abbrev]) do: [| :unit | scratch: (unitDict at: unit). scratch isZero ifFalse: [newUs add: unit. newExpons add: scratch]]. CompoundUnit units: newUs exponents: newExpons ]. cu1@(ComplexUnit traits) isConsistentWith: cu2@(ComplexUnit traits) [ cu1 bases isConsistentWith: cu2 bases ]. prototypes addPrototype: #ModifiedUnit derivedFrom: {Unit}. ModifiedUnit addSlot: #base. ModifiedUnit addSlot: #modification. "A base unit with a modification that makes it incompatible with anything not having the same modification, good for domain-specific use. e.g. 1.6 moles -> 1.6 moles of sulfuric acid. (Strings not required)" mu@(ModifiedUnit traits) base: u modification: mod [| newMU | newMU: mu clone. newMU base: u. newMU modification: mod. newMU ]. mu1@(ModifiedUnit traits) = mu2@(ModifiedUnit traits) [ mu1 modification = mu2 modification and: [mu1 base = mu2 base] ]. mu@(ModifiedUnit traits) hash [ mu base hash bitXor: mu modification hash ]. mu@(ModifiedUnit traits) bases [ mu ]. mu@(ModifiedUnit traits) conversionFactor [ mu base conversionFactor ]. mu@(ModifiedUnit traits) modify: mod [ mu base: mu modification: mod ]. mu@(ModifiedUnit traits) printOn: s@(Stream traits) [| mod | mu base printOn: s. s ; '('. mod: mu modification. (mod is: String) ifTrue: [s ; mod] ifFalse: [mod printOn: s]. s ; ')'. mu ]. mu@(ModifiedUnit traits) printFullOn: s@(Stream traits) plural: b [| mod | mu base printFullOn: s plural: b. s ; '( of '. mod: mu modification. (mod is: String) ifTrue: [s ; mod] ifFalse: [mod printOn: s]. s ; ')'. mu ]. mu@(ModifiedUnit traits) isConsistentWith: cu@(ComplexUnit traits) [ mu isConsistentWith: cu bases ]. mu1@(ModifiedUnit traits) isConsistentWith: mu2@(ModifiedUnit traits) [ mu1 base = mu2 base and: [mu1 modification = mu2 modification] ]. prototypes addPrototype: #PrefixedUnit derivedFrom: {Unit}. prototypes addSlot: #base. prototypes addSlot: #prefix. "A Unit with an SI Prefix attached." pu@(PrefixedUnit traits) prefix: prefix base: unit [| newPU | newPU: pu clone. newPU prefix: prefix. newPU base: unit. newPU ]. pu@(PrefixedUnit traits) prefixedBy: prefix "Combine the prefixes." [| newPU | newPU: pu clone. newPU prefix: newPU prefix * prefix ]. pu@(PrefixedUnit traits) isConsistentWith: u@(Unit traits) [ pu base isConsistentWith: u ]. u@(Unit traits) isConsistentWith: pu@(PrefixedUnit traits) [ pu base isConsistentWith: u ]. pu@(PrefixedUnit traits) conversionFactor [ pu prefix scalingFactor * pu base conversionFactor ]. pu@(PrefixedUnit traits) printOn: s@(Stream traits) [ s ; pu prefix abbrev. pu base printOn: s. pu ]. pu@(PrefixedUnit traits) printFullOn: s@(Stream traits) plural: b [ s ; pu prefix prefixName. pu base printFullOn: s plural: b. pu ]. prototypes addPrototype: #SIPrefix derivedFrom: {Cloneable}. SIPrefix addSlot: #abbrev. SIPrefix addSlot: #prefixName. SIPrefix addSlot: #scalingFactor. Units ensureNamespace: #Prefixes. Units Prefixes ensureDelegatedNamespace: #ByName. Units Prefixes ensureDelegatedNamespace: #ByAbbrev. sip@(SIPrefix traits) name: name abbrev: abbrev scalingFactor: n [| newSIP | newSIP: sip clone. newSIP prefixName: name. newSIP scalingFactor: n. newSIP abbrev: abbrev. Units Prefixes ByName addSlot: (intern: name) valued: newSIP. Units Prefixes ByAbbrev addSlot: (intern: abbrev) valued: newSIP. newSIP ]. sip@(SIPrefix traits) named: name [ Units SIPrefixesByName at: (intern: name) ]. sip@(SIPrefix traits) abbrev: name [ Units SIPrefixesByAbbrev at: (intern: name) ]. u@(Unit traits) prefixedBy: prefix@(SIPrefix traits) [ PrefixedUnit prefix: prefix base: u ]. p1@(SIPrefix traits) * p2@(SIPrefix traits) [| scalingFactor newSIP | scalingFactor: p1 scalingFactor * p2 scalingFactor. Units SIPrefixesByName keysAndValuesDo: [| :key :value | value scalingFactor = scalingFactor ifTrue: [^ value]]. Nil ]. SIPrefix name: 'yotta' abbrev: 'Y' scalingFactor: (10 raisedTo: 24). SIPrefix name: 'zetta' abbrev: 'Z' scalingFactor: (10 raisedTo: 21). SIPrefix name: 'exa' abbrev: 'E' scalingFactor: (10 raisedTo: 18). SIPrefix name: 'peta' abbrev: 'P' scalingFactor: (10 raisedTo: 15). SIPrefix name: 'tera' abbrev: 'T' scalingFactor: (10 raisedTo: 12). SIPrefix name: 'giga' abbrev: 'G' scalingFactor: (10 raisedTo: 9). SIPrefix name: 'mega' abbrev: 'M' scalingFactor: (10 raisedTo: 6). SIPrefix name: 'kilo' abbrev: 'k' scalingFactor: (10 raisedTo: 3). SIPrefix name: 'hecto' abbrev: 'h' scalingFactor: (10 raisedTo: 2). SIPrefix name: 'deca' abbrev: 'da' scalingFactor: (10 raisedTo: 1). SIPrefix name: 'deci' abbrev: 'd' scalingFactor: (10 raisedTo: -1). SIPrefix name: 'centi' abbrev: 'c' scalingFactor: (10 raisedTo: -2). SIPrefix name: 'milli' abbrev: 'm' scalingFactor: (10 raisedTo: -3). SIPrefix name: 'micro' abbrev: 'u' scalingFactor: (10 raisedTo: -6). SIPrefix name: 'nano' abbrev: 'n' scalingFactor: (10 raisedTo: -9). SIPrefix name: 'pico' abbrev: 'p' scalingFactor: (10 raisedTo: -12). SIPrefix name: 'atto' abbrev: 'a' scalingFactor: (10 raisedTo: -15). SIPrefix name: 'femto' abbrev: 'f' scalingFactor: (10 raisedTo: -18). SIPrefix name: 'zepto' abbrev: 'z' scalingFactor: (10 raisedTo: -21). SIPrefix name: 'yocto' abbrev: 'y' scalingFactor: (10 raisedTo: -24). prototypes addPrototype: #NamedUnit derivedFrom: {Unit}. NamedUnit addSlot: #abbrev. NamedUnit addSlot: #unitName. NamedUnit addSlot: #pluralName. nu@(NamedUnit traits) name: n1 plural: np abbrev: ab [| newU | newU: nu clone. nu unitName: n1. nu pluralName: np. nu abbrev: ab. nu ]. nu@(NamedUnit traits) printOn: s@(Stream traits) [ s ; nu abbrev. nu ]. nu@(NamedUnit traits) printFullOn: s@(Stream traits) plural: b [ b ifTrue: [s ; nu unitName] ifFalse: [s ; nu pluralName]. nu ]. prototypes addPrototype: #BaseUnit derivedFrom: {NamedUnit}. "Defines core SI units and allows for basic extension." bu@(BaseUnit traits) modify: mod [ ModifiedUnit base: bu modification: mod ]. bu@(BaseUnit traits) bases [bu]. bu@(BaseUnit traits) conversionFactor [1]. bu1@(BaseUnit traits) isConsistentWith: bu2@(BaseUnit traits) [ bu1 == bu2 ]. bu@(BaseUnit traits) isConsistentWith: cu@(ComplexUnit traits) [ bu isConsistentWith: cu bases ]. cu@(CompoundUnit traits) isConsistentWith: cu2@(BaseUnit traits) "Compound units always have a non-trivial set of units." [False]. mu@(ModifiedUnit traits) isConsistentWith: bu@(BaseUnit traits) [False]. bu@(BaseUnit traits) isConsistentWith: mu@(ModifiedUnit traits) [False]. _@(ModifiedUnit traits) is: _@(BaseUnit traits) "Modified units are effectively new bases." [True]. Units ensureDelegatedNamespace: #SI. Units SI ensureDelegatedNamespace: #ByName. Units SI ensureDelegatedNamespace: #ByAbbrev. Units SI ensureDelegatedNamespace: #ByPluralName. bu@(BaseUnit traits) name: n1 plural: np abbrev: ab [| newBU n1s nps abs | newBU: resend. Units SI ByName addSlot: (intern: n1) valued: newBU. Units SI ByAbbrev addSlot: (intern: ab) valued: newBU. Units SI ByPluralName addSlot: (intern: np) valued: newBU. newBU ]. BaseUnit name: 'gram' plural: 'grams' abbrev: 'g'. BaseUnit name: 'meter' plural: 'meters' abbrev: 'm'. BaseUnit name: 'second' plural: 'seconds' abbrev: 's'. BaseUnit name: 'candela' plural: 'candela' abbrev: 'c'. BaseUnit name: 'mole' plural: 'moles' abbrev: 'mol'. bu@(BaseUnit traits) named: name [ (intern: name) sendTo: {Units SI ByName} ]. bu@(BaseUnit traits) abbrev: name [ (intern: name) sendTo: {Units SI ByAbbrev} ]. bu@(BaseUnit traits) pluralNamed: name [ (intern: name) sendTo: {Units SI ByPluralName} ]. cu@(ComplexUnit traits) isConsistentWith: bu@(BaseUnit traits) [ bu isConsistentWith: cu bases ]. prototypes addPrototype: #TemperatureBaseUnit derivedFrom: {BaseUnit}. TemperatureBaseUnit name: 'kelvin' plural: 'kelvin' abbrev: 'K'. prototypes addSlot: #DerivedUnit. prototypes DerivedUnit: NamedUnit derive. DerivedUnit addSlot: #unitValue. "This defines a unit in terms of powers of other units, plus a scalar." Units ensureDelegatedNamespace: #ByName. Units ensureDelegatedNamespace: #ByAbbrev. Units ensureDelegatedNamespace: #ByPluralName. du@(DerivedUnit traits) name: n1 plural: np abbrev: ab value: val [| newU | newU: (du name: n1 plural: np abbrev: ab). newU unitValue: val. Units ByName addSlot: n1 valued: newU. Units ByAbbrev addSlot: ab valued: newU. Units ByPluralName addSlot: np valued: newU. newU ]. du@(DerivedUnit traits) unit [ du unitValue unit ]. du@(DerivedUnit traits) bases [ du unitValue unit bases ]. du@(DerivedUnit traits) conversionFactor [ du unitValue value * du unitValue unit conversionFactor ]. du@(DerivedUnit traits) isConsistentWith: bu@(BaseUnit traits) [ bu isConsistentWith: du base ]. bu@(BaseUnit traits) isConsistentWith: du@(DerivedUnit traits) [ bu isConsistentWith: du base ]. DerivedUnit name: 'inch' plural: 'inches' abbrev: 'in' value: (2.54 with: Unit cm). DerivedUnit name: 'foot' plural: 'feet' abbrev: 'ft' value: (12 with: Unit in). DerivedUnit name: 'yard' plural: 'yards' abbrev: 'yd' value: (3 with: Unit ft). DerivedUnit name: 'mile' plural: 'miles' abbrev: 'mi' value: (5280 with: Unit ft). DerivedUnit name: 'acre' plural: 'acres' abbrev: 'acre' value: (4046.87260987 with: Unit m squared). DerivedUnit name: 'newton' plural: 'newtons' abbrev: 'N' value: (1 with: Units kg * Units m / Units sec squared). DerivedUnit name: 'minute' plural: 'minutes' abbrev: 'min' value: (60 with: Units second). DerivedUnit name: 'hour' plural: 'hours' abbrev: 'h' value: (60 with: Units min). DerivedUnit name: 'day' plural: 'days' abbrev: 'd' value: (24 with: Units hour). DerivedUnit name: 'year' plural: 'years' abbrev: 'yr' value: (365.242198781 with: Unit day). DerivedUnit name: 'hertz' plural: 'hertz' abbrev: 'Hz' value: (1 / (1 with: Unit second)). prototypes addPrototype: #TemperatureUnit derivedFrom: {DerivedUnit}. TemperatureUnit addSlot: #additiveFactor. tu@(TemperatureUnit traits) name: n1 plural: np abbrev: ab value: val additiveFactor: af [| newTU | newTU: (tu name: n1 plural: np abbrev: ab value: val). newTU additiveFactor: af. newTU ]. TemperatureUnit name: 'degree Rankine' plural: 'degrees Rankine' abbrev: 'R' value: (5 / 9 with: Units K). additiveFactor: 0. TemperatureUnit name: 'degree Fahrenheit' plural: 'degrees Fahrenheit' abbrev: 'F' value: (5 / 9 with: Units K). additiveFactor: -459.67. TemperatureUnit name: 'degree Celsius' plural: 'degrees Celsius' abbrev: 'C' value: (1 with: Units K). additiveFactor: -273.15.