npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@speleotica/unitized

v2.0.0

Published

nice API for handling numbers with associated units

Downloads

47

Readme

@speleotica/unitized

CircleCI Coverage Status semantic-release Commitizen friendly npm version

A nice API for handling numbers with associated units.

Only units relevant to cave surveying are built into this package, but it's possible to define your own using the API.

Motivation

I've dealt with a lot of unit conversion bugs over the years. There are usually two root causes:

  • storing the associated unit in a separate variable than a number
  • forgetting to perform a unit conversion somewhere during a calculation

To cut down on these kinds of mistakes, now I try to always store the number and its unit together in a single object, and perform calculations on those objects instead of directly on the numbers. I only deal with raw numbers at the input or output boundaries of an API.

The TypeScript types are also designed to help you avoid accidentally mixing quantities of different unit types, for instance lengths and angles.

Here are some examples of what the API for this looks like:

Unitize.feet(1).add(Unitize.inches(6)) // 1.5 ft
Unitize.feet(1).get(Length.meters) // 0.3048 m
Unitize.feet(2).div(Unitize.inches(4)) // 6

type Point = {
  northing: UnitizedNumber<Length>,
  easting: UnitizedNumber<Length>,
  elevation: UnitizedNumber<Length>,
}

type SurveyLeg = {
  distance: UnitizedNumber<Length>,
  azimuth: UnitizedNumber<Angle>,
  inclination: UnitizedNumber<Angle>,
}

function calculateLeg(
  from: Point,
  { distance, azimuth, inclination }: SurveyLeg
) {
  const xy = distance.mul(Angle.cos(inclination))
  const northing = xy.mul(Angle.cos(azimuth))
  const easting = xy.mul(Angle.sin(azimuth))
  const elevation = distance.mul(Angle.sin(inclination))

  return {
    northing: from.northing.add(northing),
    easting: from.easting.add(easting),
    elevation: from.elevation.add(elevation),
  }
}

API

Unitize

import { Unitize } from '@speleotica/unitized'

Contains shortcut functions for making unitized numbers. For example, Unitize.meters(2) is equivalent to new UnitizedNumber(2, Length.meters).

  • Unitize.meters
  • Unitize.centimeters
  • Unitize.kilometers
  • Unitize.feet
  • Unitize.inches
  • Unitize.yards
  • Unitize.miles
  • Unitize.radians
  • Unitize.degrees
  • Unitize.gradians (1/400 of a unit circle)
  • Unitize.milsNATO (1/6400 of a unit circle)
  • Unitize.percentGrade (rise over run as %; 100% = 45 degrees)

Length

import { Length } from '@speleotica/unitized'

Contains length units:

  • Length.meters
  • Length.centimeters
  • Length.kilometers
  • Length.feet
  • Length.inches
  • Length.yards
  • Length.miles

Each of these units is an instance of Unit<Length>.

Angle

import { Angle } from '@speleotica/unitized'

Contains angle units:

  • Angle.radians
  • Angle.degrees
  • Angle.gradians (1/400 of a unit circle)
  • Angle.milsNATO (1/6400 of a unit circle)
  • Angle.percentGrade (rise over run as %; 100% = 45 degrees)

Each of these units is an instance of Unit<Angle>.

static sin(angle: UnitizedNumber<Angle>): number

Computes the sine of the given angle.

static cos(angle: UnitizedNumber<Angle>): number

Computes the cosine of the given angle.

static tan(angle: UnitizedNumber<Angle>): number

Computes the tangent of the given angle.

static asin(sin: number): UnitizedNumber<Angle>

Computes the arcsine of the given number.

static acos(cos: number): UnitizedNumber<Angle>

Computes the arccosine of the given number.

static atan(tan: number): UnitizedNumber<Angle>

Computes the arctangent of the given number.

static atan2(y: number, x: number): nUnitizedNumber<Angle>

Equivalent to Math.atan2, but returns a UnitizedNumber<Angle>.

static atan2(y: UnitizedNumber<Length>, x: UnitizedNumber<Length>): nUnitizedNumber<Angle>

Equivalent to Math.atan2, but returns a UnitizedNumber<Angle>.

