o-quantity
v3.1.0
Published
A library for quantity arithmetic and units
Downloads
5
Maintainers
Readme
Quantity
A library for quantity arithmetic and units
Quantities
A Quantity is an amount of some unit that handles comparisons and basic arithmetic operations.
Documentation
http://o-programming-language.org/
Why use Quantity objects?
A Quantity object uses the knowledge of its own unit to convert and compare itself with other equivalent units.
Than means that the only points in the application where the program must be aware of units and apply transformations are when it reads the quantity from a source like an API, database or user interface and when it writes the quantity to a target like an API, datatabase or user interface.
During the rest of the execution the program deals transparently with variables regardless of their actual units.
Also Quantity objects validate that arithmetic operations are evaluated with other equivalent units and will raise an error when a non equivalent unit is used flagging the units missmatch immediatly instead of silently producing a wrong value.
Arithmetic
Create quantity of a metric unit and do arithmetic operations with it
const {Kg, Gr, Mg, Lt} = require('o-quantity')
const quantity1 = Kg.amount(1)
const quantity2 = Gr.amount(250)
const total = quantity1.op('+', quantity2)
Units are properly converted if required.
Comparison
const limit = Kg.amount(3)
if( total.is('>', limit ) {
// ...
}
If units are not comparable the comparison will throw a NonEquivalentUnitsComparisonError:
Kg.amount(1).is('>', Lt.amount(1))
Convertions
Standard units can be expressed in other units of the same metric system
const {Kg} = require('o-quantity')
const quantity = Kg.amount(1)
quantity.toKg()
quantity.toGr()
quantity.toMg()
quantity.toLbs()
quantity.toOz()
While these convertions are not required for comparisons and arithmetic operations they can be used to show Quantities to users, to store quantities in a database and to communicate with external APIs.
Standard units
At this moment the standard units implemented in o-quantity
are
// Quantity of weight
const {
Kg,
Hg,
Dag,
Gr,
Dg,
Cg,
Mg,
Oz,
Lbs
} = require('o-quantity')
// Quantity of volume
const {
Kl,
Hl,
Dal,
Lt,
Dl,
Cl,
Ml
} = require('o-quantity')
// Quantity of distance
const {
Km,
Hm,
Dam,
Mt,
Dm,
Cm,
Mm
} = require('o-quantity')
// Quantity of storage
const {
Gb,
Mb,
Kb,
Bt
} = require('o-quantity')
// Quantity of time
const {
Weeks,
Days,
Hours,
Minutes,
Seconds,
Milliseconds
} = require('o-quantity')
Non standard units
A Quantity object has two components, an amount and a unit.
Usually the amount is a scalar value like a float or an integer or a value object like a RationalNumber or a FixedPointNumber.
The unit can also be any object, not just standard metric system units.
For example
const {Quantity} = require('o-quantity')
const quantity1 = new Quantity(1, 'points')
const quantity2 = new Quantity(2, 'points')
const total = quantity1.op('+', quantity2)
The same rules apply as for standard units.
For example the comparison below will throw a NonEquivalentUnitsComparisonError:
const {Quantity, Kg} = require('o-quantity')
const quantity1 = new Quantity(1, 'points')
quantity1.is('==', Kg.amount(1))
Precision
To compare quantities with a given precision use ~=
and ~==
(read it like 'approximately equal to').
Operator ~==
raises an error if the comparison is with a not equivalente unit. For example
Kg.amount(1.01).is('~==', Lt.amount(1.0099)) // error
Operator ~=
returns false if the comparison is with a not equivalente unit. For example
Kg.amount(1.01).is('~=', Lt.amount(1.0099)) // false
ArithmeticObjects
The following classes are ArithmeticObjects:
- FloatNumber
- FixedPointNumber
- RationalNumber
- Quantity
While each class implementation is different and has its own specifics they all implement the following protocol:
const n
const m
const result = n.op('+', m)
const result = n.op('-', m)
const result = n.op('*', m)
const result = n.op('/', m)
const result = n.is('==', m)
const result = n.is('===', m)
const result = n.is('~=', m)
const result = n.is('~==', m)
const result = n.is('>', m)
const result = n.is('>=', m)
const result = n.is('<', m)
const result = n.is('<=', m)
n.toNumber()
or if you prefer the method version
const n
const m
const result = n.plus(m)
const result = n.minus(m)
const result = n.multiplyBy(m)
const result = n.divideBy(m)
const result = n.equals(m)
const result = n.comparedTo(m)
const result = n.equalsWithPrecision(m, 0.01)
const result = n.comparedToWithPrecision(m, 0,001)
const result = n.greaterThan(m)
const result = n.greaterOrEqualThan(m)
const result = n.lowerThan(m)
const result = n.lowerOrEqualThan(m)
n.toNumber()
FloatNumber
FloatNumber are wrappers of javascript built-in Integer and Double numbers.
The reason to wrap them is to have a polymorphic procotol with other implementations of numbers like FixedPointNumber and be able to do things like
const result = n.plus(m)
const result = n.equals(m)
regardless of whether n and m are built-in values or complex objects.
FixedPointNumber
A FixedPointNumber is a number with a fixed number of decimal places. For example
1.02
is a FixedPointNumber with 2 decimal places.
The difference with floating point numbers is the precision used in its arithmetic operations. FixedPointNumbers will round to its number of decimal places on every operation. That makes it a good fit for handling amounts of money where the precision is 2 decimal places.
The comparison and arithmetic operations with FixedPointNumbers are the same as with Quantities:
const n = new FixedPointNumber(1.01, { decimals: 2 })
const m = new FixedPointNumber(1.02, { decimals: 2 })
const result = n.op('+', m)
except that arithmetic operations might take an additional optional parameter to get the remainder of the operation:
const n = new FixedPointNumber(10.00, { decimals: 2 })
const tail = {}
const result = n.op('/', 3, tail)
tail.tail === 0.00333
tail.rounded === 0
to allow each use case to decide what to do with the rounding remainder of the operation.
RationalNumber
A RationalNumber is a number treated like a pair of (numerator / denominator) integers instead of a floating point number.
All operations are done with numerators and denominators using Integer arithmetic therefore avoiding the accumulation of rounding errors.
A RationalNumber can be created from any other number
const n = new RationalNumber(10)
const n = new RationalNumber(0.5)
const n = new RationalNumber({ numerator: 1, denominator: 2 })
const n = new RationalNumber(new FloatNumber(0.5))
const n = new RationalNumber(new FixedPointNumber(0.5, { decimals: 2 }))
and the comparisons and operations are the same as for any ArithmeticObject.
Convertion between number types
To convert FloatNumber, RationalNumber or FixedPointNumber to a regular float, for example to communicate the value to an external API or database, use
n.toNumber()
To convert between number types use
n.toFloatNumber()
n.toRationalNumber()
n.toFixedPointNumber()
Musical Scales
Important
Before using this implementation of Musical Scales please we aware that this is an experimental work in progress and, more important, that I am a fairly recent student of music, not a music expert, and this work is part of my musical study resources and practical works.
Meaning that it would be safer (and wiser) to validate all of these concepts with your own teachers of music while using it.
ChromaticNote
A ChromaticNote
is an abstract chromatic note. Abstract means that is not in a concrete position in a scale but rather in reference to its sorrounding ChromaticNotes.
The available ChromaticNotes are
const {
Do_b, Do, Do_d,
Re_b, Re, Re_d,
Mi_b, Mi, Mi_d,
Fa_b, Fa, Fa_d,
Sol_b, Sol, Sol_d,
La_b, La, La_d,
Si_b, Si, Si_d
} = require('o-quantity').MusicalScale
and can be dynamically queried with
const { MusicalScale } = require('o-quantity')
const allNamedNotes = MusicalScale.ChromaticNote.getAllNotes()
_b
stands for the note accidental bemol
and _d
for note accidental diesis
.
A ChromaticNote supports the following operations:
const note = Si
note.isBemol()
note.isDiesis()
note.getNoteName() // returns the name of the note without its accidentals
note.getSymbolName()
note.getName()
note.getWithoutAccidentals() // the same note without its accidentals
note.getNextNote() // the following note in the cromatic scale preserving accidentals
note.getNextSemitone() // the following note at a half tone distance
note.at(height) // a ScaleNote at the given height, Height 0 is the piano middle Do
note.equals(otherNote)
note.is('==', otherNote)
note.is('>', otherNote)
note.is('>=', otherNote)
note.is('<', otherNote)
note.is('<=', otherNote)
A ScaleNote
is a ChromaticNote
note in a position in the scale.
The position is relative to the piano middle Do, which has a position (or height) of 0.
To create a ScaleNote define a position in a ChromaticNote:
Do.at(0)
La_d.at(-1)
Fa.at(3)
ScaleNote supports the same operations as a ChromaticNote plus:
Do.at(0).getHeight()
Do.at(0).up(Major3.amount(1))
La_d.at(-1).up(Perfect5.amount(3))
Fa.at(3).down(Semitones.amount(6))
Do.at(0).staffDisplayString() // a textual staff print in major G key
Do.at(0).displayString()
The available interval distances are:
Semitone,
Tone,
Minor2,
Major2,
Minor3,
Major3,
Perfect4,
Augmented4,
Perfect5,
Minor6,
Major6,
Minor7,
Major7,
Perfect8
and can be dynamically queried with
const { MusicalScale } = require('o-quantity')
const allNamedDistances = MusicalScale.ChromaticScaleDistance.getAllDistances()