Skip to main content
Univerto uses a fraction-based internal representation to maintain precision throughout the conversion process, avoiding the common floating-point errors that plague JavaScript arithmetic.

The Floating Point Problem

JavaScript uses IEEE 754 double-precision floating-point numbers, which can lead to unexpected results:
// Classic floating-point error
console.log(0.1 + 0.2) // 0.30000000000000004 ❌

// Multiplication errors
console.log(0.1 * 3) // 0.30000000000000004 ❌

// Division precision loss
console.log(1 / 3) // 0.3333333333333333 (truncated)
These errors compound during unit conversions, especially when dealing with complex conversion factors:
// Without Univerto - potential precision issues
const meters = (5 * 1609.344) // miles to meters
const feet = meters * 3.28084  // meters to feet
// Multiple floating-point operations = compounding errors

Fraction-Based Representation

Univerto solves this by representing all values and conversion factors as fractions (rational numbers) internally.

The Rational Class

At the core of Univerto’s precision system is the Rational class:
class Rational {
  private numerator: number
  private denominator: number

  constructor(numerator: number, denominator: number) {
    this.numerator = numerator
    this.denominator = denominator
  }

  multiply(other: Rational) {
    return new Rational(
      this.numerator * other.numerator,
      this.denominator * other.denominator
    )
  }

  divide(other: Rational) {
    return new Rational(
      this.numerator * other.denominator,
      this.denominator * other.numerator
    )
  }

  toNumber() {
    return this.numerator / this.denominator
  }
}
The full implementation can be found in ~/workspace/source/src/features/core/utils/rational/index.ts:1-42

How It Works

Instead of storing 0.333..., Univerto stores 1/3 as { numerator: 1, denominator: 3 }. Example: Time Conversion Let’s trace how Univerto converts 1 hour to milliseconds:
import { TIME_UNIT, TimeUnitConverter } from 'univerto/time'

const ms = TimeUnitConverter.from(1, TIME_UNIT.HOUR)
  .to(TIME_UNIT.MILLISECOND)
  .convert()
Internal process:
  1. The input value (1) is converted to a Rational: { numerator: 1, denominator: 1 }
  2. The hour scale factor is: { numerator: 3600000, denominator: 1 }
  3. The millisecond scale factor is: { numerator: 1, denominator: 1 }
  4. Division creates the conversion factor:
    hour.divide(millisecond) = { numerator: 3600000, denominator: 1 }
    
  5. Multiplication with input:
    { numerator: 1, denominator: 1 } × { numerator: 3600000, denominator: 1 }
    = { numerator: 3600000, denominator: 1 }
    
  6. Final conversion to number: 3600000 / 1 = 3600000

Maintaining Precision During Conversions

All arithmetic operations use fraction math, not floating-point math:
function convertToRational() {
  const from = scale[fromUnit]        // Rational
  const to = scale[targetUnit]        // Rational
  const fromRational = Rational.fromNumber(fromQuantity) // Rational
  const factor = from.divide(to)      // Rational division
  return fromRational.multiply(factor) // Rational multiplication
}
Only the final step converts to a JavaScript number:
function convert() {
  const result = convertToRational() // All operations in Rational
  return result.toNumber()           // Single conversion to float
}
This means:
  • No intermediate rounding errors
  • No accumulation of floating-point imprecision
  • Exact arithmetic until the very end

Scale Definitions

Each unit category defines its units as rational numbers relative to a base unit:
// Time units defined as fractions
const MS = new Rational(1, 1)           // Base unit: millisecond
const SEC = scaleUnit(MS, 1000)         // 1000ms = 1s
const MIN = scaleUnit(SEC, 60)          // 60s = 1min
const HOUR = scaleUnit(MIN, 60)         // 60min = 1hr
const DAY = scaleUnit(HOUR, 24)         // 24hr = 1day

// Nanosecond requires division
const NS = scaleUnit(MS, 1, 1000)       // 1ms = 1000ns
The scaleUnit helper creates new Rational instances by multiplying:
function scaleUnit(base: Rational, numerator: number, denominator: number = 1) {
  return base.multiply(new Rational(numerator, denominator))
}
See ~/workspace/source/src/features/core/utils/scale-unit/index.ts:3-5 and ~/workspace/source/src/features/time/constants/time-scale.ts:6-32

Precision Benefits in Action

Example 1: Complex Time Conversion

import { TIME_UNIT, TimeUnitConverter } from 'univerto/time'

// Convert 1 nanosecond to hours
const hours = TimeUnitConverter.from(1, TIME_UNIT.NANOSECOND)
  .to(TIME_UNIT.HOUR)
  .convert()

// Internally: (1/1) × (1/1000000000) × (3600000/1)^-1
// Precise fraction arithmetic throughout

Example 2: Avoiding Common Pitfalls

import { LENGTH_UNIT, LengthUnitConverter } from 'univerto/length'

// Traditional JS (with errors):
const traditionalResult = (1 / 3) * 3 // 0.9999999999999999

// Univerto (precise):
const fraction = LengthUnitConverter.from(1, LENGTH_UNIT.METER)
  .to(LENGTH_UNIT.CENTIMETER)
  .convertToFraction()
// Exact fraction until you need the decimal

When Precision Matters Most

  • Scientific calculations: Precise measurements and conversions
  • Financial applications: Currency conversions, billing
  • Engineering: CAD/CAM systems, simulations
  • Data processing: Large-scale conversions where errors compound

Accessing the Fraction Directly

If you need to maintain precision beyond Univerto’s conversion, use convertToFraction():
const fraction = TimeUnitConverter.from(1, TIME_UNIT.HOUR)
  .to(TIME_UNIT.MILLISECOND)
  .convertToFraction()

console.log(fraction) // { numerator: 3600000, denominator: 1 }
You can then pass this fraction to arbitrary-precision libraries for further processing. See High-Precision Arithmetic for details.

Implementation Reference

The precision system is implemented across several core files:
  • Rational class: ~/workspace/source/src/features/core/utils/rational/index.ts:1-42
  • Conversion logic: ~/workspace/source/src/features/core/utils/create-precise-converter/index.ts:10-30
  • Scale utilities: ~/workspace/source/src/features/core/utils/scale-unit/index.ts:3-5

Build docs developers (and LLMs) love