static normalize(angle: UnitizedNumber<Angle>): UnitizedNumber<Angle>

Normalizes the given angle to the range [0, one revolution); returns the normalized angle in the same units.

static opposite(angle: UnitizedNumber<Angle>): UnitizedNumber<Angle>

Returns the angle facing the opposite direction, in the same units.

UnitizedNumber<T extends UnitType<T>>

import { UnitizedNumber } from '@speleotica/unitized'

constructor(value: number, unit: Unit<T>)

Creates a UnitizedNumber with the given value and unit

unit: Unit<T>

The unit this UnitizedNumber's value is in.

private value: number

The numeric value of this UnitizedNumber. Accessing this directly is discouraged; use get(unit) instead.

get(unit: Unit<T>): number

Converts this UnitizedNumber's value to the given unit.

add(addend: UnitizedNumber<T>): UnitizedNumber<T>

Returns this + addend as a new UnitizedNumber in the same units as this.

get isFinite(): boolean

Returns true iff the numeric value is not NaN or infinite.

get isInfinite(): boolean

Returns true iff the numeric value is infinite.

get isNaN(): boolean

Returns true iff the numeric value is NaN.

in(unit: Unit<T>): UnitizedNumber<T>

Returns a new UnitizedNumber in the given unit.

negate(): UnitizedNumber<T>

Returns a new UnitizedNumber with the same units and negative value.

sub(subtrahend: UnitizedNumber<T>): UnitizedNumber<T>

Returns this - subtrahend as a new UnitizedNumber in the same units as this.

mul(multiplicand: number): UnitizedNumber<T>

Returns this * multiplicand as a new UnitizedNumber in the same units as this.

get isNegative(): boolean

Returns true iff the numeric value is negative.

get isPositive(): boolean

Returns true iff the numeric value is positive.

get isZero(): boolean

Returns true iff the numeric value is 0.

get isNonzero(): boolean

Returns true iff the numeric value is not 0.

mod(modulus: UnitizedNumber<T>): UnitizedNumber<T>

Returns this % modulus as a UnitizedNumber in the same units as this.

abs(): UnitizedNumber<T>

Returns a new UnitizedNumber with the same units and absolute value.

div(denominator: UnitizedNumber<T>): number

Returns this / denominator.

div(denominator: number): UnitizedNumber<T>

Returns this / denominator as a UnitizedNumber in the same units as this.

compare(other: UnitizedNumber<T>): number

Returns > 0 if this > other, < 0 if this < other, and 0 otherwise.

class UnitType<T extends UnitType<T>>

import { UnitType } from '@speleotica/unitized'

A type of unit, for example length or angle or temperature.

convert(value: number, from: Unit<T>, to: Unit<T>): number

Converts a value from one unit to another. The default implementation just returns

to.fromBase(from.toBase(value))

If you want more precision you can use a FactorTableUnitType to provide a table of conversion factors from one unit to another, or override this method in a derived class.

class Unit<T extends UnitType<T>>

import { Unit } from '@speleotica/unitized'

constructor(type: UnitType<T>, id: string, props)

Props may have fromBaseFactor and toBaseFactor, which will be used for the default fromBase and toBase implementations (which you may override in a derived class).

type: UnitType<T>

The type of this unit.

id: string

The unique id of this unit.

fromBase(value: number): number

Converts the given number from this unit to the base unit. Only UnitType should call this method. You may override this method in a derived class for nonlinear units.

toBase(value: number): number

Converts the given number from the base unit to this unit. Only UnitType should call this method. You may override this method in a derived class for nonlinear units.

class FactorTableUnitType<T extends FactorTableUnitType<T>> extends UnitType<T>

import { FactorTableUnitType } from '@speleotica/unitized'

A UnitType that uses a table of factors to more accurately convert from one unit to another (instead of converting to some base unit as an intermediary).

constructor(factors: Record<string, Record<string, number>>)

Factors is the conversion table, with unit ids as keys. value * factors[from.id][to.id] is used to convert from one unit to another. Not all pairs of units have to be included in the table; convert will fall back to converting to the base unit as an intermediary if a factor isn't found in this table.