sandritsch91 / php-science
Exact, dimension-aware physical quantities and SI units for PHP
Requires
- php: >=8.4.0
- ext-bcmath: *
Requires (Dev)
- phpunit/phpunit: ^13.4.6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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/:
- Getting started
- Quantities · Units and prefixes · Conversion and precision
- Arithmetic and comparison · Quantities that share a dimension
- Extending the library · Errors · Reference
- Internals and contributing
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) makeQuantity::of()pick that class and cannot be used by other classes. Only a single unit names a kind:kBqdoes,Bq·sandrad/sdo 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()returnsfalsefor them. A plainQuantityis compatible with every kind. Quantity::register($class, primary: true)makes a class primary; withoutprimary, 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 numberIncompatibleDimensionException: e.g. adding a length to a timeIncompatibleQuantityKindException: 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
LuminousIntensityrather than aLuminousFlux, and rad/s gives aFrequency. Useas()to correct it. Luminance (cd/m²) and angular velocity are not modelled separately.
Development
composer install
composer test
License
MIT, see LICENSE.