Giter VIP home page Giter VIP logo

broch's Introduction

Clojars Project

Broch

A library for handling numbers with units.

Main Features:

  • Conversion, comparison and arithmetic.
  • Data literals
  • Clojurescript compatible
  • No dependencies

Named after Ole Jacob Broch for his contributions as director of the International Bureau of Weights and Measures.

Names

There is some disagreement on what to name things in this space, but I've settled on these definitions:

  • measure = the thing that is measured. (i.e :length or :time)
  • unit = a measure with a set scaling and a symbol. (the scaling is based in SI units)
  • quantity = a number with a unit.

Usage

The ergonomics for handling units is inspired by the excellent tick.core.

The following code assumes this ns definition.

(ns my-ns
  (:require [broch.core :as b]))

Basic units

; to turn a number into a quantity, use a unit fn
; there are many built-in ones, like b/meters
(b/meters 10) ;=> #broch/quantity[10 "m"]

; data literals
#broch/quantity[10 "m"] ;=> #broch/quantity[10 "m"]

; the unit fns also convert quantities, if compatible
(b/feet (b/meters 10)) ;=> #broch/quantity[32.8083989501 "ft"]

; but conversion of incompatible units throws an error
(b/meters (b/seconds 3)) ;=> ExceptionInfo "Cannot convert :time into :length"

