Quantify – Type-Safe Units of Measurement with an Elegant API

I’m excited to share a new package I’ve been working on: quantify ! It’s a type-safe units of measurement library for Dart, designed to make working with physical units safer, more readable, and efficient.

Motivation & Goals:

  • True Type Safety: I wanted to catch unit mismatch errors at compile-time, not runtime. No more accidentally adding meters to seconds!

  • Intuitive API: This project was also a bit of an API proof of concept for me. I really wanted to explore Dart’s extension methods to create a fluent and natural syntax. Think 10.m (for meters) or myLength.inKm. It was a fun experiment that I think paid off!

  • Performance: Unit conversions should be fast. quantify uses double for precision and ensures that most conversions are just a single multiplication, thanks to pre-calculated factors.

  • Operator Overloading: Performing arithmetic with quantities should feel natural, like distanceA + distanceB.

  • Easy String Representation: Getting a nicely formatted string with the value and unit symbol (e.g., “1.5 km”) should be straightforward and configurable.

What quantify offers (v0.1.0):
Since this initial version focuses on establishing the core API and type safety, the number of units is currently limited. Many more are planned for future releases

  • :white_check_mark: Type-Safe Quantities: Classes like Length, Time, Temperature, and Pressure ensure you’re working with the correct types.

  • Elegant Syntax:

final pathA = 1500.m; // Create with ease using short extensions
final pathB = 2.5.km;
double pathAInMiles = pathA.inMi; // Get value in another unit
Length pathBAsYards = pathB.asYd; // Get a new Quantity object
  • :locked: Immutable: Quantity objects are immutable for safer code.

  • :artist_palette: Highly Configurable toString():

print(pathA.toString(targetUnit: LengthUnit.kilometer, fractionDigits: 1));
// Output: "1.5 km"

// With non-breaking space and locale support (if you use 'intl')
print(pathA.toString(
  targetUnit: LengthUnit.yard,
  unitSymbolSeparator: '\u00A0', // Non-breaking space
  locale: 'de_DE', // For German number formatting
  fractionDigits: 0,
));
// Output: "1640 yd" (approx, German locale might show "1.640 yd" if grouping is active)
  • :plus: Arithmetic Operations:
final totalDistance = pathA + pathB; // pathB is auto-converted
print(totalDistance.toString(fractionDigits: 0)); // "4000 m"
  • ↔️ Comparisons & Sorting: Quantities are Comparable.

Get Started:

As this is an early version and an API exploration, your feedback is especially valuable. Bug reports, or feature requests (especially for new units!) are highly welcome. Please open an issue or a PR on GitHub.

Happy coding!
Philipp

1 Like

Hi everyone,

I’ve just released quantify v0.2.0 , and it’s a big step forward, especially in terms of unit coverage!

A huge thank you to everyone who showed interest in v0.1.0.

What’s New in v0.2.0?

Version 0.2.0 completes the foundation with all 7 SI base units now implemented.
(International System of Units: SI from French Système international d’unités)

  • All 7 SI Base Units Now Supported!

    • :white_check_mark: Mass: kg (kilogram), g, mg, t (tonne), lb, oz, st (stone), slug

    • :white_check_mark: Amount of Substance (Molar Amount): mol (mole), mmol, µmol, nmol, pmol, kmol

    • :white_check_mark: Electric Current: A (ampere), mA, µA, nA, kA

    • :white_check_mark: Luminous Intensity: cd (candela), mcd, kcd

    • These join the already existing:

      • :white_check_mark: Length: m, km, ft, mi, etc.
      • :white_check_mark: Time: s, min, h, d, etc.
      • :white_check_mark: Temperature: K, °C, °F
  • Core Principles:

    • Type Safety: Still a top priority. No accidental mixing of kilograms and amperes!

    • Intuitive API: The fluent syntax like 10.kg or myCurrent.inMilliamperes is now available for all new base units.

    • Performance & Immutability: These remain core tenets.

    • Configurable toString(): Works seamlessly with all new units.

