UnitsNet icon indicating copy to clipboard operation
UnitsNet copied to clipboard

Introduce conversion methods between Angle and Ratio

Open mariuszhermansdorfer opened this issue 2 years ago • 3 comments

@angularsen, following your suggestions from #1337, here is an updated PR adding the conversion methods between Angle and Slope.

How do you suggest we proceed with creating strings like "1 in 2, 1 in 5, 1:2, 1:5" etc. ?

mariuszhermansdorfer avatar Dec 22 '23 18:12 mariuszhermansdorfer

I could simply add the following method to Ratio.extra.cs:

        /// <summary>
        /// Converts the ratio to a string representing slope in a more detailed fraction format.
        /// </summary>
        /// <param name="cultureInfo">The culture info to format the string. If null, the current culture is used.</param>
        /// <returns>A string representing the slope in a detailed fraction format like "3 in 4" or "3 : 4".</returns>
        public string ToDetailedSlopeString(CultureInfo cultureInfo)
        {
            cultureInfo = cultureInfo ?? CultureInfo.CurrentCulture;

            // Find the closest fraction to represent the slope
            (int numerator, int denominator) = ConvertToFraction(this.DecimalFractions);

            string slopeFormat = cultureInfo.Name == "en-US" ? "{0} in {1}" : "{0} : {1}";

            return string.Format(cultureInfo, slopeFormat, numerator, denominator);
        }

        private (int, int) ConvertToFraction(double decimalFraction, int maxDenominator = 100)
        {
            if (decimalFraction == 0)
            {
                return (0, 1);
            }

            int sign = Math.Sign(decimalFraction);
            decimalFraction = Math.Abs(decimalFraction);

            int wholePart = (int)decimalFraction;
            decimalFraction -= wholePart;

            int lowerNumerator = 0, lowerDenominator = 1, upperNumerator = 1, upperDenominator = 1;

            while (lowerDenominator <= maxDenominator && upperDenominator <= maxDenominator)
            {
                int middleDenominator = lowerDenominator + upperDenominator;
                int middleNumerator = lowerNumerator + upperNumerator;

                if (middleDenominator > maxDenominator) break;

                double middleValue = (double)middleNumerator / middleDenominator;

                if (decimalFraction < middleValue)
                {
                    upperNumerator = middleNumerator;
                    upperDenominator = middleDenominator;
                }
                else if (decimalFraction > middleValue)
                {
                    lowerNumerator = middleNumerator;
                    lowerDenominator = middleDenominator;
                }
                else
                {
                    lowerNumerator = upperNumerator = middleNumerator;
                    lowerDenominator = upperDenominator = middleDenominator;
                    break;
                }
            }

            // Choose the fraction that is closer to the original decimalFraction
            double lowerDiff = decimalFraction - (double)lowerNumerator / lowerDenominator;
            double upperDiff = (double)upperNumerator / upperDenominator - decimalFraction;

            int finalNumerator, finalDenominator;

            if (lowerDiff < upperDiff)
            {
                finalNumerator = wholePart * lowerDenominator + lowerNumerator;
                finalDenominator = lowerDenominator;
            }
            else
            {
                finalNumerator = wholePart * upperDenominator + upperNumerator;
                finalDenominator = upperDenominator;
            }

            return (finalNumerator * sign, finalDenominator);
        }

mariuszhermansdorfer avatar Dec 22 '23 18:12 mariuszhermansdorfer

I don't know what the correct term is, but I'd expect something like ToRatioString() to output on the format 1:3 for 1 to 3 ratio.

This could be a method just like you proposed above. Then create some unit tests to verify the behavior for a range of values. For example, how does it work when the value is almost but not exactly 1:3, like 1:2.999 ? I assume it would be represented as 1000:2999? Or should there be some option to perform rounding? I'm not sure.

angularsen avatar Dec 22 '23 22:12 angularsen

There is some similar work here, for FeetInches.ToArchitecturalString(): https://github.com/angularsen/UnitsNet/blob/a95e33fafa466bf27706b54fd6ea9069a9dce97c/UnitsNet/CustomCode/Quantities/Length.extra.cs#L238-L314

Maybe it can be reused or take inspiration from.

angularsen avatar Dec 22 '23 22:12 angularsen

This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar Jul 08 '24 17:07 github-actions[bot]

This PR was automatically closed due to inactivity.

github-actions[bot] avatar Jul 16 '24 02:07 github-actions[bot]