; you can compare compatible units
(b/> #broch/quantity[1 "km"] #broch/quantity[999 "m"]) ;=> true

; and do arithmetic
(b/- #broch/quantity[1 "km"] #broch/quantity[1 "mi"]) ;=> #broch/quantity[-0.609344 "km"]

; and, again, we get sensible errors if incompatible
(b/- #broch/quantity[2 "km"] #broch/quantity[1 "s"]) ;=> ExceptionInfo "Cannot add/subtract :length and :time"

Derived units

; units have an internal composition-map of measure to exponent + scaling

; simple units just have their own measure
(b/composition (b/meters 2))
; => {:length 1, :broch/scaled 1}

; compound units have a more complicated composition map of the measures they're composed of
; as we all remember from school: W = J/s = N·m/s = kg·m²/s³
(b/composition (b/watts 4)) 
;=> {:mass 1, :length 2, :time -3, :broch/scaled 1}

; a kilowatt is the same, but scaled by 1000
(b/composition (b/kilowatts 4))
;=> {:mass 1, :length 2, :time -3, :broch/scaled 1000}

; this allows more complicated arithmetic (* and /) to derive the correct unit and convert the quantity, if it's defined
(b/* #broch/quantity[3 "m/s²"] #broch/quantity[3 "s"]) ;=> #broch/quantity[9 "m/s"]
(b/* #broch/quantity[12 "kW"] #broch/quantity[5 "h"]) ;=> #broch/quantity[60 "kWh"]
(b// #broch/quantity[12 "J"] #broch/quantity[1 "km"]) ;=> #broch/quantity[0.12 "N"]

; If all units are cancelled out, a number is returned
(b// #broch/quantity[1 "m"] #broch/quantity[2 "m"]) ;=> 1/2

; If no unit with a derived composition is defined, an error is thrown
(b// #broch/quantity[1 "m"] #broch/quantity[2 "s"]) ;=> #broch/quantity[0.5 "m/s"]
(b// #broch/quantity[2 "s"] #broch/quantity[1 "m"]) 
;=> ExceptionInfo "No derived unit is registered for {#broch/quantity[nil "s"] 1, #broch/quantity[nil "m"] -1}"

Defining new units

Broch comes with a bunch of units pre-defined in broch.core (more will likely be added in time, requests and PRs are welcome).

But defining your own units is a peace-of-cake.

; all units have a measure and a symbol 
(b/measure #broch/quantity[1 "m"]) ;=> :speed 
(b/symbol #broch/quantity[1 "m"]) ;=> "m" (same as in the tag literal)

; broch uses the measure to know how to convert and derive units
; both must be given when defining the unit along with its scale from the "base" unit of that measure
(b/defunit meters :length "m" 1) ;=> #'my-ns/meters
(b/defunit seconds :time "s" 1) ;=> #'my-ns/seconds

; The built-in units rely on the SI-system for measures and their base units. 
; So the meter is the base unit of :length, and other units of :length must specify their scale relative to it. 
(b/defunit feet :length "ft" 0.3048) ;=> #'my-ns/feet  

; derived units are similar, but also take a unit-map giving their composition
(b/defunit meters-per-second :speed "m/s" {meters 1 seconds -1} 1) ;=> #'my-ns/meters-per-second
; Also note that since these units are already defined, running the `defunit` forms above would print warnings like 
; "WARN: a unit with symbol m already exists! Overriding..." 
; You probably don't want to override taken symbols, make up a new one instead.

; If you provide a composition, the given scaling is relative to the composing units
; so you could say for example:
; a yard is 0.9144 meters
(defunit yards :length "yd" 0.9144) :=> #'my-ns/yards 
; and a foot is a third of a yard
(defunit feet :length "ft" 1/3 {yards 1}) :=> #'my-ns/feet
; and there's 12 inches in a foot
(defunit inches :length "in" 1/12 {feet 1}) :=> #'my-ns/inches
; and it knows that an inch is scaled 0.0254 of a meter
(b/meters (b/inches 1)) ;=> #broch/quantity[0.0254 "m"]

; Measures and symbols are just names though, so they could be anything. For example:
(b/defunit me :coolness "me" 1) ;=> #'my-ns/me
(b/defunit rich-hickey :coolness "DJ Rich" 1000) ;=> #'my-ns/rich-hickey
(= #broch/quantity[1 "DJ Rich"] #broch/quantity[1000 "me"]) ;=> true

Tradeoffs

This library is not written for high performance. It rather tries to be as accurate as possible and avoid precision loss with floating-point numbers. This means that it sometimes "upcasts" numbers to ratios, if it cannot keep it as a double without losing precision. Ratios can be harder to read for humans, so where that is a concern you can cast the number type with b/boxed

(b/boxed double (b/meters 355/113)) ;=> #broch/quantity[3.141592920353982 "m"]

FAQ

Where's Celsius and Fahrenheit?

TLDR: Intentionally left out.

Both these common ways of denoting temperature has intentionally been left out of this library. This is because neither °C nor °F are actually just units of measure in the true sense, because their zero-points are not zero. They are units on a scale, which is why we prefix them with a °.

Zero grams is no mass, and zero miles per hour is no speed, but zero °C is not no temperature. It's quite a lot of temperature actually, exactly 273.15 K of temperature. Zero kelvin is no temperature, and that's why it is included in this library, and why it's (probably) the only unit for temperature you'll ever see used in any real computations involving temperature.

We could have added support for translation (shifting the zero-point), but that would have complicated conversion and raised some difficult questions on how to handle equality and arithmetic with these non-zero-based units.

For example:

; remember that 32°F = 0°C
(b/+ (b/fahrenheit 32) (b/celcius 0)) ;=> ?
(b/+ (b/celcius 0) (b/fahrenheit 32)) ;=> ?

Is it 0 °C or 64 °F? Both answers are plausible depending on which unit you choose to convert to before adding them together. And picking one interpretation, say always converting to the first argument's unit, would mean that b/+ and b/* are no longer commutative for temperatures, which is no good.

In conclusion, if you need to do stuff with temperatures and show the result in °C or °F, do whatever you need to do in kelvins and then scale the result yourself like this:

(def k #broch/quantity[345 "K"])

; to fahrenheit
(double
 (- (* (b/num k) 9/5)
    (rationalize 459.67))) 
;=> 161.33

; to celcius
(double
 (- (b/num k)
    (rationalize 273.15))) 
;=> 71.85

Deploy

Build jar and deploy to clojars with

$ clj -T:build clean
$ clj -T:build jar
$ env CLOJARS_USERNAME=<username> CLOJARS_PASSWORD=<clojars-token> clj -X:deploy

broch's People

Contributors

2food avatar frozenlock avatar handerpeder avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.