measure
Measure
This shard lets you measure, compare, and convert values in different units of measure.
For example, you can do things like:
- Compare a weight in ounces to one in grams
- Convert a length in miles to one in kilometers
Installation
-
Add the dependency to your
shard.yml
:dependencies: measure: github: jgaskins/measure
-
Run
shards install
Usage
Length
You measure lengths with Measure::Length
. You can either instantiate it with the constructor or the shorthand methods on Number
:
require "measure/length"
# Both of these expressions are equivalent:
Measure::Length.new(50, :miles)
50.miles
You can also convert measurements to other units:
50.miles.to(:km)
# => Measure::Length(@magnitude=80.46719742504969, @unit=Measure::Length::Unit::Kilometer)
10.miles.to(:feet)
# => Measure::Length(@magnitude=52800.0, @unit=Measure::Length::Unit::Foot)
1.foot.to(:cm)
# => Measure::Length(@magnitude=30.47999902464003, @unit=Measure::Length::Unit::Centimeter)
6.feet.to(:inches)
# => Measure::Length(@magnitude=72.0, @unit=Measure::Length::Unit::Inch)
The full list of length units are available here.
Weight
Similar to Measure::Length
for length/distance measurements, we measure weights with Measure::Weight
:
require "measure/weight"
# Both of these expressions are equivalent:
Measure::Weight.new(50, :pounds)
50.pounds
It also supports conversions:
50000.grams.to(:pounds)
# => Measure::Weight(@magnitude=110.231, @unit=Measure::Weight::Unit::Pound)
50000.grams.to(:kilograms)
# => Measure::Weight(@magnitude=50.0, @unit=Measure::Weight::Unit::Kilogram)
50.grams.to(:ounces)
# => Measure::Weight(@magnitude=1.763696, @unit=Measure::Weight::Unit::Ounce)
Comparisons with similar projects
Measure
isn't the only Crystal shard that offers support for various units of measure. Below is a comparison with the ones I've tried.
spider-gazelle/crunits
— Units
This shard is a Crystal port of the Ruby unitwise
gem. The Ruby gem is amazing, to be clear, and the Crystal shard appears to support similar levels of flexibility and extensibility.
The reason I chose not to use it and instead write Measure
is that Units
handles measurements as BigDecimal
for the magnitude and String
for the unit of measure. This has two primary drawbacks.
The first is that, if you use the wrong unit of measure (misspell it, for example), you can't find out until run time. So if you wrote "hours"
instead of "hour"
, you may not notice until that code is in production. This fits the Ruby perspective well enough, but Measure
leans on the Crystal type system so you can detect these sorts of bugs much earlier. You can argue that you should see it work before deploying it, but let's be real, we've all screwed this up. We've YOLOed code to prod, written tests that didn't test what we thought we were testing and never saw them fail, etc. We're not perfect. If you haven't, let me know and I'll hire you.
The second is that using BigDecimal
and String
makes crunits
very malleable (you don't have to patch the shard to add units, for example), but it's also significantly slower. BigDecimal
allows arbitrary precision, but that precision has a performance cost. If you need this precision, use crunits
. String
s for units mean every instantiation, arithmetic operation, conversion, etc all involve string parsing. This adds a lot of overhead. On my machine, the benchmarks look like this:
Instantiation
crunits 4.84k (206.70µs) (± 1.50%) 908kB/op 245402.46× slower
measure 1.19G ( 0.84ns) (± 1.33%) 0.0B/op fastest
Arithmetic
crunits 1.44M (693.46ns) (± 0.58%) 1.93kB/op 359.61× slower
measure 518.57M ( 1.93ns) (± 3.71%) 0.0B/op fastest
Conversion
crunits 13.28k ( 75.30µs) (± 0.71%) 290kB/op 78776.24× slower
measure 1.05G ( 0.96ns) (± 1.42%) 0.0B/op fastest
For some of these, the monotonic clock is overcounting how long it takes to run the block for Measure
because it doesn't have sufficient precision, so the real difference may be significantly larger. But even so, a factor of 360-245k is pretty huge. Relying on primitive 64-bit floats and enums makes Measure
significantly faster.
Chances are, instantiating Units::Measurement
instances won't be a bottleneck in your application, but it and the heap-memory usage (Units::Measurement
allocates almost 1MB of heap per instance) are factors to consider.
Contributing
- Fork it (https://github.com/jgaskins/measure/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Contributors
- Jamie Gaskins - creator and maintainer
measure
- 1
- 0
- 0
- 0
- 1
- 2 days ago
- January 31, 2025
MIT License
Wed, 19 Feb 2025 23:23:05 GMT