Search by

sandritsch91 / php-science

Sandritsch91

Exact, dimension-aware physical quantities and SI units for PHP

Package info

github.com/Sandritsch91/php-sience

pkg:composer/sandritsch91/php-science

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-11-03 20:28 UTC

This package is auto-updated.

Last update: 2026-11-03 20:35:57 UTC


README

Exact, dimension-aware physical quantities and SI units for PHP 8.4+.

Values are stored as arbitrary-precision decimals (BcMath\Number), so 0.1 m + 0.2 m is exactly 0.3 m and converting between prefixes never introduces floating-point noise.

Documentation

The full documentation is in docs/:

This README is a short overview.

Installation

composer require sandritsch91/php-science

Requires PHP 8.4 or newer with the bcmath extension.

Usage

use sandritsch91\phpScience\BaseQuantities\Length;
use sandritsch91\phpScience\BaseQuantities\Time;
use sandritsch91\phpScience\MetricPrefix;
use sandritsch91\phpScience\Quantity;

$distance = new Length(150, 'km');          // or new Length(150, MetricPrefix::KILO)
$duration = new Time(2, 'h');

$speed = $distance->divide($duration);      // Velocity: 75km/h
echo $speed->convertTo('m/s', 3);            // 20.833m/s

// Arithmetic picks the right class from the resulting dimension.
$force = Quantity::of(2, 'kg')->multiply(Quantity::of('9.81', 'm/s²')); // Force: 19.62kg·m/s²
echo $force->convertTo('N');                 // 19.62N

// Ohm's law and other circuit formulas work the same way.
$resistance = Quantity::of(230, 'V')->divide(Quantity::of('0.5', 'A')); // Resistance: 460V/A
echo $resistance->convertTo('kΩ');           // 0.46kΩ

// Addition and comparison work across units.
echo (new Length(1, 'km'))->add(new Length(250));           // 1.25km
var_dump((new Time(1, 'h'))->isGreaterThan(new Time(59, 'min'))); // true

All quantities are immutable: convertTo(), add(), round() and friends return new instances.

Units

Unit strings are parsed by Unit::parse():

Syntax Example
Prefixed unit km, mg, µs (or us), dam
Quotient m/s, km/h, J/(mol·K)
Product N·m, N*m, kg m
Exponents m², m^2, s⁻¹, s^-1, 1/s

Known units: the SI base units m, g (so kg works), s, A, K, mol, cd; the derived units Hz, N, Pa, J, W, C, V, Ω (or ohm), S, F, H, Wb, T, kat, Bq, Gy, Sv, lm, lx, rad, sr; and min, h, d and L. All 24 SI prefixes from quecto (q) to quetta (Q) are supported.

Symbols are case-sensitive: S is siemens and s second, H henry and h hour. T on its own is tesla, while TJ or Ts still mean terajoule and terasecond.

Quantities

Base Derived
Length, Mass, Time, ElectricCurrent, Temperature, AmountOfSubstance, LuminousIntensity Area, Volume, Velocity, Acceleration, Frequency, Force, Pressure, Energy, Power, ElectricCharge, Voltage, Resistance, Conductance, Capacitance, Inductance, MagneticFlux, MagneticFluxDensity, Density, Momentum, MolarMass, Concentration, CatalyticActivity, SpecificEnergy, Illuminance, Torque, Radioactivity, AbsorbedDose, DoseEquivalent, LuminousFlux, Angle, SolidAngle

Any other combination becomes a plain Quantity. Use Quantity::register(MyQuantity::class) to add your own.

Quantities that share a dimension

A dimension alone cannot tell some quantities apart: torque and energy are both kg·m²/s², radioactivity and frequency are both s⁻¹. Each dimension therefore has one primary class, which arithmetic returns, and may have alternatives:

Dimension Primary Alternatives
kg·m²/s² Energy Torque
s⁻¹ Frequency Radioactivity (Bq)
m²/s² SpecificEnergy AbsorbedDose (Gy), DoseEquivalent (Sv)
cd LuminousIntensity LuminousFlux (lm)
dimensionless plain Quantity Angle (rad), SolidAngle (sr)
Quantity::of(5, 'kBq');                      // Radioactivity: the unit names the kind
Quantity::of(5, '1/s');                      // Frequency: the primary class
$torque = $force->multiply($arm)->as(Torque::class); // Force × Length is an Energy until reinterpreted

$torque->add(new Energy(1));                 // IncompatibleQuantityKindException
new Frequency(1, 'Bq');                      // IncompatibleQuantityKindException: Bq is reserved for radioactivity
  • Units reserved for one kind (Bq, Gy, Sv, lm, lx, rad, sr) make Quantity::of() pick that class and cannot be used by other classes. Only a single unit names a kind: kBq does, Bq·s and rad/s do not.
  • as() reinterprets a quantity as another class of the same dimension, keeping its value and unit.
  • Different kinds cannot be added, subtracted or compared, and equals() returns false for them. A plain Quantity is compatible with every kind.
  • Quantity::register($class, primary: true) makes a class primary; without primary, the first class registered for a dimension is primary. Quantity::classesFor($dimension) lists all classes of a dimension.

Precision

Methods that divide (divide(), convertTo(), and add()/subtract() when units differ) take an optional $scale, the number of fractional digits, rounded half away from zero. Without it, results that terminate (such as all prefix conversions) are exact, and results that don't (such as 1/3 or minutes to hours) keep 10 extra digits. Comparisons (equals(), compareTo(), ...) are always exact. Use round() to round explicitly.

Errors

Every exception implements sandritsch91\phpScience\Exception\ExceptionInterface:

  • InvalidUnitException: unknown unit or prefix, or a prefix on a unit that does not accept one (kh)
  • InvalidValueException: the value is not a finite number
  • IncompatibleDimensionException: e.g. adding a length to a time
  • IncompatibleQuantityKindException: same dimension but different kinds, e.g. adding an energy to a torque

Division by zero throws PHP's DivisionByZeroError.

Limitations

  • Temperature scales with an offset (°C, °F) are not supported; use kelvin.
  • Degrees are not supported yet: their factor π/180 has no exact decimal value. Use radians.
  • Arithmetic always returns the primary class, so a result can be of the wrong kind. For example, lx × m² gives a LuminousIntensity rather than a LuminousFlux, and rad/s gives a Frequency. Use as() to correct it. Luminance (cd/m²) and angular velocity are not modelled separately.

Development

composer install
composer test

License

MIT, see LICENSE.