Saturday, December 15, 2018

Number formattings for user prompt

You probably know about formatting numbers for floating types. But do you whish sometimes an easy way to format them with keeping the precision but loosing useless trailing zero value digits? I got that whish too, so I built some helper methods.
The minimum precision digits will be kept (as defined in user profile, usually 2 digits), but higher precision will also be kept, but without useless zeros at the end of formatted result.

Example:
Your number is a decimal with the value 25.222456000 you want to show 25.222456
Your number is a decimial with the value 25.7 you want to show 25.70


The implementation is for the decimal type, but you can easely convert or duplicate it for any other floating type.


using System;
using System.Globalization;

namespace AnyNamespace
{
    /// <summary>
    /// Provides functionality to <c>decimal</c> data type
    /// </summary>
    public static class DecimalExtender
    {
        /// <summary>
        /// Formats a decimal according to current user profile settings
        /// (often called country region settings),
        /// but has option to keep precision though
        /// </summary>
        /// <param name="input">
        /// The value to format
        /// </param>
        /// <param name="format">
        /// The value type:
        /// <c>n</c> is for number,
        /// <c>p</c> is for percent,
        /// <c>c</c> is for currency,
        /// for more infomation about format constants
        /// see <see href="https://docs.microsoft.com/de-de/dotnet/standard/base-types/standard-numeric-format-strings?view=netframework-4.7.2"/>
        /// </param>
        /// <param name="decimalDigitPropertyName">
        /// The property on <c>NumberFormatInfo</c> type
        /// to use to retrieve minimal number of digits for precision,
        /// for more information see <see cref="NumberFormatInfo"/>
        /// </param>
        /// <param name="keepPercision">
        /// Keeps precision over minimal number of digits for precision
        /// </param>
        /// <returns>
        /// The formatted number as string
        /// </returns>
        static string Format(decimal input, string format,
            string decimalDigitPropertyName, bool keepPercision)
        {
            var cultureFormat = (NumberFormatInfo)CultureInfo.CurrentCulture
                .NumberFormat.Clone();

            /* if PercentDecimalDigits is used,
             * we need to take care of x100 multiplication
             * when detecting decimal palces ...
             */
            var decimalPlaces = (decimalDigitPropertyName ==
                nameof(NumberFormatInfo.PercentDecimalDigits)
                    ? (input * 100)
                        : input) % 1;

            int requiredDecimalPlacesLength = 0;

            if (decimalPlaces != 0)
            {
                var decimalString = Math.Abs(decimalPlaces)
                    .ToString(CultureInfo.InvariantCulture);

                while (decimalString.Length > 2
                    && decimalString.Substring(decimalString.Length - 1) ==
                        0.ToString(CultureInfo.InvariantCulture))
                {
                    decimalString = decimalString
                        .Substring(0, decimalString.Length - 1);
                }

                requiredDecimalPlacesLength = decimalString.Length - 2;
            }

            if (keepPercision
                && requiredDecimalPlacesLength
                    > (int)cultureFormat.GetType()
                        .GetProperty(decimalDigitPropertyName)
                            .GetValue(cultureFormat))
            {
                // extend the precision to exact after point lenght
                cultureFormat.GetType().GetProperty(decimalDigitPropertyName)
                    .SetValue(cultureFormat, requiredDecimalPlacesLength);
            }

            string ret = input.ToString(format, cultureFormat);

            return ret;
        }

        /// <summary>
        /// Formats a number value, but keeps decimal places if required
        /// </summary>
        /// <param name="input">
        /// The figure to format
        /// </param>
        /// <param name="keepPercision">
        /// Keeps precision over minimal number of digits for precision
        /// </param>
        /// <returns>
        /// The formated number
        /// </returns>
        /// <remarks>
        /// The method uses the <c>n</c> constant for number format formation,
        /// for more information see
        /// <see href="https://docs.microsoft.com/de-de/dotnet/standard/base-types/standard-numeric-format-strings?view=netframework-4.7.2"/>
        /// </remarks>
        public static string FormatNumber(this decimal input, bool keepPrecision)
        {
            return Format(input, "n",
                nameof(NumberFormatInfo.NumberDecimalDigits), keepPrecision);
        }

        /// <summary>
        /// Formats a percent value, but keeps decimal places if required
        /// </summary>
        /// <param name="input">
        /// The figure to format
        /// </param>
        /// <param name="keepPercision">
        /// Keeps precision over minimal number of digits for precision
        /// </param>
        /// <returns>
        /// The formated percent
        /// </returns>
        /// <remarks>
        /// The method uses the <c>p</c> constant for percent format formation,
        /// for more information see
        /// <see href="https://docs.microsoft.com/de-de/dotnet/standard/base-types/standard-numeric-format-strings?view=netframework-4.7.2"/>
        /// </remarks>
        public static string FormatPercent(this decimal input,
            bool keepPrecision)
        {
            return Format(input, "p",
                nameof(NumberFormatInfo.PercentDecimalDigits), keepPrecision);
        }

        /// <summary>
        /// Formats a currency value, but keeps decimal places if required
        /// </summary>
        /// <param name="input">
        /// The figure to format
        /// </param>
        /// <param name="keepPercision">
        /// Keeps precision over minimal number of digits for precision
        /// </param>
        /// <returns>
        /// The formated currency
        /// </returns>
        /// The method uses the <c>c</c> constant for currency format formation,
        /// for more information see
        /// <see href="https://docs.microsoft.com/de-de/dotnet/standard/base-types/standard-numeric-format-strings?view=netframework-4.7.2"/>
        /// </remarks>
        public static string FormatCurrency(this decimal input,
            bool keepPrecision)
        {
            return Format(input, "c",
                nameof(NumberFormatInfo.CurrencyDecimalDigits), keepPrecision);
        }

