From 4dfe8163a47986131d1e3822af02de4cbbc81642 Mon Sep 17 00:00:00 2001 From: Harshal LADHE Date: Thu, 14 Sep 2023 11:24:03 +0530 Subject: [PATCH] Updated readme, units, and changelog --- CHANGELOG.md | 15 +- README.md | 382 +++++++++++++++++++++++++++++++++++++++++++++++++++ units.md | 32 +++++ 3 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 units.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f2ebd6..3c00161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ +## [1.0.0](https://github.com/shivam091/unit_measurements/compare/v0.1.0...v1.0.0) - 2023-09-14 + +### What's new + +- Added support to build unit groups. +- Added unit group for `length` units. +- Added unit group for `weight` units. +- Added support to build `si` units. +- Added support to parse `Complex`, `Rational`, `Scientific` numbers, and `ratios`. +- Added support to convert quantity between two units using `#convert_to`, `#convert_to!`, and `#parse` methods. + +---------- + ## [0.1.0] - 2023-09-13 -- Initial release +### Initial release ----------- diff --git a/README.md b/README.md index ebf0748..7d542b2 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,30 @@ A library that encapsulate measurements and their units in Ruby. **Harshal V. Ladhe, M.Sc. Computer Science.** +## Introduction + +Many technical applications need use of specialized calculations at some point of time. +Frequently, these calculations require unit conversions to ensure accurate +results. Needless to say, this is a pain to properly keep track of, and is prone +to numerous errors. + +## Solution + +The `unit_measurements` gem is designed to simplify the handling of units for scientific calculations. + +## Advantages + +1. Provides easy conversion between units. +2. Built in support for various [unit groups](units.md). +3. Lightweight and easily extensible to include other units and conversions. +4. Can convert `complex`, `rational`, `fractions`, `exponents`, `scientific notations`, and `ratios`. + +## Disclaimer + +_The unit conversions presented in `unit_measurements` are provided for reference and general informational purposes. +While we aim to offer accurate conversions, we cannot guarantee their precision in all scenarios. +Users are advised to cross-verify conversions as needed for their specific use cases._ + ## Minimum Requirements * Ruby 3.2.2+ (https://www.ruby-lang.org/en/downloads/branches/) @@ -31,6 +55,364 @@ Or otherwise simply install it yourself as: `$ gem install unit_measurements` +## Usage + +The **`UnitMeasurements::Measurement`** class is responsible for conversion of quantity to various compatble units. + +Measurements can't be initialized or converted to other units directly with the `UnitMeasurements::Measurement` class, +but rather with the unit group classes viz., `UnitMeasurements::Weight`, `UnitMeasurements::Length`, etc. + +**Initialize a measurement:** + +```ruby +UnitMeasurements::Weight.new(1, :kg) +#=> 1 kg +``` + +**Converting to other units:** + +This gem allows you to convert among units of same unit group. +You can convert measurement to other unit using `#convert_to` (aliased as `#to`) +or `#convert_to!` (aliased as `#to!`) methods. + +You can use `#convert_to` as: + +```ruby +UnitMeasurements::Weight.new(1, :kg).convert_to(:g) +#=> 1000 g +``` + +If you want to modify measurement object itself, you can use `#convert_to!` method as: + +```ruby +UnitMeasurements::Weight.new(1, :kg).convert_to!(:g) +#=> 1000 g +``` + +You can also chain call of `#convert_to` and `#convert_to!` methods as: + +```ruby +UnitMeasurements::Weight.new(1, :kg).convert_to(:g).convert_to(:t).convert_to!(:q) +#=> 0.01 q +``` + +**Parse string without having to split out the quantity and source unit:** + +```ruby +UnitMeasurements::Weight.parse("1 kg") +#=> 1 kg +``` + +**Parse string that mentions quantity, source unit, and target unit:** + +```ruby +UnitMeasurements::Weight.parse("1 kg to g") +#=> 1000 g +UnitMeasurements::Weight.parse("1 kg as g") +#=> 1000 g +UnitMeasurements::Weight.parse("1 kg in g") +#=> 1000 g +``` + +**Parse rational numbers, source unit, and (or) target unit:** + +```ruby +UnitMeasurements::Weight.new(Rational(2, 3), :kg).convert_to(:g) +#=> 666.666666666667 g +UnitMeasurements::Weight.new("2/3", :kg).convert_to(:g) +#=> 666.666666666667 g +UnitMeasurements::Weight.parse("2/3 kg").convert_to(:g) +#=> 666.666666666667 g +UnitMeasurements::Weight.parse("2/3 kg to g") +#=> 666.666666666667 g +``` + +**Parse complex numbers, source unit, and (or) target unit:** + +```ruby +UnitMeasurements::Weight.new(Complex(2, 3), :kg).convert_to(:g) +#=> 2000.0+3000.0i g +UnitMeasurements::Weight.new("2+3i", :kg).convert_to(:g) +#=> 2000.0+3000.0i g +UnitMeasurements::Weight.parse("2+3i kg").convert_to(:g) +#=> 2000.0+3000.0i g +UnitMeasurements::Weight.parse("2+3i kg to g") +#=> 2000.0+3000.0i g +``` + +**Parse scientific numbers, source unit, and (or) target unit:** + +```ruby +UnitMeasurements::Weight.new(BigDecimal(2), :kg).convert_to(:g) +#=> 2000 g +UnitMeasurements::Weight.new(0.2e1, :kg).convert_to(:g) +#=> 2000 g +UnitMeasurements::Weight.parse("0.2e1 kg").convert_to(:g) +#=> 2000 g +UnitMeasurements::Weight.parse("0.2e1 kg to g") +#=> 2000 g +``` + +**Parse ratios, source unit, and (or) target unit:** + +```ruby +UnitMeasurements::Weight.new("1:2", :kg).convert_to(:g) +#=> 500 g +UnitMeasurements::Weight.parse("1:2 kg").convert_to(:g) +#=> 500 g +UnitMeasurements::Weight.parse("1:2 kg to g") +#=> 500 g +``` + +**Parse fractional notations, source unit, and (or) target unit:** + +```ruby +UnitMeasurements::Weight.new("1/2", :kg).convert_to(:g) +#=> 500 g +UnitMeasurements::Weight.parse("1/2 kg").convert_to(:g) +#=> 500 g +UnitMeasurements::Weight.parse("1/2 kg to g") +#=> 500 g +UnitMeasurements::Weight.new("½", :kg).convert_to(:g) +#=> 500 g +UnitMeasurements::Weight.parse("½ kg").convert_to(:g) +#=> 500 g +UnitMeasurements::Weight.parse("½ kg to g") +#=> 500 g +``` + +**Parse mixed fractional notations, source unit, and (or) target unit:** + +```ruby +UnitMeasurements::Weight.new("2 1/2", :kg).convert_to(:g) +#=> 2500 g +UnitMeasurements::Weight.parse("2 1/2 kg").convert_to(:g) +#=> 2500 g +UnitMeasurements::Weight.parse("2 1/2 kg to g") +#=> 2500 g +UnitMeasurements::Weight.new("2 ½", :kg).convert_to(:g) +#=> 2500 g +UnitMeasurements::Weight.parse("2 ½ kg").convert_to(:g) +#=> 2500 g +UnitMeasurements::Weight.parse("2 ½ kg to g") +#=> 2500 g +``` + +Supported special characters for fractional notations are `¼`, `½`, `¾`, `⅓`, `⅔`, `⅕`, `⅖`, `⅗`, `⅘`, `⅙`, `⅚`, `⅐`, `⅛`, `⅜`, `⅝`, `⅞`, `⅑`, `⅒`, `↉`, `⁄`. + +**Parse exponents, source unit, and (or) target unit:** + +```ruby +UnitMeasurements::Weight.new("2e+2", :kg).convert_to(:g) +#=> 200000 g +UnitMeasurements::Weight.parse("2e² kg").convert_to(:g) +#=> 200000 g +UnitMeasurements::Weight.parse("2e+2 kg to g") +#=> 200000 g +UnitMeasurements::Weight.new("2e⁺²", :kg).convert_to(:g) +#=> 200000 g +UnitMeasurements::Weight.parse("2e⁺2 kg").convert_to(:g) +#=> 200000 g +UnitMeasurements::Weight.parse("2e⁻² kg to g") +#=> 20 g +``` + +Supported special characters for exponents are `⁰`, `¹`, `²`, `³`, `⁴`, `⁵`, `⁶`, `⁷`, `⁸`, `⁹`, `⁺`, `⁻`. + +**Extract the unit and the quantity from measurement:** + +```ruby +weight = UnitMeasurements::Weight.new(1.0, :kg) +weight.quantity +#=> 0.1e1 +weight.unit +#=> # +``` + +**See all units of the unit group:** + +```ruby +UnitMeasurements::Weight.units +#=> [#, ..., ...] +``` + +**See names of all valid units of the unit group:** + +```ruby +UnitMeasurements::Weight.unit_names +#=> ["g", "kg", "lb", "oz", ...] +``` + +**See all valid units of the unit group along with their aliases:** + +```ruby +UnitMeasurements::Weight.unit_names_with_aliases +#=> ["g", "gram", "gramme", "grammes", "grams", "kg", "kilogram", "kilogramme", "kilogrammes", "kilograms", "lb", "ounce", "ounces", "oz", "pound", "pounds", ...] +``` + +**Finding units within the unit group:** + +You can use `#unit_for` or `#unit_for!` (aliased as `#[]`) to find units within +the unit group. `#unit_for!` method returns error if a unit is not present in the +unit group. + +```ruby +UnitMeasurements::Weight.unit_for(:g) +#=> # +UnitMeasurements::Weight.unit_for(:z) +#=> nil +UnitMeasurements::Weight.unit_for!(:g) +#=> # +UnitMeasurements::Weight.unit_for!(:z) +#=> Invalid unit: 'z'. (UnitMeasurements::UnitError) +``` + +**Finding whether the unit is defined within the unit group:** + +```ruby +UnitMeasurements::Weight.defined?(:g) +#=> true +UnitMeasurements::Weight.defined?(:kg) +#=> true +UnitMeasurements::Weight.defined?(:gramme) +#=> false +``` + +**Check if the unit is a valid unit or alias within the unit group:** + +```ruby +UnitMeasurements::Weight.unit_or_alias?(:g) +#=> true +UnitMeasurements::Weight.unit_or_alias?(:kg) +#=> true +UnitMeasurements::Weight.unit_or_alias?(:gramme) +#=> true +``` + +## Units + +The **`UnitMeasurements::Unit`** class is used to represent the units for a measurement. + +### SI units support + +There is support for SI units through the use of `si_unit` method. +Units declared through it will have automatic support for all SI prefixes: + +| Multiplying Factor | SI Prefix | Scientific Notation | +| ----------------------------------------- | ---------- | ------------------- | +| 1 000 000 000 000 000 000 000 000 000 000 | quetta (Q) | 10^30 | +| 1 000 000 000 000 000 000 000 000 000 | ronna (R) | 10^27 | +| 1 000 000 000 000 000 000 000 000 | yotta (Y) | 10^24 | +| 1 000 000 000 000 000 000 000 | zetta (Z) | 10^21 | +| 1 000 000 000 000 000 000 | exa (E) | 10^18 | +| 1 000 000 000 000 000 | peta (P) | 10^15 | +| 1 000 000 000 000 | tera (T) | 10^12 | +| 1 000 000 000 | giga (G) | 10^9 | +| 1 000 000 | mega (M) | 10^6 | +| 1 000 | kilo (k) | 10^3 | +| 1 00 | hecto (h) | 10^2 | +| 1 0 | deca (da) | 10^1 | +| 0.1 | deci (d) | 10^-1 | +| 0.01 | centi (c) | 10^-2 | +| 0.001 | milli (m) | 10^-3 | +| 0.000 001 | micro (µ) | 10^-6 | +| 0.000 000 001 | nano (n) | 10^-9 | +| 0.000 000 000 001 | pico (p) | 10^-12 | +| 0.000 000 000 000 001 | femto (f) | 10^-15 | +| 0.000 000 000 000 000 001 | atto (a) | 10^-18 | +| 0.000 000 000 000 000 000 001 | zepto (z) | 10^-21 | +| 0.000 000 000 000 000 000 000 001 | yocto (y) | 10^-24 | +| 0.000 000 000 000 000 000 000 000 001 | ronto (r) | 10^-27 | +| 0.000 000 000 000 000 000 000 000 000 001 | quecto (q) | 10^-30 | + +### Bundled units + +There are tons of units that are bundled in `unit_measurements`. You can check them out [here](units.md). + +### Specifing units + +By default, `unit_measurements` ships with all the unit groups and this happens automatically +when requiring the gem in the following manner. + +```ruby +require "unit_measurements" +``` + +**You can skip these unit groups and only [build your own unit groups](#building-new-unit-groups) by doing:** + +```ruby +require "unit_measurements/base" +``` + +or simply + +```ruby +gem "unit_measurements", require: "unit_measurements/base" +``` + +**You can also use unit groups in your application as per your need as:** + +```ruby +require "unit_measurements/base" + +require "unit_measurements/unit_groups/length" +require "unit_measurements/unit_groups/weight" +require "unit_measurements/unit_groups/volume" +``` + +or + +```ruby +gem "unit_measurements", require: ["unit_measurements/base", "unit_measurements/unit_groups/length"] +``` + +### Building new unit groups + +This library provides simpler way to build your own unit groups. To build new unit group, +use `UnitMeasurements.build` in order to define base units and conversion units within it: + +If you build unit using `base` method, base unit automatically gets set for the unit group. + +```ruby +UnitMeasurements::Time = UnitMeasurements.build do + # Add a base unit to the group. + base :s, aliases: [:second, :seconds] + + # Add other units to the group, along with their conversion multipliers against + # base unit. + unit :min, value: 60.0, aliases: [:minute, :minutes] + + # You can also specify conversion string if it's converted against a unit other + # than the unit group's base unit. + unit :h, value: "60 min", aliases: [:hour, :hours] +end +``` + +If the unit is supporting [si prefixes](#si-units-support), you can use `si_unit` method to build it. +If you build unit using `si_unit`, Base unit is automatically added to the group along with all SI prefixes for it. + +```ruby +UnitMeasurements::Time = UnitMeasurements.build do + # Add a SI unit to the unit group + si_unit :s, aliases: [:second, :seconds] + + unit :min, value: "60 s", aliases: [:minute, :minutes] +end +``` + +All units allow aliases, as long as they are unique. Unit symbol can be used to +define the unit as long as it is unique. All unit names are case sensitive. + +### Namespaces + +All unit groups and their definition classes are namespaced by default, but can be aliased in your application. + +```ruby +Weight = UnitMeasurements::Weight +Length = UnitMeasurements::Length +Volume = UnitMeasurements::Volume +``` + ## Contributing 1. Fork it diff --git a/units.md b/units.md new file mode 100644 index 0000000..0ab3ae4 --- /dev/null +++ b/units.md @@ -0,0 +1,32 @@ +# Bundled unit groups and units + +As there are lots of units bundled with `unit_measurements`, we recommend you to check below list of +bundled units before converting your measurements. + +**Notes:** +1. Base unit for each unit group is highlighted. +2. Unit numbers suffixed with `*` support all [SI prefixes](README.md#si-units-support). + +Below are the units which are bundled in the unit_measurements. + +## 1. Length/Distance + +These units are defined in `UnitMeasurements::Length`. + +| # | Symbol | Aliases | +|:--|:--|:--| +| **1\*** | **m** | **meter, metre, meters, metres** | +| 2 | in | ", inch, inches | +| 3 | ft | ', foot, feet | +| 4 | yd | yard, yards | +| 5 | mi | mile, miles | + +## 2. Weight/Mass + +These units are defined in `UnitMeasurements::Weight`. + +| # | Symbol | Aliases | +|--|--|--| +| **1\*** | **g** | **gram, grams, gramme, grammes** | +| 2 | q | quintal, quintals | +| 3 | t | tonne, tonnes, metric tonne, metric tonnes |