Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 75 additions & 3 deletions UnitsNet.Tests/CustomCode/LengthTests.FeetInches.cs
Original file line number Diff line number Diff line change
@@ -1,9 +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 Xunit;

namespace UnitsNet.Tests
{
Expand All @@ -20,7 +18,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);
}

Expand Down Expand Up @@ -108,5 +106,79 @@ 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));
}

[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));
}
}
}
6 changes: 2 additions & 4 deletions UnitsNet.Tests/CustomCode/LengthTests.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -52,7 +49,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;

Expand Down Expand Up @@ -253,6 +250,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);
Expand Down
71 changes: 61 additions & 10 deletions UnitsNet/CustomCode/Quantities/Length.extra.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
// 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
{
public partial struct Length
{
private const double InchesInOneFoot = 12;
internal const double InchesInOneFoot = 12;

/// <summary>
/// Converts the length to a customary feet/inches combination.
Expand All @@ -33,7 +31,7 @@ public FeetInches FeetInches
/// </summary>
public static Length FromFeetInches(double feet, double inches)
{
return FromInches(InchesInOneFoot*feet + inches);
return FromInches(InchesInOneFoot * feet + inches);
}

/// <summary>
Expand All @@ -48,7 +46,8 @@ public static Length FromFeetInches(double feet, double inches)
/// <returns>Parsed length.</returns>
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.
Expand Down Expand Up @@ -159,9 +158,37 @@ 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.
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
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);
}

/// <summary>
Expand Down Expand Up @@ -189,16 +216,33 @@ public string ToArchitecturalString(int fractionDenominator)
{
throw new ArgumentOutOfRangeException(nameof(fractionDenominator), "Denominator for fractional inch must be greater than zero.");
}
var feet = Feet;
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);

var inchTrunc = (int)Math.Truncate(Inches);
var numerator = (int)Math.Round((Inches - inchTrunc) * fractionDenominator);

if (numerator == fractionDenominator)
{
inchTrunc++;
numerator = 0;
}

if (inchTrunc == Length.InchesInOneFoot)
{
feet++;
inchTrunc = 0;
}

var inchPart = new System.Text.StringBuilder();

if (inchTrunc != 0 || numerator == 0)
Expand Down Expand Up @@ -233,12 +277,19 @@ int GreatestCommonDivisor(int a, int b)

inchPart.Append('"');

if (Feet == 0)
if (feet == 0)
{
return inchPart.ToString();
}

return $"{Feet}' - {inchPart}";

if (isNegative)
{
//re-negate feet so the output uses a culture correct negative sign.
feet = -feet;
}

return $"{feet}' - {inchPart}";
}
}
}
Loading