Skip to content

Commit b0361ba

Browse files
authored
Normalize Temperature arithmetic (#550)
* Temperature: Generate default arithmetic * Remove Temperature.Multiply/Divide and - operator methods These are instead handled by standard arithmetic, now generated for Temperature. * Add Temperature.ToDelta(other) instance method * Add TemperatureDelta.FromTemperatures(left, right) static method * Add ToDeltaUnit() Use ToString() and Enum.Parse() to get slightly more robust conversion without having to generate a switch statement for all cases.
1 parent 03680d1 commit b0361ba

File tree

5 files changed

+93
-98
lines changed

5 files changed

+93
-98
lines changed

Common/UnitDefinitions/Temperature.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"Name": "Temperature",
33
"BaseUnit": "Kelvin",
44
"XmlDoc": "A temperature is a numerical measure of hot or cold. Its measurement is by detection of heat radiation or particle velocity or kinetic energy, or by the bulk behavior of a thermometric material. It may be calibrated in any of various temperature scales, Celsius, Fahrenheit, Kelvin, etc. The fundamental physical definition of temperature is provided by thermodynamics.",
5-
"GenerateArithmetic": false,
65
"BaseDimensions": {
76
"Θ": 1
87
},
@@ -104,4 +103,4 @@
104103
]
105104
}
106105
]
107-
}
106+
}

UnitsNet.Tests/CustomCode/TemperatureDeltaTests.cs

+26-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//------------------------------------------------------------------------------
22
// <auto-generated>
33
// This code was generated (once) by \generate-code.bat, but will not be
4-
// regenerated when it already exists. The purpose of creating this file is to make
4+
// regenerated when it already exists. The purpose of creating this file is to make
55
// it easier to remember to implement all the unit conversion test cases.
6-
//
6+
//
77
// Whenever a new unit is added to this unit class and \generate-code.bat is run,
88
// the base test class will get a new abstract property and cause a compile error
99
// in this derived class, reminding the developer to implement the test case
@@ -19,17 +19,17 @@
1919

2020
// Copyright (c) 2013 Andreas Gullberg Larsen ([email protected]).
2121
// https://github.com/angularsen/UnitsNet
22-
//
22+
//
2323
// Permission is hereby granted, free of charge, to any person obtaining a copy
2424
// of this software and associated documentation files (the "Software"), to deal
2525
// in the Software without restriction, including without limitation the rights
2626
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2727
// copies of the Software, and to permit persons to whom the Software is
2828
// furnished to do so, subject to the following conditions:
29-
//
29+
//
3030
// The above copyright notice and this permission notice shall be included in
3131
// all copies or substantial portions of the Software.
32-
//
32+
//
3333
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3434
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3535
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -38,6 +38,8 @@
3838
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
3939
// THE SOFTWARE.
4040
using System;
41+
using System.Globalization;
42+
using UnitsNet.Units;
4143
using Xunit;
4244

4345
namespace UnitsNet.Tests.CustomCode
@@ -59,5 +61,24 @@ public void TemperatureDeltaTimesSpecificEntropyEqualsSpecificEnergy()
5961
SpecificEnergy specificEnergy = SpecificEntropy.FromJoulesPerKilogramKelvin(10) * TemperatureDelta.FromKelvins(6);
6062
Assert.Equal(specificEnergy, SpecificEnergy.FromJoulesPerKilogram(60));
6163
}
64+
65+
[Theory]
66+
[InlineData(TemperatureUnit.DegreeCelsius, 30, 20, "10 ∆°C")]
67+
[InlineData(TemperatureUnit.DegreeCelsius, 20, 30, "-10 ∆°C")]
68+
[InlineData(TemperatureUnit.DegreeFahrenheit, 30, 20, "10 ∆°F")]
69+
[InlineData(TemperatureUnit.DegreeFahrenheit, 20, 30, "-10 ∆°F")]
70+
[InlineData(TemperatureUnit.Kelvin, 30, 20, "10 ∆K")]
71+
[InlineData(TemperatureUnit.Kelvin, 20, 30, "-10 ∆K")]
72+
public void FromTemperatures_ReturnsDeltaOfTheTwoTemperaturesInLeftUnit(TemperatureUnit unit, int value, int otherValue, string expected)
73+
{
74+
Temperature temperature = Temperature.From(value, unit);
75+
Temperature otherTemperature = Temperature.From(otherValue, unit);
76+
77+
// Act
78+
var delta = TemperatureDelta.FromTemperatures(temperature, otherTemperature);
79+
80+
// Assert
81+
Assert.Equal(expected, delta.ToString(CultureInfo.InvariantCulture, "{0:0} {1}"));
82+
}
6283
}
6384
}

