Repository: angularsen/UnitsNet (2026-07-25T21:34:50Z)

Issue:
Add Information/BitRate/Duration and Frequency/Duration unit relations
**Is your feature request related to a problem? Please describe.**
UnitsNet is missing a small but natural set of relations around information, rate, frequency, and duration.

There is currently no built-in relation for:

- `Information = BitRate * Duration`
- `1 = Frequency * Duration`
- `BitRate = Information * Frequency`

So even when a formula is semantically clear, code has to fall back to raw scalar extraction like `.Bits`, `.BitsPerSecond`, `.Seconds`, and `.Hertz`.

A practical example is monitor / display bandwidth calculation, where formulas naturally want to express refresh period from refresh rate, frequency from period, and bitrate from information-per-event and event frequency.

**Describe the solution you'd like**
Add these relations to `UnitRelations.json`:

```json
[
  "Information.Bit = BitRate.BitPerSecond * Duration.Second",
  "1 = Frequency.PerSecond * Duration.Second",
  "BitRate.BitPerSecond = Information.Bit * Frequency.PerSecond"
]
```

This would allow generated operators to keep these formulas in semantic quantity types instead of forcing manual conversion to primitive doubles.

**Describe alternatives you've considered**
The current alternative is to write the formulas manually, for example:

```csharp
BitRate.FromBitsPerSecond(information.Bits / duration.Seconds)
Duration.FromSeconds(1d / frequency.Hertz)
Frequency.FromHertz(1d / duration.Seconds)
BitRate.FromBitsPerSecond(bitsPerEvent.Bits * eventRate.Hertz)
```

This works, but it loses the clarity and composability that unit-aware types are meant to provide.

**Additional context**
One concrete use case is monitor bandwidth calculation, where formulas like these show up naturally:

- `refreshPeriod = 1 / refreshRate`
- `horizontalFrequency = 1 / horizontalPeriod`
- `bitRate = bitsPerPixel * pixelClock`
- `bitsPerPixel = bitRate / pixelClock`

Without these relations, those expressions have to be rewritten in terms of scalar property access and reconstruction.


Discussion:
Please see https://github.com/angularsen/UnitsNet/blob/master/Common/UnitRelations.json

I believe it should be fairly straight forward to add there, then run `dotnet run CodeGen` to generate the code. If you make it work, please submit a PR and I'll be happy to help get it merged.
This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.
This issue was automatically closed due to inactivity.



Patch:
diff --git a/Common/UnitRelations.json b/Common/UnitRelations.json
index 18db3c53e6..5989e280ff 100644
--- a/Common/UnitRelations.json
+++ b/Common/UnitRelations.json
@@ -2,6 +2,7 @@
   "1 = Area.SquareMeter * ReciprocalArea.InverseSquareMeter",
   "1 = Density.KilogramPerCubicMeter * SpecificVolume.CubicMeterPerKilogram",
   "1 = ElectricResistivity.OhmMeter * ElectricConductivity.SiemensPerMeter",
+  "1 = Frequency.PerSecond * Duration.Second",
   "1 = Length.Meter * ReciprocalLength.InverseMeter",
   "Acceleration.MeterPerSecondSquared = Jerk.MeterPerSecondCubed * Duration.Second",
   "AmountOfSubstance.Mole = MolarFlow.MolePerSecond * Duration.Second",
@@ -14,6 +15,7 @@
   "AreaDensity.KilogramPerSquareMeter = Density.KilogramPerCubicMeter * Length.Meter",
   "AreaMomentOfInertia.MeterToTheFourth = Area.SquareMeter * Area.SquareMeter",
   "AreaMomentOfInertia.MeterToTheFourth = Volume.CubicMeter * Length.Meter",
+  "BitRate.BitPerSecond = Information.Bit * Frequency.PerSecond",
   "double = SpecificEnergy.JoulePerKilogram * BrakeSpecificFuelConsumption.KilogramPerJoule",
   "DynamicViscosity.NewtonSecondPerMeterSquared = Density.KilogramPerCubicMeter * KinematicViscosity.SquareMeterPerSecond",
   "ElectricCharge.AmpereHour = ElectricCurrent.Ampere * Duration.Hour",