I’m excited about the library’s progress! Your feedback, bug reports, feature requests (especially for new derived units!), and contributions are always welcome. Please open an issue or PR on GitHub.

Quantify v0.9.0 is here with new units and relational operators

1. Relational Operators (>, <, >=, <=) for everything!

// Before
if (oneMile.compareTo(oneKm) > 0)
// Now
if (oneMile > oneKm)

2. Clear Distinction: Magnitude vs. Strict Equality

To avoid confusion, quantify now offers clear tools for different kinds of equality checks. isEquivalentTo checks for the same physical amount, while the standard == operator checks for identical value and unit.

// Magnitude check: Do they represent the same distance?
print(1.m.isEquivalentTo(100.cm)); // true

// Strict check: Are they represented in the exact same way?
print(1.m == 100.cm);              // false (different units)

3. Unit Expansion (from 4 to 17+ quantity types!)

  • :balance_scale: Mass : kg, g, lb, oz, tonne, stone, and more.
  • :high_voltage: Electric Current : A (ampere), mA, µA, kA.
  • :test_tube: Amount of Substance : mol, mmol, µmol.
  • :light_bulb: Luminous Intensity : cd (candela), mcd.
  • :triangular_ruler: Angle : rad (radians), deg (degrees), rev (revolutions), arcmin.
  • :counterclockwise_arrows_button: Angular Velocity : rpm, rad/s, deg/s.
  • :racing_car: Speed / Velocity : m/s, km/h, mph, kn (knots).
  • :fast_forward_button: Acceleration : m/s², g (standard gravity).
  • :flexed_biceps: Force : N (newton), lbf (pound-force), kgf.
  • :globe_showing_europe_africa: Area : m², km², ha (hectare), acre, ft².
  • :droplet: Volume : m³, L (liter), mL, gal (gallon), fl-oz, tsp, tbsp.
  • :musical_note: Frequency : Hz, MHz, GHz, rpm, bpm (beats per minute).
  • :battery: Electric Charge : C (coulomb), Ah (ampere-hour), e (elementary charge).
  • :globe_with_meridians: Solid Angle : sr (steradian), deg² (square degree).

Practical Examples

1. :battery: ElectricCharge - Estimating Battery Runtimes

// A power bank with a capacity of 10,000 mAh (or 10 Ah)
final powerBankCapacity = 10.ah;

// A device drawing a constant 500 milliamperes
final deviceCurrent = 500.mA;

// How long will the power bank last? (Time = Charge / Current)
final runtime = powerBankCapacity.timeFor(deviceCurrent);

print('Runtime for a ${deviceCurrent.asMilliamperes}: '
      '${runtime.toString(targetUnit: TimeUnit.hour)}');
// Output: Runtime for a 500.0 mA: 20.0 h

2. :pizza: Area - Scientifically Settle the Pizza Debate

Which is a better deal: one 18-inch pizza or two 12-inch pizzas?

With the new > operator, the code reads just like the question.

import 'dart:math' as math;

// Define our pizza diameters
final lDiameter = 18.inch;
final mDiameter = 12.inch;

// Calculate the area of each (Area = π * r²)
final pizza18 = Area(math.pi * math.pow(lDiameter.inInch / 2, 2), AreaUnit.squareInch);
final pizza12 = Area(math.pi * math.pow(mDiameter.inInch / 2, 2), AreaUnit.squareInch);

print('One 18-inch pizza: ${pizza18.toString(fractionDigits: 0)}');
print('Two 12-inch pizzas: ${(pizza12 * 2).toString(fractionDigits: 0)}');

if (pizza18 > pizza12 * 2) {
  print(
    'The 18-inch pizza is MORE pizza! '
    '(By ${(pizza18 - pizza12 * 2).toString(fractionDigits: 0)})',
  );
}

// Output:
// One 18-inch pizza: 254 in²
// Two 12-inch pizzas: 226 in²
// The 18-inch pizza is MORE pizza! (By 28 in²)

Your feedback and ideas for more fun examples are always welcome.

1 Like