Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Unit - Grade (Slope) #1337

Closed
mariuszhermansdorfer opened this issue Dec 10, 2023 · 12 comments
Closed

New Unit - Grade (Slope) #1337

mariuszhermansdorfer opened this issue Dec 10, 2023 · 12 comments

Comments

@mariuszhermansdorfer
Copy link

Is your feature request related to a problem? Please describe.
In architecture and landscape architecture, there are multiple ways of representing a grade:
image

It can be defined as:

  • an angle (26.6°)
  • a percentage value (50%)
  • a per mille value (500‰)
  • a ratio written as a fraction (1/2)

image

Describe the solution you'd like
I'd like UnitsNet to group all the above representations within one GradeUnit to allow for seamless conversions. Currently, using the RatioUnit, it is possible to convert from a percent value to per mille, but angle is missing. Also, it'd be great to have support for fractions. similarly to how the LengthUnit supports feet and inches.

@angularsen
Copy link
Owner

I think this sounds useful and has a wide range of applications.
Would you be interested in attempting a pull request?

Detailed steps here:
https://github.com/angularsen/UnitsNet/wiki/Adding-a-New-Unit

I'm happy to assist.

@mariuszhermansdorfer
Copy link
Author

mariuszhermansdorfer commented Dec 10, 2023

Thanks @angularsen!

The description for a PR looks very manageable, I’ll gladly give it a shot. I could definitely use some help with the fraction part, though.

Let me cook up a first sketch and submit it as a draft PR so that I can ask for more specific feedback.

[EDIT] This is relevant:

/// <summary>
/// Outputs feet and inches on the format: {feetValue}' - {inchesValueIntegral}[ / {inchesValueFractional}]"
/// The inches are rounded to the nearest fraction of the fractionDenominator argument and reduced over the greatest common divisor.
/// The fractional inch value is omitted if the numerator is 0 after rounding, or if the provided denominator is 1.
/// </summary>
/// <param name="fractionDenominator">The maximum precision to express the rounded inch fraction part. Use 1 to round to nearest integer, with no fraction.</param>
/// <example>
/// <code>
/// var length = Length.FromFeetInches(3, 2.6);
/// length.ToArchitecturalString(1) => 3' - 3"
/// length.ToArchitecturalString(2) => 3' - 2 1/2"
/// length.ToArchitecturalString(4) => 3' - 2 1/2"
/// length.ToArchitecturalString(8) => 3' - 2 5/8"
/// length.ToArchitecturalString(16) => 3' - 2 5/8"
/// length.ToArchitecturalString(32) => 3' - 2 19/32"
/// length.ToArchitecturalString(128) => 3' - 2 77/128"
/// </code>
/// </example>
/// <exception cref="ArgumentOutOfRangeException">Denominator for fractional inch must be greater than zero.</exception>
public string ToArchitecturalString(int fractionDenominator)
{
if (fractionDenominator < 1)
{
throw new ArgumentOutOfRangeException(nameof(fractionDenominator), "Denominator for fractional inch must be greater than zero.");
}
var inchTrunc = (int)Math.Truncate(Inches);
var numerator = (int)Math.Round((Inches - inchTrunc) * fractionDenominator);
if (numerator == fractionDenominator)
{
inchTrunc++;
numerator = 0;
}
var inchPart = new System.Text.StringBuilder();
if (inchTrunc != 0 || numerator == 0)
{
inchPart.Append(inchTrunc);
}
if (numerator > 0)
{
int GreatestCommonDivisor(int a, int b)
{
while (a != 0 && b != 0)
{
if (a > b)
a %= b;
else
b %= a;
}
return a | b;
}
int gcd = GreatestCommonDivisor(numerator, fractionDenominator);
if (inchPart.Length > 0)
{
inchPart.Append(' ');
}
inchPart.Append($"{numerator / gcd}/{fractionDenominator / gcd}");
}
inchPart.Append('"');
if (Feet == 0)
{
return inchPart.ToString();
}
return $"{Feet}' - {inchPart}";
}

@mariuszhermansdorfer
Copy link
Author

mariuszhermansdorfer commented Dec 12, 2023

@angularsen,

I have a draft PR which passes all the tests except for this one:
image
Any ideas why this can be the case?

[EDIT] Math.Tan takes radians as input, not degrees. Will fix and submit the PR tomorrow.

Also, I defined Percent as the main unit, do you agree with this approach, or would you prefer using Fraction as default?

