Releases: nholthaus/units
v3.3.0
Corrected Semantics for Ratio-Dimensionless Units
This release fixes multiple inconsistencies in the handling of ratio-dimensionless units
(e.g. percent, ppm, ppb) that previously could lead to silent precision loss, unit-scale loss,
or incorrect results in compound expressions.
These changes primarily affect arithmetic involving:
- integral-backed ratio-dimensionless units,
- compound units such as
ppb / yr, - accumulation over time or other dimensions.
1) Normalization and truncation behavior (fixed)
Previous behavior
Ratio-dimensionless units normalize to a fraction (e.g. 50% → 0.5), but that normalization could
happen in the unit’s underlying type. With integral underlying types, this could silently truncate.
Example:
percent<int>(50).value() == 0 // truncated from 0.5This truncation could then propagate into arithmetic and comparisons.
Current behavior
Ratio-dimensionless units normalize in floating-point space when needed, so value() preserves the
fractional meaning regardless of underlying type:
percent<int>(50).value() == 0.52) Ratio scale could be lost in compound units (fixed)
Previous behavior
Expressions that form compound units from ratio-dimensionless numerators could discard the ratio
scale during type formation. For example, in:
ppb / yrthe result could behave as if it were effectively 1 / yr (dimensionally correct, numerically wrong).
This could surface when multiplying back by time:
(ppb_per_year * yr) // could fail to round-trip to ppb semanticsCurrent behavior
Compound units preserve the ratio-dimensionless scale, so:
(ppb / yr) * yr -> ppbwith the correct magnitude.
3) Mixed “points space” vs “fraction space” outcomes (fixed)
Ratio-dimensionless units have two relevant representations:
- raw(): the stored “points” (e.g.
50_pct.raw() == 50) - value(): the normalized fraction (e.g.
50_pct.value() == 0.5)
Previous behavior
Semantically similar expressions could produce different results depending on which overloads were
selected (and thus whether raw() or value() was used internally).
Example class of issue:
dimensionless(1.0) / 50_pct // not necessarily consistent with
1.0 / 50_pctCurrent behavior
Scalar interactions with ratio-dimensionless units consistently use normalized values where a scalar
interpretation is intended, while unit-unit operations preserve ratio semantics until explicitly converted.
As a result, equivalent expressions remain equivalent in practice.
4) Compound assignment on ratio-dimensionless units is now defined
Previous behavior
Compound assignment operators (e.g. +=, *=, /=) could inherit the same inconsistencies described
above, especially for integral-backed ratio-dimensionless units.
Current behavior
Compound assignment behavior is explicitly defined so that:
- arithmetic is performed in the correct semantic domain (normalized when appropriate),
- the stored representation remains consistent and deterministic.
Summary of what changed for users
Before this release:
- Integral-backed ratio-dimensionless values could silently change meaning due to truncation.
- Compound unit formation could preserve dimensional correctness but lose numeric scale.
- Equivalent expressions could yield different results based on operand ordering and overload selection.
After this release:
- Ratio-dimensionless values are normalized consistently and without truncation.
- Ratio scale is preserved through compound unit arithmetic (e.g. rates like
ppb/yr). - Equivalent expressions are consistent in both type behavior and numeric results.
v3.2.0
v3.1.1
v2.3.5
v3.1.0-beta
What's Changed
New Features:
- Uplift to C++23
- Alias template CTAD (
meters lengthinstead ofmeters<double> length) - ADL Improvements
- SFINAE replaced by concepts where appropriate
- NAN support
constexprcmath functions- Many new units (
jerk, radiometric units, etc) - unit constants (
1.0 * km == kilometers(1.0))
Fixes:
- ternary operator works as expected
- cmake: Allow to use existing googletest package by @krf in #310
- Fix compile error under GCC-12 by @krf in #309
- MSVC-specific Empty Baseclass Optimization activation. by @Guillaume227 in #317
- Fix mil definition by @ts826848 in #321
- -Wshadow compilation warning fix - use -Wshadow for compiling tests by @Guillaume227 in #318
- introduce UNIT_NO_LITERAL_SUPPORT directive by @Guillaume227 in #316
New Contributors
- @krf made their first contribution in #310
- @Guillaume227 made their first contribution in #317
Full Changelog: v3.0.0.beta.2...v3.1.0-beta
v2.3.4
What's Changed
- Updated CMake policy version to resolve deprecation warning. by @joe-barnier in #337
- Added namePlural to UNIT_ADD_NAME by @raulcalvo in #338
- Some numeric_limits and numeric helper functions were missing by @siposcsaba89 in #331
- Add #undef PI by @codereptile in #336
- Avoid pascal macro conflict when working on mingw by @raghunandannm in #325
- Using intmax_t instead of size_t fixes narrowing conversion on 32bit by @pmaciol in #322
- Add jerk and RPS units by @AhmedAredah in #327
- Add inch_of_mercury by @adamdoda in #326
New Contributors
- @joe-barnier made their first contribution in #337
- @raulcalvo made their first contribution in #338
- @siposcsaba89 made their first contribution in #331
- @codereptile made their first contribution in #336
- @raghunandannm made their first contribution in #325
- @pmaciol made their first contribution in #322
- @AhmedAredah made their first contribution in #327
- @adamdoda made their first contribution in #326
Full Changelog: v2.3.3...v2.3.4
v3.0.0 Beta 2
What's Changed
- Fix incorrect math function output for scaled dimensionless types, v3.x edition by @ts826848 in #295
- fix locale unit test
- update gtest cmake
- add vs2019 to appveyor
- make
.value()and.to<>()behave the same for scaled dimensionless units - ensure consistency and testing of scaled dimensionless unit math functions
Full Changelog: v3.0.0.beta...v3.0.0.beta.2
v2.3.3
v3.0.0 Beta
- added support for nlohmann json
- NaN support
- add
Galunit of acceleration - improved stream operators