3.1.0 • Published 3 years ago

o-quantity v3.1.0

Weekly downloads
12
License
ISC
Repository
-
Last release
3 years ago

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()
3.1.0

3 years ago

3.0.0

3 years ago

2.0.0

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago

0.4.3

3 years ago

0.4.1

4 years ago

0.4.2

4 years ago

0.4.0

4 years ago

0.3.0

4 years ago

0.2.1

4 years ago

0.2.0

4 years ago

0.1.0

4 years ago