UnitsNet.Tests/CustomCode/TemperatureTests.cs

+22-43
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
// Copyright (c) 2013 Andreas Gullberg Larsen ([email protected]).
22
// https://github.com/angularsen/UnitsNet
3-
//
3+
//
44
// Permission is hereby granted, free of charge, to any person obtaining a copy
55
// of this software and associated documentation files (the "Software"), to deal
66
// in the Software without restriction, including without limitation the rights
77
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
88
// copies of the Software, and to permit persons to whom the Software is
99
// furnished to do so, subject to the following conditions:
10-
//
10+
//
1111
// The above copyright notice and this permission notice shall be included in
1212
// all copies or substantial portions of the Software.
13-
//
13+
//
1414
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1515
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1616
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -45,46 +45,6 @@ public class TemperatureTests : TemperatureTestsBase
4545

4646
protected override double KelvinsInOneKelvin => 1;
4747

48-
[SuppressMessage("ReSharper", "ImpureMethodCallOnReadonlyValueField",
49-
Justification = "R# incorrectly identifies method as impure, due to internal method calls.")]
50-
[Theory]
51-
[InlineData(TemperatureUnit.DegreeCelsius, 10, 1, "10 °C")]
52-
[InlineData(TemperatureUnit.DegreeCelsius, 10, 5, "2 °C")]
53-
[InlineData(TemperatureUnit.DegreeCelsius, 10, -10, "-1 °C")]
54-
[InlineData(TemperatureUnit.DegreeFahrenheit, 10, 1, "10 °F")]
55-
[InlineData(TemperatureUnit.DegreeFahrenheit, 10, 5, "2 °F")]
56-
[InlineData(TemperatureUnit.DegreeFahrenheit, 10, -10, "-1 °F")]
57-
public void DividedByTemperatureDeltaEqualsTemperature(TemperatureUnit unit, int temperatureVal, int divisor, string expected)
58-
{
59-
Temperature temperature = Temperature.From(temperatureVal, unit);
60-
61-
// Act
62-
Temperature resultTemp = temperature.Divide(divisor, unit);
63-
64-
string actual = resultTemp.ToUnit(unit).ToString(CultureInfo.InvariantCulture, "{0:0} {1}");
65-
Assert.Equal(expected, actual);
66-
}
67-
68-
[SuppressMessage("ReSharper", "ImpureMethodCallOnReadonlyValueField",
69-
Justification = "R# incorrectly identifies method as impure, due to internal method calls.")]
70-
[Theory]
71-
[InlineData(TemperatureUnit.DegreeCelsius, 10, 0, "0 °C")]
72-
[InlineData(TemperatureUnit.DegreeCelsius, 10, 5, "50 °C")]
73-
[InlineData(TemperatureUnit.DegreeCelsius, 10, -5, "-50 °C")]
74-
[InlineData(TemperatureUnit.DegreeFahrenheit, 10, 0, "0 °F")]
75-
[InlineData(TemperatureUnit.DegreeFahrenheit, 10, 5, "50 °F")]
76-
[InlineData(TemperatureUnit.DegreeFahrenheit, 10, -5, "-50 °F")]
77-
public void MultiplyByTemperatureDeltaEqualsTemperature(TemperatureUnit unit, int temperatureVal, int factor, string expected)
78-
{
79-
Temperature temperature = Temperature.From(temperatureVal, unit);
80-
81-
// Act
82-
Temperature resultTemp = temperature.Multiply(factor, unit);
83-
84-
string actual = resultTemp.ToUnit(unit).ToString(CultureInfo.InvariantCulture, "{0:0} {1}");
85-
Assert.Equal(expected, actual);
86-
}
87-
8848
[Theory]
8949
[InlineData(TemperatureUnit.DegreeCelsius, -10, 0, "-10 °C")]
9050
[InlineData(TemperatureUnit.DegreeCelsius, -10, 10, "0 °C")]
@@ -141,5 +101,24 @@ public void TemperaturePlusTemperatureDeltaEqualsTemperature(TemperatureUnit uni
141101
string actual = resultTemp.ToUnit(unit).ToString(CultureInfo.InvariantCulture, "{0:0} {1}");
142102
Assert.Equal(expected, actual);
143103
}
104+
105+
[Theory]
106+
[InlineData(TemperatureUnit.DegreeCelsius, 30, 20, "10 ∆°C")]
107+
[InlineData(TemperatureUnit.DegreeCelsius, 20, 30, "-10 ∆°C")]
108+
[InlineData(TemperatureUnit.DegreeFahrenheit, 30, 20, "10 ∆°F")]
109+
[InlineData(TemperatureUnit.DegreeFahrenheit, 20, 30, "-10 ∆°F")]
110+
[InlineData(TemperatureUnit.Kelvin, 30, 20, "10 ∆K")]
111+
[InlineData(TemperatureUnit.Kelvin, 20, 30, "-10 ∆K")]
112+
public void ToDelta_ReturnsDeltaOfTwoTemperaturesInSameUnit(TemperatureUnit unit, int value, int otherValue, string expected)
113+
{
114+
Temperature temperature = Temperature.From(value, unit);
115+
Temperature otherTemperature = Temperature.From(otherValue, unit);
116+
117+
// Act
118+
var delta = temperature.ToDelta(otherTemperature);
119+
120+
// Assert
121+
Assert.Equal(expected, delta.ToString(CultureInfo.InvariantCulture, "{0:0} {1}"));
122+
}
144123
}
145124
}
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
// Copyright (c) 2013 Andreas Gullberg Larsen ([email protected]).
22
// https://github.com/angularsen/UnitsNet
3-
//
3+
//
44
// Permission is hereby granted, free of charge, to any person obtaining a copy
55
// of this software and associated documentation files (the "Software"), to deal
66
// in the Software without restriction, including without limitation the rights
77
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
88
// copies of the Software, and to permit persons to whom the Software is
99
// furnished to do so, subject to the following conditions:
10-
//
10+
//
1111
// The above copyright notice and this permission notice shall be included in
1212
// all copies or substantial portions of the Software.
13-
//
13+
//
1414
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1515
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1616
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -36,7 +36,7 @@ public partial struct Temperature
3636
// Windows Runtime Component does not allow operator overloads: https://msdn.microsoft.com/en-us/library/br230301.aspx
3737
#if !WINDOWS_UWP
3838
/// <summary>
39-
/// Add a <see cref="Temperature" /> and a <see cref="TemperatureDelta" />.
39+
/// Add a <see cref="TemperatureDelta" /> to a <see cref="Temperature" />.
4040
/// </summary>
4141
/// <remarks>Due to temperature units having different scales, the arithmetic must be performed on the same scale.</remarks>
4242
/// <returns>The new temperature.</returns>
@@ -46,7 +46,7 @@ public partial struct Temperature
4646
}
4747