@@ -35,6 +37,7 @@
   "ForcePerLength.NewtonPerMeter = Force.Newton * ReciprocalLength.InverseMeter",
   "ForcePerLength.NewtonPerMeter = Pressure.NewtonPerSquareMeter * Length.Meter",
   "ForcePerLength.NewtonPerMeter = SpecificWeight.NewtonPerCubicMeter * Area.SquareMeter",
+  "Information.Bit = BitRate.BitPerSecond * Duration.Second",
   "KinematicViscosity.SquareMeterPerSecond = Length.Meter * Speed.MeterPerSecond",
   "Length.Meter = Area.SquareMeter * ReciprocalLength.InverseMeter",
   "Length.Meter = Speed.MeterPerSecond * Duration.Second",
diff --git a/Docs/adding-operator-overloads.md b/Docs/adding-operator-overloads.md
index 6c4acfb59c..3ea8eb80d7 100644
--- a/Docs/adding-operator-overloads.md
+++ b/Docs/adding-operator-overloads.md
@@ -5,6 +5,12 @@ There is a large number of operator overloads, to facilitate strongly typed comp
 Operators that can be expressed as a multiplication, division, or inverse relation may be generated from
 `Common/UnitRelations.json`; see [Quantity relations](quantity-and-unit-definition-schema.md#quantity-relations).
 
+For generated relation tests, use the existing relation-test ownership pattern:
+
+- Multiplication tests go in `<LeftQuantity>Tests.cs`, where `LeftQuantity` is the first operand in the explicit `UnitRelations.json` entry. Put the inferred reversed multiplication test there too.
+- Inferred division tests go in `<ResultQuantity>Tests.cs`, because the generated division operators are exposed by the result quantity.
+- Inverse relation tests go in each quantity's own test file, because each side gets its own `Inverse()` method.
+
 1. Put operator overload in `Length.extra.cs` if the **first parameter** is `Length`
 2. Add a short xmldoc summary as per the example below. You can add more descriptions if it is useful.
 3. Add a unit test case and place it in the equivalent file `LengthTests.cs`.
diff --git a/UnitsNet/GeneratedCode/Quantities/BitRate.g.cs b/UnitsNet/GeneratedCode/Quantities/BitRate.g.cs
index 818cfb5660..ce767ed7ff 100644
--- a/UnitsNet/GeneratedCode/Quantities/BitRate.g.cs
+++ b/UnitsNet/GeneratedCode/Quantities/BitRate.g.cs
@@ -43,6 +43,9 @@ namespace UnitsNet
         ILinearQuantity<BitRate, BitRateUnit>,
 #if NET7_0_OR_GREATER
         IDivisionOperators<BitRate, BitRate, double>,
+        IDivisionOperators<BitRate, Information, Frequency>,
+        IMultiplyOperators<BitRate, Duration, Information>,
+        IDivisionOperators<BitRate, Frequency, Information>,
         IComparisonOperators<BitRate, BitRate, bool>,
         IParsable<BitRate>,
 #endif
@@ -1093,6 +1096,28 @@ public static bool TryParseUnit([NotNullWhen(true)]string? str, IFormatProvider?
 
         #endregion
 
+        #region Relational Operators
+
+        /// <summary>Get <see cref="Frequency"/> from <see cref="BitRate"/> / <see cref="Information"/>.</summary>
+        public static Frequency operator /(BitRate bitRate, Information information)
+        {
+            return Frequency.FromPerSecond(bitRate.BitsPerSecond / information.Bits);
+        }
+
+        /// <summary>Get <see cref="Information"/> from <see cref="BitRate"/> * <see cref="Duration"/>.</summary>
+        public static Information operator *(BitRate bitRate, Duration duration)
+        {
+            return Information.FromBits(bitRate.BitsPerSecond * duration.Seconds);
+        }
+
+        /// <summary>Get <see cref="Information"/> from <see cref="BitRate"/> / <see cref="Frequency"/>.</summary>
+        public static Information operator /(BitRate bitRate, Frequency frequency)
+        {
+            return Information.FromBits(bitRate.BitsPerSecond / frequency.PerSecond);
+        }
+
+        #endregion
+
         #region Equality / IComparable
 
         /// <summary>Returns true if less or equal to.</summary>
diff --git a/UnitsNet/GeneratedCode/Quantities/Duration.g.cs b/UnitsNet/GeneratedCode/Quantities/Duration.g.cs
index f8ab8fb0b1..7902e4b323 100644
--- a/UnitsNet/GeneratedCode/Quantities/Duration.g.cs
+++ b/UnitsNet/GeneratedCode/Quantities/Duration.g.cs
@@ -48,6 +48,7 @@ namespace UnitsNet
         IMultiplyOperators<Duration, ElectricCurrentGradient, ElectricCurrent>,
         IMultiplyOperators<Duration, Power, Energy>,
         IMultiplyOperators<Duration, ForceChangeRate, Force>,
+        IMultiplyOperators<Duration, BitRate, Information>,
         IMultiplyOperators<Duration, Speed, Length>,
         IMultiplyOperators<Duration, MassFlow, Mass>,
         IMultiplyOperators<Duration, PressureChangeRate, Pressure>,
@@ -691,6 +692,13 @@ public static bool TryParseUnit([NotNullWhen(true)]string? str, IFormatProvider?
 
         #region Relational Operators
 
+        /// <summary>Calculates the inverse of this quantity.</summary>
+        /// <returns>The corresponding inverse quantity, <see cref="Frequency"/>.</returns>
+        public Frequency Inverse()
+        {
+            return Frequency.FromPerSecond(1 / Seconds);
+        }
+
         /// <summary>Get <see cref="Acceleration"/> from <see cref="Duration"/> * <see cref="Jerk"/>.</summary>
         public static Acceleration operator *(Duration duration, Jerk jerk)
         {
@@ -739,6 +747,12 @@ public static bool TryParseUnit([NotNullWhen(true)]string? str, IFormatProvider?
             return Force.FromNewtons(duration.Seconds * forceChangeRate.NewtonsPerSecond);
         }
 
+        /// <summary>Get <see cref="Information"/> from <see cref="Duration"/> * <see cref="BitRate"/>.</summary>
+        public static Information operator *(Duration duration, BitRate bitRate)
+        {
+            return Information.FromBits(duration.Seconds * bitRate.BitsPerSecond);
+        }
+
         /// <summary>Get <see cref="Length"/> from <see cref="Duration"/> * <see cref="Speed"/>.</summary>
         public static Length operator *(Duration duration, Speed speed)
         {
diff --git a/UnitsNet/GeneratedCode/Quantities/Frequency.g.cs b/UnitsNet/GeneratedCode/Quantities/Frequency.g.cs
index ca09928825..230957eaae 100644
--- a/UnitsNet/GeneratedCode/Quantities/Frequency.g.cs
+++ b/UnitsNet/GeneratedCode/Quantities/Frequency.g.cs
@@ -40,6 +40,7 @@ namespace UnitsNet
         ILinearQuantity<Frequency, FrequencyUnit>,
 #if NET7_0_OR_GREATER
         IDivisionOperators<Frequency, Frequency, double>,
+        IMultiplyOperators<Frequency, Information, BitRate>,
         IMultiplyOperators<Frequency, Energy, Power>,
         IComparisonOperators<Frequency, Frequency, bool>,
         IParsable<Frequency>,
@@ -661,6 +662,19 @@ public static bool TryParseUnit([NotNullWhen(true)]string? str, IFormatProvider?
 
         #region Relational Operators
 
+        /// <summary>Calculates the inverse of this quantity.</summary>
+        /// <returns>The corresponding inverse quantity, <see cref="Duration"/>.</returns>
+        public Duration Inverse()
+        {
+            return Duration.FromSeconds(1 / PerSecond);
+        }
+
+        /// <summary>Get <see cref="BitRate"/> from <see cref="Frequency"/> * <see cref="Information"/>.</summary>
+        public static BitRate operator *(Frequency frequency, Information information)
+        {
+            return BitRate.FromBitsPerSecond(frequency.PerSecond * information.Bits);
+        }
+
         /// <summary>Get <see cref="Power"/> from <see cref="Frequency"/> * <see cref="Energy"/>.</summary>
         public static Power operator *(Frequency frequency, Energy energy)
         {
diff --git a/UnitsNet/GeneratedCode/Quantities/Information.g.cs b/UnitsNet/GeneratedCode/Quantities/Information.g.cs
index 396b29890f..c84a1b36a0 100644
--- a/UnitsNet/GeneratedCode/Quantities/Information.g.cs
+++ b/UnitsNet/GeneratedCode/Quantities/Information.g.cs
@@ -40,6 +40,9 @@ namespace UnitsNet
         ILinearQuantity<Information, InformationUnit>,
 #if NET7_0_OR_GREATER
         IDivisionOperators<Information, Information, double>,
+        IMultiplyOperators<Information, Frequency, BitRate>,
+        IDivisionOperators<Information, Duration, BitRate>,
+        IDivisionOperators<Information, BitRate, Duration>,
         IComparisonOperators<Information, Information, bool>,
         IParsable<Information>,
 #endif
@@ -1076,6 +1079,28 @@ public static bool TryParseUnit([NotNullWhen(true)]string? str, IFormatProvider?
 
         #endregion
 
+        #region Relational Operators
+
+        /// <summary>Get <see cref="BitRate"/> from <see cref="Information"/> * <see cref="Frequency"/>.</summary>
+        public static BitRate operator *(Information information, Frequency frequency)
+        {
+            return BitRate.FromBitsPerSecond(information.Bits * frequency.PerSecond);
+        }
+
+        /// <summary>Get <see cref="BitRate"/> from <see cref="Information"/> / <see cref="Duration"/>.</summary>
+        public static BitRate operator /(Information information, Duration duration)
+        {
+            return BitRate.FromBitsPerSecond(information.Bits / duration.Seconds);
+        }
+
+        /// <summary>Get <see cref="Duration"/> from <see cref="Information"/> / <see cref="BitRate"/>.</summary>
+        public static Duration operator /(Information information, BitRate bitRate)
+        {
+            return Duration.FromSeconds(information.Bits / bitRate.BitsPerSecond);
+        }
+
+        #endregion
+
         #region Equality / IComparable
 
         /// <summary>Returns true if less or equal to.</summary>


Test patch:
diff --git a/UnitsNet.Tests/CustomCode/BitRateTests.cs b/UnitsNet.Tests/CustomCode/BitRateTests.cs
index 8a7f25e800..8f9e6b4166 100644
--- a/UnitsNet.Tests/CustomCode/BitRateTests.cs
+++ b/UnitsNet.Tests/CustomCode/BitRateTests.cs
@@ -22,6 +22,7 @@
 
 
 using System;
+using Xunit;
 
 namespace UnitsNet.Tests.CustomCode
 {
@@ -72,5 +73,37 @@ public class BitRateTests : BitRateTestsBase
         protected override double ExbibitsPerSecondInOneBitPerSecond => 8.67361738E-19;
         protected override double ExbibytesPerSecondInOneBitPerSecond => 1.0842021724855E-19;
         protected override double ExbioctetsPerSecondInOneBitPerSecond => 1.0842021724855E-19;
+
+        [Fact]
+        public void BitRateTimesDurationEqualsInformation()
+        {
+            Information information = BitRate.FromKilobitsPerSecond(10) * Duration.FromSeconds(3);
+
+            Assert.Equal(30000, information.Bits);
+        }
+
+        [Fact]
+        public void DurationTimesBitRateEqualsInformation()
+        {
+            Information information = Duration.FromMilliseconds(500) * BitRate.FromKilobitsPerSecond(10);
+
+            Assert.Equal(5000, information.Bits);
+        }
+
+        [Fact]
+        public void BitRateDividedByFrequencyEqualsInformation()
+        {
+            Information information = BitRate.FromKilobitsPerSecond(16) / Frequency.FromHertz(2);
+
+            Assert.Equal(8000, information.Bits);
+        }
+
+        [Fact]
+        public void BitRateDividedByInformationEqualsFrequency()
+        {
+            Frequency frequency = BitRate.FromKilobitsPerSecond(16) / Information.FromKilobytes(1);
+
+            Assert.Equal(2, frequency.Hertz);
+        }
     }
 }
diff --git a/UnitsNet.Tests/CustomCode/DurationTests.cs b/UnitsNet.Tests/CustomCode/DurationTests.cs
index 9753a43986..e895f171c3 100644
--- a/UnitsNet.Tests/CustomCode/DurationTests.cs
+++ b/UnitsNet.Tests/CustomCode/DurationTests.cs
@@ -242,5 +242,13 @@ public void DurationTimesForceChangeRate()
             Force force = Duration.FromSeconds(10) * ForceChangeRate.FromNewtonsPerSecond(100);
             Assert.Equal(Force.FromNewtons(1000), force);
         }
+
+        [Fact]
+        public void DurationInverseEqualsFrequency()
+        {
+            Frequency frequency = Duration.FromMilliseconds(250).Inverse();
+
+            Assert.Equal(4, frequency.Hertz);
+        }
     }
 }
diff --git a/UnitsNet.Tests/CustomCode/FrequencyTests.cs b/UnitsNet.Tests/CustomCode/FrequencyTests.cs
index e5f76d64a4..b46840ad77 100644
--- a/UnitsNet.Tests/CustomCode/FrequencyTests.cs
+++ b/UnitsNet.Tests/CustomCode/FrequencyTests.cs
@@ -2,6 +2,7 @@
 // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet.
 
 using System;
+using Xunit;
 
 namespace UnitsNet.Tests
 {
@@ -30,5 +31,13 @@ public class FrequencyTests : FrequencyTestsBase
         protected override double RadiansPerSecondInOneHertz => 2 * Math.PI;
 
         protected override double BeatsPerMinuteInOneHertz => 60;
+
+        [Fact]
+        public void FrequencyInverseEqualsDuration()
+        {
+            Duration duration = Frequency.FromHertz(2).Inverse();
+
+            Assert.Equal(0.5, duration.Seconds);
+        }
     }
 }
diff --git a/UnitsNet.Tests/CustomCode/InformationTests.cs b/UnitsNet.Tests/CustomCode/InformationTests.cs
index 3cd13ab2c4..ae392d68b9 100644
--- a/UnitsNet.Tests/CustomCode/InformationTests.cs
+++ b/UnitsNet.Tests/CustomCode/InformationTests.cs
@@ -91,5 +91,37 @@ public void OneKBHas1000Bytes()
         {
             Assert.Equal(1000, Information.FromKilobytes(1).Bytes);
         }
+
+        [Fact]
+        public void InformationDividedByDurationEqualsBitRate()
+        {
+            BitRate bitRate = Information.FromKilobytes(3) / Duration.FromSeconds(2);
+
+            Assert.Equal(12000, bitRate.BitsPerSecond);
+        }
+
+        [Fact]
+        public void InformationDividedByBitRateEqualsDuration()
+        {
+            Duration duration = Information.FromMegabits(3) / BitRate.FromMegabitsPerSecond(2);
+
+            Assert.Equal(1.5, duration.Seconds);
+        }
+
+        [Fact]
+        public void InformationTimesFrequencyEqualsBitRate()
+        {
+            BitRate bitRate = Information.FromBytes(100) * Frequency.FromHertz(2);
+
+            Assert.Equal(1600, bitRate.BitsPerSecond);
+        }
+
+        [Fact]
+        public void FrequencyTimesInformationEqualsBitRate()
+        {
+            BitRate bitRate = Frequency.FromHertz(2) * Information.FromBytes(100);
+
+            Assert.Equal(1600, bitRate.BitsPerSecond);
+        }
     }
 }