        /// <summary>
        /// Formats a number value, but keeps decimal places if required
        /// </summary>
        /// <param name="input">
        /// The figure to format
        /// </param>
        /// <param name="keepPercision">
        /// Keeps precision over minimal number of digits for precision
        /// </param>
        /// <returns>
        /// The formated number
        /// </returns>
        public static string FormatNumber(this decimal? input,
            bool keepPrecision)
        {
            return FormatNumber((decimal)input, keepPrecision);
        }

        /// <summary>
        /// Formats a percent value, but keeps decimal places if required
        /// </summary>
        /// <param name="input">
        /// The figure to format
        /// </param>
        /// <param name="keepPercision">
        /// Keeps precision over minimal number of digits for precision
        /// </param>
        /// <returns>
        /// The formated percent
        /// </returns>
        public static string FormatPercent(this decimal? input,
            bool keepPrecision)
        {
            return FormatPercent((decimal)input, keepPrecision);
        }

        /// <summary>
        /// Formats a currency value, but keeps decimal places if required
        /// </summary>
        /// <param name="input">
        /// The figure to format
        /// </param>
        /// <param name="keepPercision">
        /// Keeps precision over minimal number of digits for precision
        /// </param>
        /// <returns>
        /// The formated currency
        /// </returns>
        public static string FormatCurrency(this decimal? input,
            bool keepPrecision)
        {
            return FormatCurrency((decimal)input, keepPrecision);
        }
    }
}


And that's how you use it:


using System;
using AnyNamespace;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // example when you're using en-us default culture settings

            Console.WriteLine((2.5500m).FormatNumber(false));
            // => to 2.55

            Console.WriteLine((2.55546400m).FormatNumber(false));
            // => to 2.56

            Console.WriteLine((2.5500m).FormatNumber(true));
            // => to 2.55

            Console.WriteLine((2.55546400m).FormatNumber(true));
            // => to 2.555464

            Console.WriteLine((2.5500m).FormatCurrency(false));
            // => to $2.55

            Console.WriteLine((2.55546400m).FormatCurrency(false));
            // => to $2.56

            Console.WriteLine((2.5500m).FormatCurrency(true));
            // => to $2.55

            Console.WriteLine((2.55546400m).FormatCurrency(true));
            // => to $2.555464

            Console.WriteLine((2.5500m).FormatPercent(false));
            // => to 255.00%

            Console.WriteLine((2.55546400m).FormatPercent(false));
            // => to 255.55%

            Console.WriteLine((2.5500m).FormatPercent(true));
            // => to 255.00%

            Console.WriteLine((2.55546400m).FormatPercent(true));
            // => to 255.5464%


            Console.ReadKey();

        }
    }
}

Sunday, December 2, 2018

Rounding numbers for business applications

Today an article about to round floating type values. You've probably noticed, that Math.Round(...) asks for decimal precision while in business applications, you mostly have a value portion for precision (such as 0.5).

Even more, if you perform Math.Round(0.5, 0) you may expect, that the result is 1. But no, since the default midpoint rounding is to even, you will get 0, while Math.Round(1.5, 0) still results in 2. A little bit strange, eh? Well, I'm not mathematican and I please you to bear with me, because I cannot explain that 'midpoint rounding to even versus midpoint rounding away from zero'-thingy.
But anyway, for business applications you would rather expect:
  • Round(input 0.5, precisionDigits 0) is 1
  • Round(input 1.5, precisionDigits 0) is 2
To avoid spending much time in mathematican behaviors when round a number, you can just use the following methods:

/// <summary>
/// Rounds a decimal value in commercial way
/// </summary>
/// <param name="input">
/// The figure subject to round
/// </param>
/// <param name="roundingSize">
/// The rounding size
/// </param>
/// <returns>
/// The round result
/// </returns>
public static decimal CommercialRound(decimal input, decimal roundingSize)
{
    decimal ret = input;

    if (roundingSize != 0)
    {
        ret = Math.Round(input / roundingSize, MidpointRounding.AwayFromZero) 
            * roundingSize;
    }
    return ret;
}

/// <summary>
/// Rounds down to given rounding size
/// </summary>
/// <param name="input">
/// The figure subject to round
/// </param>
/// <param name="roundingSize">
/// The size to round to
/// </param>
/// <returns>
/// The rounded value
/// </returns>
public static decimal RoundDown(decimal input, decimal roundingSize)
{
    decimal ret = input;

    if (roundingSize != 0)
    {
        ret = Math.Floor(input / roundingSize) * roundingSize;
    }
    return ret;
}

/// <summary>
/// Rounds up to given rounding size
/// </summary>
/// <param name="input">
/// The figure subject to round
/// </param>
/// <param name="roundingSize">
/// The size to round to
/// </param>
/// <returns>
/// The rounded value
/// </returns>
public static decimal RoundUp(decimal input, decimal roundingSize)
{
    decimal ret = input;

    if (roundingSize != 0)
    {
        ret = Math.Ceiling(input / roundingSize) * roundingSize;
    }
    return ret;
}


As you can see, they work with the decimal type, but I'm quite sure you can create overloaded method with any other floating data type you work with.
Furthermore you can convert these methods easely to extension methods, just add the this keyword before the type keyword of the first parameter in the method signature, like:

public static decimal CommercialRound(this decimal input, decimal roundingSize)

That way you can write code like:


var decimalValue = 15.54887m;
var roundedDecimalValue = decimalValue.CommercialRound(0.25);


Happy rounding :)