4848
/// <summary>
49-
/// Add a <see cref="TemperatureDelta" /> and a <see cref="Temperature" />.
49+
/// Add a <see cref="TemperatureDelta" /> to a <see cref="Temperature" />.
5050
/// </summary>
5151
/// <remarks>Due to temperature units having different scales, the arithmetic must be performed on the same scale.</remarks>
5252
/// <returns>The new temperature.</returns>
@@ -56,7 +56,7 @@ public partial struct Temperature
5656
}
5757

5858
/// <summary>
59-
/// Subtract a <see cref="Temperature" /> by a <see cref="TemperatureDelta" />.
59+
/// Subtract a <see cref="TemperatureDelta" /> from a <see cref="Temperature" />.
6060
/// </summary>
6161
/// <remarks>Due to temperature units having different scales, the arithmetic must be performed on the same scale.</remarks>
6262
/// <returns>The new temperature.</returns>
@@ -66,49 +66,15 @@ public partial struct Temperature
6666
}
6767

6868
/// <summary>
69-
/// Subtract a <see cref="Temperature" /> by a <see cref="TemperatureDelta" />.
69+
/// Subtract two temperatures to get a <see cref="TemperatureDelta"/> in this temperature's unit, which you can later convert to any temperature unit.
70+
/// This is useful since Celsius, Fahrenheit and Kelvins don't share a common zero point on the scale and normal subtraction
71+
/// would operate on Kelvins and likely give an unexpected result.
72+
/// Example:
73+
/// double deltaCelsius = celsius30.ToDelta(celsius20).DegreesCelsius; // 10 C
74+
/// double wrongDeltaCelsius = (celsius30 - celsius20).DegreesCelsius; // 303.15 K - 293.15 K = 10 K = -263.15 degrees Celsius
7075
/// </summary>
71-
/// <remarks>Due to temperature units having different scales, the arithmetic must be performed on the same scale.</remarks>
72-
/// <returns>The delta temperature (difference).</returns>
73-
public static TemperatureDelta operator -(Temperature left, Temperature right)
74-
{
75-
return new TemperatureDelta(left.Kelvins - right.Kelvins, TemperatureDeltaUnit.Kelvin);
76-
}
76+
/// <returns>A temperature delta.</returns>
77+
public TemperatureDelta ToDelta(Temperature other) => TemperatureDelta.FromTemperatures(this, other);
7778
#endif
78-
79-
/// <summary>
80-
/// Multiply temperature with a <paramref name="factor" /> in a given <paramref name="unit" />.
81-
/// </summary>
82-
/// <remarks>
83-
/// Due to different temperature units having different zero points, we cannot simply
84-
/// multiply or divide a temperature by a factor. We must first convert to the desired unit, then perform the
85-
/// calculation.
86-
/// </remarks>
87-
/// <param name="factor">Factor to multiply by.</param>
88-
/// <param name="unit">Unit to perform multiplication in.</param>
89-
/// <returns>The resulting <see cref="Temperature" />.</returns>
90-
public Temperature Multiply(double factor, TemperatureUnit unit)
91-
{
92-
double resultInUnit = As(unit) * factor;
93-
return From(resultInUnit, unit);
94-
}
95-
96-
97-
/// <summary>
98-
/// Divide temperature by a <paramref name="divisor" /> in a given <paramref name="unit" />.
99-
/// </summary>
100-
/// <remarks>
101-
/// Due to different temperature units having different zero points, we cannot simply
102-
/// multiply or divide a temperature by a factor. We must first convert to the desired unit, then perform the
103-
/// calculation.
104-
/// </remarks>
105-
/// <param name="divisor">Factor to multiply by.</param>
106-
/// <param name="unit">Unit to perform multiplication in.</param>
107-
/// <returns>The resulting <see cref="Temperature" />.</returns>
108-
public Temperature Divide(double divisor, TemperatureUnit unit)
109-
{
110-
double resultInUnit = As(unit) / divisor;
111-
return From(resultInUnit, unit);
112-
}
11379
}
11480
}