{
  "Name": "Grade",
  "BaseUnit": "Percent",
  "XmlDocSummary": "The grade, or slope, of a surface is the tangent of its angle compared to the horizontal, indicating its steepness. It's often expressed as a ratio or fraction of vertical rise to horizontal run, where a larger value means a steeper angle.",
  "XmlDocRemarks": "https://en.wikipedia.org/wiki/Grade_%28slope%29",
  "BaseDimensions": {
    "L": 1
  },
  "Units": [
    {
      "SingularName": "Percent",
      "PluralName": "Percent",
      "FromUnitToBaseFunc": "{x}",
      "FromBaseToUnitFunc": "{x}",
      "Localization": [
        {
          "Culture": "en-US",
          "Abbreviations": [ "%" ]
        }
      ]
    },
    {
      "SingularName": "Permille",
      "PluralName": "Permille",
      "FromUnitToBaseFunc": "{x} / 1e1",
      "FromBaseToUnitFunc": "{x} * 1e1",
      "Localization": [
        {
          "Culture": "en-US",
          "Abbreviations": [ "" ]
        }
      ]
    },
    {
      "SingularName": "Degree",
      "PluralName": "Degrees",
      "FromUnitToBaseFunc": "Math.Tan({x}) * 1e2",
      "FromBaseToUnitFunc": "Math.Atan({x} / 1e2) * (180 / Math.PI)",
      "Localization": [
        {
          "Culture": "en-US",
          "Abbreviations": [ "°", "deg" ]
        },
        {
          "Culture": "ru-RU",
          "Abbreviations": [ "°" ]
        }
      ]
    },
    {
      "SingularName": "Fraction",
      "PluralName": "Fractions",
      "FromUnitToBaseFunc": "{x} * 1e2",
      "FromBaseToUnitFunc": "{x} / 1e2",
      "Localization": [
        {
          "Culture": "en-US",
          "Abbreviations": [ "" ]
        }
      ]
    }
  ]
}

@tm-ren
Copy link
Contributor

tm-ren commented Dec 13, 2023

Sorry to interfere.
Is this not simply an extension to the Ratio quantity with more units?
Perhaps more operators are required so that it is easier to obtain?
For example, dividing a length by another length is simply a ratio whose value could be obtained as a percentage as per your first image.
Similarly, any quantity divided by another of the same type is a ratio. Meaning, any quantity multiplied by a ratio is a quantity of the same type.

I personally think its more useful to extend the usage of an existing generic quantity rather than introducing a different quantity that represents the same thing.

@mariuszhermansdorfer
Copy link
Author

I generally agree with your suggestion @tm-ren. But how would you treat the angle conversion to degrees? Does it make sense to have an angle as a generic Ratio unit?

@tm-ren
Copy link
Contributor

tm-ren commented Dec 13, 2023

An angle is a ratio technically. Consider:
arcLength = radius * arcAngleInRadians

Angles are special enough that it makes sense they are quantities in their own right.
How about implementing an easy way to initialise an Angle quantity from a Ratio quantity?

@mariuszhermansdorfer
Copy link
Author

Makes sense. I need a simple way of converting between units too. As far as I know, one can only convert between units within the same Quantity, or can a cross-quantity conversion be easily implemented?

@angularsen
Copy link
Owner

angularsen commented Dec 13, 2023 via email

@mariuszhermansdorfer
Copy link
Author

How do you suggest we proceed from now? I guess #1338 should be ignored then. Please let me know how I can help.

@angularsen
Copy link
Owner

angularsen commented Dec 14, 2023 via email

@angularsen
Copy link
Owner

@mariuszhermansdorfer I propose this:

  1. Drop the PR and create a new one, sorry! 🙈
  2. Add CustomCode/Quantities/Ratio.extra.cs with method public Angle ToSlopeAngle(), the file does not already exist, but see how other .extra.cs files are done
  3. Modify https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/CustomCode/Quantities/Angle.extra.cs to add method public Ratio ToSlopeRatio()

The units you requested should then already be covered by either Angle or Ratio, right?

  • an angle (26.6°) AngleUnit.Degree
  • a percentage value (50%) RatioUnit.Percent
  • a per mille value (500‰) RatioUnit.PartPerThousand
  • a ratio written as a fraction (1/2) RatioUnit.DecimalFraction

Creating string "1/2" is currently not supported, but maybe something we could add to Ratio if useful.
Some related discussion/work in #478, #1106.

@mariuszhermansdorfer
Copy link
Author

Makes sense @angularsen. I'll act accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants