From 04894470eabdb79fe22d925e6f3821049743bf37 Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:11:40 -0400 Subject: [PATCH 01/19] add handling for FeetInches output where rounding makes inches =12 --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index 138d9e9f7f..54a3df7286 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -161,7 +161,17 @@ public string ToString(IFormatProvider? cultureInfo) // Note that it isn't customary to use fractions - one wouldn't say "I am 5 feet and 4.5 inches". // So inches are rounded when converting from base units to feet/inches. - return string.Format(cultureInfo, "{0:n0} {1} {2:n0} {3}", Feet, footUnit, Math.Round(Inches), inchUnit); + // When we do this we check if we rounded inches to 12(InchesInOneFoot). + // If it does feet/inches are fixed something like 4 ft 0 in is displayed instead of 3ft 12 in for things very close to 4 e.g. 3.9999 ft + var feet = Feet; + var inches = Math.Round(Inches); + if(inches == InchesInOneFoot) + { + feet++; + inches = 0; + } + + return string.Format(cultureInfo, "{0:n0} {1} {2:n0} {3}", feet, footUnit, inches, inchUnit); } /// From 9483162fc09027ffea1a237636bb08a5ccb777e1 Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:31:16 -0400 Subject: [PATCH 02/19] update ToArchitecturalString to not output 12 in the inches position --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index 54a3df7286..d2a05b0fac 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -200,6 +200,7 @@ public string ToArchitecturalString(int fractionDenominator) throw new ArgumentOutOfRangeException(nameof(fractionDenominator), "Denominator for fractional inch must be greater than zero."); } + var feet = Feet; var inchTrunc = (int)Math.Truncate(Inches); var numerator = (int)Math.Round((Inches - inchTrunc) * fractionDenominator); @@ -209,6 +210,12 @@ public string ToArchitecturalString(int fractionDenominator) numerator = 0; } + if (inchTrunc == InchesInOneFoot) + { + feet++; + inchTrunc = 0; + } + var inchPart = new System.Text.StringBuilder(); if (inchTrunc != 0 || numerator == 0) @@ -248,7 +255,7 @@ int GreatestCommonDivisor(int a, int b) return inchPart.ToString(); } - return $"{Feet}' - {inchPart}"; + return $"{feet}' - {inchPart}"; } } } From e0bb45a0188a6d05b6014b58278d4f30793f2379 Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Sat, 2 May 2026 13:38:50 -0400 Subject: [PATCH 03/19] Update Length.extra.cs fixed using `Feet` instead of local copy `feet` --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index d2a05b0fac..20091b22a9 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -250,7 +250,7 @@ int GreatestCommonDivisor(int a, int b) inchPart.Append('"'); - if (Feet == 0) + if (feet == 0) { return inchPart.ToString(); } From 4c374c18cc07dbf4244a8745d29d161ad3c73f9d Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Sat, 2 May 2026 13:48:06 -0400 Subject: [PATCH 04/19] Update Length.extra.cs fix formatting --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index 20091b22a9..a52510a3cb 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -165,7 +165,7 @@ public string ToString(IFormatProvider? cultureInfo) // If it does feet/inches are fixed something like 4 ft 0 in is displayed instead of 3ft 12 in for things very close to 4 e.g. 3.9999 ft var feet = Feet; var inches = Math.Round(Inches); - if(inches == InchesInOneFoot) + if (inches == InchesInOneFoot) { feet++; inches = 0; From abd31a51cece1cb028369a6b5fc645c8e170a63e Mon Sep 17 00:00:00 2001 From: apmoskevitz Date: Thu, 11 Jun 2026 10:12:37 -0400 Subject: [PATCH 05/19] wrote tests for fixed feet inches code and corrected procedure to correct failures --- .../CustomCode/LengthTests.FeetInches.cs | 48 ++++++++++++++++++- UnitsNet.Tests/CustomCode/LengthTests.cs | 6 ++- .../CustomCode/Quantities/Length.extra.cs | 38 +++++++++++---- 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs index 4c4855b436..66e641bf6f 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs @@ -20,7 +20,7 @@ public class FeetInchesTests public void FeetInchesFrom() { Length meter = Length.FromFeetInches(2, 3); - double expectedMeters = 2/FeetInOneMeter + 3/InchesInOneMeter; + double expectedMeters = 2 / FeetInOneMeter + 3 / InchesInOneMeter; AssertEx.EqualTolerance(expectedMeters, meter.Meters, FeetTolerance); } @@ -108,5 +108,51 @@ public void TryParseFeetInches_GivenInvalidString_ReturnsFalseAndZeroOut(string Assert.False(Length.TryParseFeetInches(str, out Length result, formatProvider)); Assert.Equal(Length.Zero, result); } + + [Theory] + [InlineData(-11.9999, 0, -11.9999)] + [InlineData(-23.98, -1, -11.98)] + [InlineData(-13, -1, -1)] + [InlineData(-38.563, -3, -2.563)] + + public static void NegativeFeetInchesIsAsExpected(double inch, double expectedFeet, double expectedInches) + { + var length = Length.FromInches(inch); + + Assert.Equal(expectedFeet, length.FeetInches.Feet, tolerance: 0.000000000001d); + Assert.Equal(expectedInches, length.FeetInches.Inches, tolerance: 0.000000000001d); + + } + + [Theory] + [InlineData(1, -11, 0, 1)] + [InlineData(-2, 2, -1, -10)] + [InlineData(-1, 32, 1, 8)] + + public static void MixedPositiveNegativeFeetInchesIsAsExpected(double feet, double inch, double expectedFeet, double expectedInches) + { + var length = Length.FromFeetInches(feet, inch); + + Assert.Equal(expectedFeet, length.FeetInches.Feet); + Assert.Equal(expectedInches, length.FeetInches.Inches); + + } + + [Theory] + [InlineData(11.9999, 16, "1' - 0\"")] + [InlineData(-11.9999, 16, "-1' - 0\"")] + [InlineData(23.98, 32, "1' - 11 31/32\"")] + [InlineData(-23.98, 32, "-1' - 11 31/32\"")] + [InlineData(13, 32, "1' - 1\"")] + [InlineData(-13, 32, "-1' - 1\"")] + [InlineData(38.563, 32, "3' - 2 9/16\"")] + [InlineData(-38.563, 32, "-3' - 2 9/16\"")] + + public static void NegativeToArchitecturalString_ReturnsFormatted(double inch, int fractionDenominator, string expected) + { + var length = Length.FromInches(inch); + + Assert.Equal(expected, length.FeetInches.ToArchitecturalString(fractionDenominator)); + } } } diff --git a/UnitsNet.Tests/CustomCode/LengthTests.cs b/UnitsNet.Tests/CustomCode/LengthTests.cs index f658ed9d87..9de0563572 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.cs @@ -52,7 +52,7 @@ public class LengthTests : LengthTestsBase protected override double ShacklesInOneMeter => 0.0364538; - protected override double NauticalMilesInOneMeter => 1.0/1852.0; + protected override double NauticalMilesInOneMeter => 1.0 / 1852.0; protected override double HandsInOneMeter => 9.8425196850393701; @@ -240,6 +240,9 @@ public static void InverseReturnsReciprocalLength(double value, double expected) Assert.Equal(expected, inverseLength.InverseMeters); } + + + [Theory] [InlineData(3, 2.563, 16, "3' - 2 9/16\"")] [InlineData(3, 2.563, 32, "3' - 2 9/16\"")] @@ -253,6 +256,7 @@ public static void InverseReturnsReciprocalLength(double value, double expected) [InlineData(3, 2.6, 16, "3' - 2 5/8\"")] [InlineData(3, 2.6, 32, "3' - 2 19/32\"")] [InlineData(3, 2.6, 128, "3' - 2 77/128\"")] + [InlineData(3, 11.9988, 128, "4' - 0\"")] public static void ToArchitecturalString_ReturnsFormatted(double ft, double inch, int fractionDenominator, string expected) { var length = Length.FromFeetInches(ft, inch); diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index a52510a3cb..f1952b642b 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -11,7 +11,7 @@ namespace UnitsNet { public partial struct Length { - private const double InchesInOneFoot = 12; + internal const double InchesInOneFoot = 12; /// /// Converts the length to a customary feet/inches combination. @@ -33,7 +33,7 @@ public FeetInches FeetInches /// public static Length FromFeetInches(double feet, double inches) { - return FromInches(InchesInOneFoot*feet + inches); + return FromInches(InchesInOneFoot * feet + inches); } /// @@ -48,7 +48,8 @@ public static Length FromFeetInches(double feet, double inches) /// Parsed length. public static Length ParseFeetInches(string str, IFormatProvider? formatProvider = null) { - if (str == null) throw new ArgumentNullException(nameof(str)); + if (str == null) + throw new ArgumentNullException(nameof(str)); if (!TryParseFeetInches(str, out Length result, formatProvider)) { // A bit lazy, but I didn't want to duplicate this edge case implementation just to get more narrow exception descriptions. @@ -165,12 +166,12 @@ public string ToString(IFormatProvider? cultureInfo) // If it does feet/inches are fixed something like 4 ft 0 in is displayed instead of 3ft 12 in for things very close to 4 e.g. 3.9999 ft var feet = Feet; var inches = Math.Round(Inches); - if (inches == InchesInOneFoot) + if (inches == Length.InchesInOneFoot) { feet++; inches = 0; } - + return string.Format(cultureInfo, "{0:n0} {1} {2:n0} {3}", feet, footUnit, inches, inchUnit); } @@ -199,10 +200,20 @@ public string ToArchitecturalString(int fractionDenominator) { throw new ArgumentOutOfRangeException(nameof(fractionDenominator), "Denominator for fractional inch must be greater than zero."); } - var feet = Feet; - var inchTrunc = (int)Math.Truncate(Inches); - var numerator = (int)Math.Round((Inches - inchTrunc) * fractionDenominator); + var inches = Inches; + //if negative value we record this and invert the values, at the end we add a negative sign as necessary, but all the calculations are done positive so rounding behavior is the same. + var isNegative = Feet < 0 || Inches < 0; + if (isNegative) + { + feet = -feet; + inches = -inches; + } + + + var inchTrunc = (int)Math.Truncate(inches); + var numerator = (int)Math.Round((inches - inchTrunc) * fractionDenominator); + if (numerator == fractionDenominator) { @@ -210,7 +221,7 @@ public string ToArchitecturalString(int fractionDenominator) numerator = 0; } - if (inchTrunc == InchesInOneFoot) + if (inchTrunc == Length.InchesInOneFoot) { feet++; inchTrunc = 0; @@ -255,7 +266,14 @@ int GreatestCommonDivisor(int a, int b) return inchPart.ToString(); } - return $"{feet}' - {inchPart}"; + //add the sign to the beginning if negative + string? sign = null; + if (isNegative) + { + sign = "-"; + } + + return $"{sign}{feet}' - {inchPart}"; } } } From 07210dfe002ba4b0f7ee32c8dabfb0974c0d3652 Mon Sep 17 00:00:00 2001 From: apmoskevitz Date: Thu, 11 Jun 2026 10:54:05 -0400 Subject: [PATCH 06/19] fixed feetinches.toString rounding negative lengths weirdly and added unit tests for that. --- .../CustomCode/LengthTests.FeetInches.cs | 30 +++++++++++++++++++ .../CustomCode/Quantities/Length.extra.cs | 22 ++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs index 66e641bf6f..e4f5402264 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Globalization; +using System.Text; +using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Xunit; namespace UnitsNet.Tests @@ -154,5 +156,33 @@ public static void NegativeToArchitecturalString_ReturnsFormatted(double inch, i Assert.Equal(expected, length.FeetInches.ToArchitecturalString(fractionDenominator)); } + + [Theory] + [InlineData(11.9999, "1 ft 0 in")] + [InlineData(-11.9999, "-1 ft 0 in")] + [InlineData(23.98, "2 ft 0 in")] + [InlineData(-23.98, "-2 ft 0 in")] + [InlineData(13, "1 ft 1 in")] + [InlineData(-13, "-1 ft 1 in")] + [InlineData(38.563, "3 ft 3 in")] + [InlineData(-38.563, "-3 ft 3 in")] + [InlineData(50.2, "4 ft 2 in")] + [InlineData(-50.2, "-4 ft 2 in")] + [InlineData(-50.2, "-4 фут 2 дюйм", "ru-RU")]//ensure we are using alternate units + [InlineData(-50.2, "\u22124 ft 2 in", "nb-NO")]// nb-NO does not have alternate abbreviations defined in length.json but does use a different negative symbol + public static void FeetInches_ToStringFormatsCorrectly(double inch, string expected, string? cultureString = null) + { + var length = Length.FromInches(inch); + CultureInfo culture; + if (cultureString == null) + { + culture = CultureInfo.InvariantCulture; + } + else + { + culture = new CultureInfo(cultureString, useUserOverride: false); + } + Assert.Equal(expected, length.FeetInches.ToString(culture)); + } } } diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index f1952b642b..c4f790a054 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -160,18 +160,36 @@ public string ToString(IFormatProvider? cultureInfo) var footUnit = Length.GetAbbreviation(LengthUnit.Foot, cultureInfo); var inchUnit = Length.GetAbbreviation(LengthUnit.Inch, cultureInfo); + // Note that it isn't customary to use fractions - one wouldn't say "I am 5 feet and 4.5 inches". // So inches are rounded when converting from base units to feet/inches. // When we do this we check if we rounded inches to 12(InchesInOneFoot). // If it does feet/inches are fixed something like 4 ft 0 in is displayed instead of 3ft 12 in for things very close to 4 e.g. 3.9999 ft - var feet = Feet; - var inches = Math.Round(Inches); + double feet; + double inches; + bool isNegative = Feet < 0 || Inches < 0; + if (isNegative) + { + feet = -Feet; + inches = Math.Round(-Inches); + } + else + { + feet = Feet; + inches = Math.Round(Inches); + } + if (inches == Length.InchesInOneFoot) { feet++; inches = 0; } + if (isNegative) + { + //we re-negate feet here so the negative will be handled by the built in formatter + feet = -feet; + } return string.Format(cultureInfo, "{0:n0} {1} {2:n0} {3}", feet, footUnit, inches, inchUnit); } From 177291c5f2c3b073a6e9ce0240119e7b96f49442 Mon Sep 17 00:00:00 2001 From: apmoskevitz Date: Thu, 11 Jun 2026 10:58:28 -0400 Subject: [PATCH 07/19] fix using culture correct negative sign in ToArchitecturalString (maybe not important but made consistant with the ToString method --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index c4f790a054..338a4bf039 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -284,14 +284,14 @@ int GreatestCommonDivisor(int a, int b) return inchPart.ToString(); } - //add the sign to the beginning if negative - string? sign = null; + if (isNegative) { - sign = "-"; + //re-negate feet so the output uses a culture correct negative sign. + feet = -feet; } - return $"{sign}{feet}' - {inchPart}"; + return $"{feet}' - {inchPart}"; } } } From ccc4e2e0a15fecfea319d5084429f9b712ef4513 Mon Sep 17 00:00:00 2001 From: apmoskevitz Date: Thu, 11 Jun 2026 11:06:18 -0400 Subject: [PATCH 08/19] fix usings and formatting --- UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs | 4 ---- UnitsNet.Tests/CustomCode/LengthTests.cs | 6 ------ UnitsNet/CustomCode/Quantities/Length.extra.cs | 2 -- 3 files changed, 12 deletions(-) diff --git a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs index e4f5402264..522784ff27 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs @@ -1,11 +1,7 @@ // Licensed under MIT No Attribution, see LICENSE file at the root. // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. -using System.Collections.Generic; using System.Globalization; -using System.Text; -using Microsoft.VisualStudio.TestPlatform.Common.Utilities; -using Xunit; namespace UnitsNet.Tests { diff --git a/UnitsNet.Tests/CustomCode/LengthTests.cs b/UnitsNet.Tests/CustomCode/LengthTests.cs index 9de0563572..36a611bcf0 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.cs @@ -1,10 +1,7 @@ // Licensed under MIT No Attribution, see LICENSE file at the root. // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. -using System; using System.Globalization; -using UnitsNet.Units; -using Xunit; namespace UnitsNet.Tests { @@ -240,9 +237,6 @@ public static void InverseReturnsReciprocalLength(double value, double expected) Assert.Equal(expected, inverseLength.InverseMeters); } - - - [Theory] [InlineData(3, 2.563, 16, "3' - 2 9/16\"")] [InlineData(3, 2.563, 32, "3' - 2 9/16\"")] diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index 338a4bf039..1e228bf68f 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -1,11 +1,9 @@ // Licensed under MIT No Attribution, see LICENSE file at the root. // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. -using System; using System.Globalization; using System.Text.RegularExpressions; using System.Threading; -using UnitsNet.Units; namespace UnitsNet { From d53b076015dc9c14d68a9b635caec01173761e0e Mon Sep 17 00:00:00 2001 From: Andreas Gullberg Larsen Date: Fri, 17 Jul 2026 14:23:38 +0200 Subject: [PATCH 09/19] Cover FeetInches ToString rounding carry --- UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs index 522784ff27..76491f5d60 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs @@ -2,6 +2,7 @@ // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. using System.Globalization; +using UnitsNet.Tests.Helpers; namespace UnitsNet.Tests { @@ -180,5 +181,17 @@ public static void FeetInches_ToStringFormatsCorrectly(double inch, string expec } Assert.Equal(expected, length.FeetInches.ToString(culture)); } + + [Theory] + [InlineData(47.9988, "4 ft 0 in")] + [InlineData(-47.9988, "-4 ft 0 in")] + public static void FeetInches_ToString_RoundsTwelveInchesIntoNextFoot(double inch, string expected) + { + using var _ = new CultureScope(CultureInfo.InvariantCulture); + + var length = Length.FromInches(inch); + + Assert.Equal(expected, length.FeetInches.ToString()); + } } } From 4d764f8065f40bf4c169782c4d2af3a727a0cd4a Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:18:45 -0400 Subject: [PATCH 10/19] fix <-1 foot losing the negative sign with just inches --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index 1e228bf68f..f3633f9fa1 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -278,9 +278,14 @@ int GreatestCommonDivisor(int a, int b) inchPart.Append('"'); if (feet == 0) - { - return inchPart.ToString(); - } +{ +if (isNegative) +{ + //negate inches so we output the correct sign. + inchPart = -inchPart; +} +return inchPart.ToString(); +} if (isNegative) From eeaa9f9e3cd2a48cc580dcd5746211774f8ac022 Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:39:03 -0400 Subject: [PATCH 11/19] Update LengthTests.FeetInches.cs added additional test cases for inch only negation --- UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs index f452b6a6d6..86f3930848 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs @@ -159,7 +159,12 @@ public static void MixedPositiveNegativeFeetInchesIsAsExpected(double feet, doub [InlineData(-13, 32, "-1' - 1\"")] [InlineData(38.563, 32, "3' - 2 9/16\"")] [InlineData(-38.563, 32, "-3' - 2 9/16\"")] - + [InlineData(-5.5, 32, "-5 1/2\"")] + [InlineData(5.75, 32, "5 3/4\"")] + [InlineData(6, 32, "6\"")] + [InlineData(-9, 32, "-9\"")] + [InlineData(36, 32, "3' - 0\"")] + [InlineData(-48, 128, "-4' - 0\"")] public static void NegativeToArchitecturalString_ReturnsFormatted(double inch, int fractionDenominator, string expected) { var length = Length.FromInches(inch); From b8f308f6a2defbc2f88402aedb0b14000321d8bc Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:42:09 -0400 Subject: [PATCH 12/19] remove extra lines --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index c6ae6016a0..4ce0f3fd94 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -288,7 +288,6 @@ static int GreatestCommonDivisor(int a, int b) return inchPart.ToString(); } - if (isNegative) { //re-negate feet so the output uses a culture correct negative sign. From 28b5f28fce2c8d88a0e3c2c9de877c8db94d061d Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:42:25 -0400 Subject: [PATCH 13/19] remove extra blank lines --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index 4ce0f3fd94..f09c259d76 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -227,7 +227,6 @@ public string ToArchitecturalString(int fractionDenominator) inches = -inches; } - var inchTrunc = (int)Math.Truncate(inches); var numerator = (int)Math.Round((inches - inchTrunc) * fractionDenominator); From 3160630eaf628fcdbab8f20b09f6d54dd86f3e65 Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:10:16 -0400 Subject: [PATCH 14/19] fix <12 behavior for negatives in toString(), --- .../CustomCode/Quantities/Length.extra.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index f09c259d76..abc33160ca 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -167,10 +167,12 @@ public string ToString(IFormatProvider? cultureInfo) double feet; double inches; bool isNegative = Feet < 0 || Inches < 0; + var negativeSign = ""; if (isNegative) { feet = -Feet; inches = Math.Round(-Inches); + negativeSign = cultureInfo.NumberFormat.NegativeSign } else { @@ -184,12 +186,7 @@ public string ToString(IFormatProvider? cultureInfo) inches = 0; } - if (isNegative) - { - //we re-negate feet here so the negative will be handled by the built in formatter - feet = -feet; - } - return string.Format(cultureInfo, "{0:n0} {1} {2:n0} {3}", feet, footUnit, inches, inchUnit); + return string.Format(cultureInfo, "{4}{0:n0} {1} {2:n0} {3}", feet, footUnit, inches, inchUnit, negativeSign); } /// @@ -278,18 +275,21 @@ static int GreatestCommonDivisor(int a, int b) inchPart.Append('"'); if (feet == 0) -{ -if (isNegative) -{ - //negate inches so we output the correct sign. - inchPart = -inchPart; -} -return inchPart.ToString(); -} + { + if (isNegative) + { + //negate inches so the output uses a culture correct negative sign. + inchPart = -inchPart; + } + + return inchPart.ToString(); + } if (isNegative) { //re-negate feet so the output uses a culture correct negative sign. + //the behaviour different of feet = -feet between .netframework and .netcore/.net runtimes, + //where newer runtimes support -0.0 is not an issue here because we do not emit the feet part if it is 0. feet = -feet; } From b99ad40af5babe59fe91e605e2a5ca6b335b328e Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:14:36 -0400 Subject: [PATCH 15/19] Apply suggestion from @Applesauce314 --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index abc33160ca..b05ad312b1 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -44,8 +44,7 @@ public static Length FromFeetInches(QuantityValue feet, QuantityValue inches) /// Parsed length. public static Length ParseFeetInches(string str, IFormatProvider? formatProvider = null) { - if (str == null) - throw new ArgumentNullException(nameof(str)); + if (str == null) throw new ArgumentNullException(nameof(str)); if (!TryParseFeetInches(str, out Length result, formatProvider)) { // A bit lazy, but I didn't want to duplicate this edge case implementation just to get more narrow exception descriptions. From 77acc3f371641a96c0ce7c02813b9eb52a70c57d Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:16:58 -0400 Subject: [PATCH 16/19] Update LengthTests.FeetInches.cs with additional tests for negative. --- UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs index 86f3930848..4c2f394332 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs @@ -182,9 +182,14 @@ public static void NegativeToArchitecturalString_ReturnsFormatted(double inch, i [InlineData(38.563, "3 ft 3 in")] [InlineData(-38.563, "-3 ft 3 in")] [InlineData(50.2, "4 ft 2 in")] - [InlineData(-50.2, "-4 ft 2 in")] + [InlineData(-50.2, "-4 ft 2 in")] + [InlineData(-7.6, "-0 ft 8 in")] + [InlineData(7.6, "0 ft 8 in")] + [InlineData(0, "0 ft 0 in")] + [InlineData(-0.0d, "0 ft 0 in")] [InlineData(-50.2, "-4 фут 2 дюйм", "ru-RU")]//ensure we are using alternate units - [InlineData(-50.2, "\u22124 ft 2 in", "nb-NO")]// nb-NO does not have alternate abbreviations defined in length.json but does use a different negative symbol + [InlineData(-50.2, "\u22124 ft 2 in", "nb-NO")]// nb-NO does not have alternate abbreviations defined in length.json but does use a different negative symbol + [InlineData(-8, "\u22120 ft 8 in", "nb-NO")]// nb-NO does not have alternate abbreviations defined in length.json but does use a different negative symbol public static void FeetInches_ToStringFormatsCorrectly(double inch, string expected, string? cultureString = null) { var length = Length.FromInches(inch); From f217ae423bd05da1713147e4ffae3ba57fc0648e Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:07:48 -0400 Subject: [PATCH 17/19] fix missing ; fix missing ; --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index b05ad312b1..2ce5b7ede7 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -171,7 +171,7 @@ public string ToString(IFormatProvider? cultureInfo) { feet = -Feet; inches = Math.Round(-Inches); - negativeSign = cultureInfo.NumberFormat.NegativeSign + negativeSign = cultureInfo.NumberFormat.NegativeSign; } else { From 5e93be11a45ec93cd51ce9839aecb946c1861abc Mon Sep 17 00:00:00 2001 From: Andrew Moskevitz <49752377+Applesauce314@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:17:44 -0400 Subject: [PATCH 18/19] Update Length.extra.cs to make inchesInOneFoot internal --- UnitsNet/CustomCode/Quantities/Length.extra.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index 2ce5b7ede7..15ae79ed79 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -10,7 +10,7 @@ namespace UnitsNet { public partial struct Length { - private static readonly QuantityValue InchesInOneFoot = 12; + internal static readonly QuantityValue InchesInOneFoot = 12; /// /// Converts the length to a customary feet/inches combination. From 7bc6569dce69c9883c0280bcbf13ead70355a48b Mon Sep 17 00:00:00 2001 From: apmoskevitz Date: Wed, 19 Aug 2026 11:32:09 -0400 Subject: [PATCH 19/19] fix build errors and merge issue --- .../CustomCode/LengthTests.FeetInches.cs | 213 +++++++++--------- UnitsNet.Tests/CustomCode/LengthTests.cs | 1 + .../CustomCode/Quantities/Length.extra.cs | 17 +- 3 files changed, 112 insertions(+), 119 deletions(-) diff --git a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs index 4c2f394332..36ec44d9ac 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs @@ -2,32 +2,28 @@ // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. using System.Globalization; +using System.Numerics; using UnitsNet.Tests.Helpers; namespace UnitsNet.Tests; public class FeetInchesTests { - private const string EnglishUs = "en-US"; - private const string GermanSwitzerland = "de-CH"; - + + private static readonly CultureInfo EnglishUs = new("en-US", useUserOverride: false); + private static readonly CultureInfo GermanSwitzerland = new("de-CH", useUserOverride: false); + private const double FeetInOneMeter = 3.28084; + private const double InchesInOneMeter = 39.37007874; + private const double FeetTolerance = 1e-5; + private const double InchesTolerance = 1e-5; + [Fact] public void FeetInchesFrom() { - private static readonly CultureInfo EnglishUs = new("en-US", useUserOverride: false); - private static readonly CultureInfo GermanSwitzerland = new("de-CH", useUserOverride: false); - private const double FeetInOneMeter = 3.28084; - private const double InchesInOneMeter = 39.37007874; - private const double FeetTolerance = 1e-5; - private const double InchesTolerance = 1e-5; - - [Fact] - public void FeetInchesFrom() - { - Length meter = Length.FromFeetInches(2, 3); - double expectedMeters = 2 / FeetInOneMeter + 3 / InchesInOneMeter; - AssertEx.EqualTolerance(expectedMeters, meter.Meters, FeetTolerance); - } + Length meter = Length.FromFeetInches(2, 3); + double expectedMeters = 2 / FeetInOneMeter + 3 / InchesInOneMeter; + AssertEx.EqualTolerance(expectedMeters, meter.Meters, FeetTolerance); + } [Fact] public void FeetInchesRoundTrip() @@ -83,9 +79,8 @@ public static IEnumerable ValidData [Theory] [MemberData(nameof(ValidData))] - public void TryParseFeetInches(string str, double expectedFeet, string cultureName) + public void TryParseFeetInches(string str, double expectedFeet, CultureInfo formatProvider) { - var formatProvider = new CultureInfo(cultureName, false); Assert.True(Length.TryParseFeetInches(str, out Length result, formatProvider)); AssertEx.EqualTolerance(expectedFeet, result.Feet, 1e-5); } @@ -113,115 +108,109 @@ public static IEnumerable InvalidData ]; } - [Theory] - [MemberData(nameof(InvalidData))] - public void TryParseFeetInches_GivenInvalidString_ReturnsFalseAndZeroOut(string str, CultureInfo formatProvider) - { - Assert.False(Length.TryParseFeetInches(str, out Length result, formatProvider)); - Assert.Equal(Length.Zero, result); - } + [Theory] + [MemberData(nameof(InvalidData))] + public void TryParseFeetInches_GivenInvalidString_ReturnsFalseAndZeroOut(string str, CultureInfo formatProvider) + { + Assert.False(Length.TryParseFeetInches(str, out Length result, formatProvider)); + Assert.Equal(Length.Zero, result); + } - [Theory] - [InlineData(-11.9999, 0, -11.9999)] - [InlineData(-23.98, -1, -11.98)] - [InlineData(-13, -1, -1)] - [InlineData(-38.563, -3, -2.563)] + [Theory] + [InlineData(-11.9999, 0, -11.9999)] + [InlineData(-23.98, -1, -11.98)] + [InlineData(-13, -1, -1)] + [InlineData(-38.563, -3, -2.563)] - public static void NegativeFeetInchesIsAsExpected(double inch, double expectedFeet, double expectedInches) - { - var length = Length.FromInches(inch); + public static void NegativeFeetInchesIsAsExpected(double inch, double expectedFeet, double expectedInches) + { + var length = Length.FromInches(inch); - Assert.Equal(expectedFeet, length.FeetInches.Feet, tolerance: 0.000000000001d); - Assert.Equal(expectedInches, length.FeetInches.Inches, tolerance: 0.000000000001d); + Assert.Equal(new System.Numerics.BigInteger(expectedFeet), length.FeetInches.Feet); + Assert.Equal(expectedInches, length.FeetInches.Inches, tolerance: 0.000000000001d); - } + } - [Theory] - [InlineData(1, -11, 0, 1)] - [InlineData(-2, 2, -1, -10)] - [InlineData(-1, 32, 1, 8)] + [Theory] + [InlineData(1, -11, 0, 1)] + [InlineData(-2, 2, -1, -10)] + [InlineData(-1, 32, 1, 8)] - public static void MixedPositiveNegativeFeetInchesIsAsExpected(double feet, double inch, double expectedFeet, double expectedInches) - { - var length = Length.FromFeetInches(feet, inch); + public static void MixedPositiveNegativeFeetInchesIsAsExpected(long feet, double inch, long expectedFeet, double expectedInches) + { + var length = Length.FromFeetInches(feet, inch); - Assert.Equal(expectedFeet, length.FeetInches.Feet); - Assert.Equal(expectedInches, length.FeetInches.Inches); + Assert.Equal(new BigInteger(expectedFeet), length.FeetInches.Feet); + Assert.Equal(expectedInches, length.FeetInches.Inches.ToDouble()); - } + } - [Theory] - [InlineData(11.9999, 16, "1' - 0\"")] - [InlineData(-11.9999, 16, "-1' - 0\"")] - [InlineData(23.98, 32, "1' - 11 31/32\"")] - [InlineData(-23.98, 32, "-1' - 11 31/32\"")] - [InlineData(13, 32, "1' - 1\"")] - [InlineData(-13, 32, "-1' - 1\"")] - [InlineData(38.563, 32, "3' - 2 9/16\"")] - [InlineData(-38.563, 32, "-3' - 2 9/16\"")] - [InlineData(-5.5, 32, "-5 1/2\"")] - [InlineData(5.75, 32, "5 3/4\"")] - [InlineData(6, 32, "6\"")] - [InlineData(-9, 32, "-9\"")] - [InlineData(36, 32, "3' - 0\"")] - [InlineData(-48, 128, "-4' - 0\"")] - public static void NegativeToArchitecturalString_ReturnsFormatted(double inch, int fractionDenominator, string expected) - { - var length = Length.FromInches(inch); + [Theory] + [InlineData(11.9999, 16, "1' - 0\"")] + [InlineData(-11.9999, 16, "-1' - 0\"")] + [InlineData(23.98, 32, "1' - 11 31/32\"")] + [InlineData(-23.98, 32, "-1' - 11 31/32\"")] + [InlineData(13, 32, "1' - 1\"")] + [InlineData(-13, 32, "-1' - 1\"")] + [InlineData(38.563, 32, "3' - 2 9/16\"")] + [InlineData(-38.563, 32, "-3' - 2 9/16\"")] + [InlineData(-5.5, 32, "-5 1/2\"")] + [InlineData(5.75, 32, "5 3/4\"")] + [InlineData(6, 32, "6\"")] + [InlineData(-9, 32, "-9\"")] + [InlineData(36, 32, "3' - 0\"")] + [InlineData(-48, 128, "-4' - 0\"")] + [InlineData(-0.5d, 128, "-1/2\"")] + [InlineData(0.5d, 128, "1/2\"")] + public static void NegativeToArchitecturalString_ReturnsFormatted(double inch, int fractionDenominator, string expected) + { + var length = Length.FromInches(inch); - Assert.Equal(expected, length.FeetInches.ToArchitecturalString(fractionDenominator)); - } + Assert.Equal(expected, length.FeetInches.ToArchitecturalString(fractionDenominator)); + } - [Theory] - [InlineData(11.9999, "1 ft 0 in")] - [InlineData(-11.9999, "-1 ft 0 in")] - [InlineData(23.98, "2 ft 0 in")] - [InlineData(-23.98, "-2 ft 0 in")] - [InlineData(13, "1 ft 1 in")] - [InlineData(-13, "-1 ft 1 in")] - [InlineData(38.563, "3 ft 3 in")] - [InlineData(-38.563, "-3 ft 3 in")] - [InlineData(50.2, "4 ft 2 in")] - [InlineData(-50.2, "-4 ft 2 in")] - [InlineData(-7.6, "-0 ft 8 in")] - [InlineData(7.6, "0 ft 8 in")] - [InlineData(0, "0 ft 0 in")] - [InlineData(-0.0d, "0 ft 0 in")] - [InlineData(-50.2, "-4 фут 2 дюйм", "ru-RU")]//ensure we are using alternate units - [InlineData(-50.2, "\u22124 ft 2 in", "nb-NO")]// nb-NO does not have alternate abbreviations defined in length.json but does use a different negative symbol - [InlineData(-8, "\u22120 ft 8 in", "nb-NO")]// nb-NO does not have alternate abbreviations defined in length.json but does use a different negative symbol - public static void FeetInches_ToStringFormatsCorrectly(double inch, string expected, string? cultureString = null) + [Theory] + [InlineData(11.9999, "1 ft 0 in")] + [InlineData(-11.9999, "-1 ft 0 in")] + [InlineData(23.98, "2 ft 0 in")] + [InlineData(-23.98, "-2 ft 0 in")] + [InlineData(13, "1 ft 1 in")] + [InlineData(-13, "-1 ft 1 in")] + [InlineData(38.563, "3 ft 3 in")] + [InlineData(-38.563, "-3 ft 3 in")] + [InlineData(50.2, "4 ft 2 in")] + [InlineData(-50.2, "-4 ft 2 in")] + [InlineData(-7.6, "-0 ft 8 in")] + [InlineData(7.6, "0 ft 8 in")] + [InlineData(0, "0 ft 0 in")] + [InlineData(-0.0d, "0 ft 0 in")] + [InlineData(-50.2, "-4 фут 2 дюйм", "ru-RU")]//ensure we are using alternate units + [InlineData(-50.2, "\u22124 ft 2 in", "nb-NO")]// nb-NO does not have alternate abbreviations defined in length.json but does use a different negative symbol + [InlineData(-8, "\u22120 ft 8 in", "nb-NO")]// nb-NO does not have alternate abbreviations defined in length.json but does use a different negative symbol + public static void FeetInches_ToStringFormatsCorrectly(double inch, string expected, string? cultureString = null) + { + var length = Length.FromInches(inch); + CultureInfo culture; + if (cultureString == null) { - var length = Length.FromInches(inch); - CultureInfo culture; - if (cultureString == null) - { - culture = CultureInfo.InvariantCulture; - } - else - { - culture = new CultureInfo(cultureString, useUserOverride: false); - } - Assert.Equal(expected, length.FeetInches.ToString(culture)); + culture = CultureInfo.InvariantCulture; } - - [Theory] - [InlineData(47.9988, "4 ft 0 in")] - [InlineData(-47.9988, "-4 ft 0 in")] - public static void FeetInches_ToString_RoundsTwelveInchesIntoNextFoot(double inch, string expected) + else { - using var _ = new CultureScope(CultureInfo.InvariantCulture); - - var length = Length.FromInches(inch); - - Assert.Equal(expected, length.FeetInches.ToString()); + culture = new CultureInfo(cultureString, useUserOverride: false); } + Assert.Equal(expected, length.FeetInches.ToString(culture)); + } + [Theory] - [MemberData(nameof(InvalidData))] - public void TryParseFeetInches_GivenInvalidString_ReturnsFalseAndZeroOut(string str, string cultureName) + [InlineData(47.9988, "4 ft 0 in")] + [InlineData(-47.9988, "-4 ft 0 in")] + public static void FeetInches_ToString_RoundsTwelveInchesIntoNextFoot(double inch, string expected) { - var formatProvider = new CultureInfo(cultureName, false); - Assert.False(Length.TryParseFeetInches(str, out Length result, formatProvider)); - Assert.Equal(Length.Zero, result); + using var _ = new CultureScope(CultureInfo.InvariantCulture); + + var length = Length.FromInches(inch); + + Assert.Equal(expected, length.FeetInches.ToString()); } } diff --git a/UnitsNet.Tests/CustomCode/LengthTests.cs b/UnitsNet.Tests/CustomCode/LengthTests.cs index 3669bccdbc..196c645e9e 100644 --- a/UnitsNet.Tests/CustomCode/LengthTests.cs +++ b/UnitsNet.Tests/CustomCode/LengthTests.cs @@ -266,6 +266,7 @@ public static void InverseReturnsReciprocalLength(double value, double expected) [InlineData(3, 2.6, 32, "3' - 2 19/32\"")] [InlineData(3, 2.6, 128, "3' - 2 77/128\"")] [InlineData(3, 11.9988, 128, "4' - 0\"")] + [InlineData(0, 0.5d, 128, "1/2\"")] public static void ToArchitecturalString_ReturnsFormatted(double ft, double inch, int fractionDenominator, string expected) { var length = Length.FromFeetInches(ft, inch); diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index 15ae79ed79..721f76106a 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -20,7 +20,7 @@ public FeetInches FeetInches get { QuantityValue totalInches = Inches; - return new FeetInches((BigInteger) (totalInches / InchesInOneFoot), totalInches % InchesInOneFoot); + return new FeetInches((BigInteger)(totalInches / InchesInOneFoot), totalInches % InchesInOneFoot); } } @@ -44,7 +44,8 @@ public static Length FromFeetInches(QuantityValue feet, QuantityValue inches) /// Parsed length. public static Length ParseFeetInches(string str, IFormatProvider? formatProvider = null) { - if (str == null) throw new ArgumentNullException(nameof(str)); + if (str == null) + throw new ArgumentNullException(nameof(str)); if (!TryParseFeetInches(str, out Length result, formatProvider)) { // A bit lazy, but I didn't want to duplicate this edge case implementation just to get more narrow exception descriptions. @@ -163,15 +164,15 @@ public string ToString(IFormatProvider? cultureInfo) // So inches are rounded when converting from base units to feet/inches. // When we do this we check if we rounded inches to 12(InchesInOneFoot). // If it does feet/inches are fixed something like 4 ft 0 in is displayed instead of 3ft 12 in for things very close to 4 e.g. 3.9999 ft - double feet; + BigInteger feet; double inches; - bool isNegative = Feet < 0 || Inches < 0; + var isNegative = Feet < 0 || Inches < 0; var negativeSign = ""; if (isNegative) { feet = -Feet; inches = Math.Round(-Inches); - negativeSign = cultureInfo.NumberFormat.NegativeSign; + negativeSign = (cultureInfo as CultureInfo)?.NumberFormat.NegativeSign ?? "-"; } else { @@ -243,6 +244,7 @@ public string ToArchitecturalString(int fractionDenominator) if (inchTrunc != 0 || numerator == 0) { + inchPart.Append(inchTrunc); } @@ -277,10 +279,11 @@ static int GreatestCommonDivisor(int a, int b) { if (isNegative) { + var negativeSign = CultureInfo.CurrentCulture.NumberFormat.NegativeSign; //negate inches so the output uses a culture correct negative sign. - inchPart = -inchPart; + inchPart.Insert(0, negativeSign); } - + return inchPart.ToString(); }