UnitsNet/CustomCode/Quantities/TemperatureDelta.extra.cs

+30
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
// THE SOFTWARE.
2121

2222
// ReSharper disable once CheckNamespace
23+
24+
using System;
25+
using UnitsNet.Units;
26+
2327
namespace UnitsNet
2428
{
2529
// Windows Runtime Component has constraints on public types: https://msdn.microsoft.com/en-us/library/br230301.aspx#Declaring types in Windows Runtime Components
@@ -47,6 +51,32 @@ public partial struct TemperatureDelta
4751
{
4852
return specificEntropy * temperatureDelta;
4953
}
54+
55+
/// <summary>
56+
/// Subtract two temperatures to get a <see cref="TemperatureDelta"/> in <paramref name="left"/>'s unit, which you can later convert to any temperature unit.
57+
/// This is useful since Celsius, Fahrenheit and Kelvins don't share a common zero point on the scale and normal subtraction
58+
/// would operate on Kelvins and likely give an unexpected result.
59+
/// Example:
60+
/// double deltaCelsius = TemperatureDelta.FromTemperatures(celsius30, celsius20).DegreesCelsius; // 10 C
61+
/// double wrongDeltaCelsius = (celsius30 - celsius20).DegreesCelsius; // 303.15 K - 293.15 K = 10 K = -263.15 degrees Celsius
62+
/// </summary>
63+
/// <returns>A temperature delta.</returns>
64+
public static TemperatureDelta FromTemperatures(Temperature left, Temperature right)
65+
{
66+
TemperatureDeltaUnit deltaUnit = ToDeltaUnit(left.Unit);
67+
return new TemperatureDelta(left.Value - right.As(left.Unit), deltaUnit);
68+
}
69+
70+
/// <summary>
71+
/// Converts a temperature unit to a temperature delta unit.
72+
/// They share the exact same unit names, but are of different enum types and have slightly different semantic meaning.
73+
/// </summary>
74+
/// <param name="temperatureUnit">Temperature unit.</param>
75+
/// <returns>Equivalent temperature delta unit.</returns>
76+
public static TemperatureDeltaUnit ToDeltaUnit(TemperatureUnit temperatureUnit)
77+
{
78+
return (TemperatureDeltaUnit)Enum.Parse(typeof(TemperatureDeltaUnit), temperatureUnit.ToString());
79+
}
5080
#endif
5181
}
5282
}

0 commit comments

Comments
 (0)