Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF SmartPrecisionConverter

0.00/5 (No votes)
19 Jan 2017 1  
A value converter that allows smart rounding for text display

Introduction

In many WPF applications that display data, you could be dealing with a large variety of data. Numerical values in the same grid can be in the millions and in fractions. In that case, rounding the number simply isn't enough because if there is 1,234,567.894, it's likely the decimal precision is completely meaningless; however, if I have .0000894 and I round to three decimal places, I could be losing all of the data.

It's possible to use scientific notation, but scientfic notation can make it very difficult to compare values. Additionally, scientific notation tends to be a very poor display format for .01 to 1,000 which may cover a good amount of the data to be displayed.

The SmartPrecisionConverter simply applies an algorithm to round based on the number. In the case of large numbers, there's no decimals and in the case of small numbers, you specified number of non-zero digits.

Using the Code

Here are some examples of the expected output:

  • 1,234.5678 with 3 non-zero significant digits auto adjusted should be 1,234
  • 234.5678 with 3 non-zero significant digits auto adjusted should be 234
  • 34.5678 with 3 non-zero significant digits auto adjusted should be 34.6
  • .0005678 with 3 non-zero significant digits auto adjusted should be .000568

Below is the complete code for the converter...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;

namespace NSanity.Wpf.Core.Converters
{
    [ValueConversion(typeof(object), typeof(string))]
    public class SmartPrecisionConverter : IValueConverter
    {
        private const double SignificantNonZeroDigits = 3;

        /// <summary>
        /// Converts a numerical value to a rounded value with a configured or 
        /// algorithm applied number of non-zero digits.
        /// </summary>
        /// <param name="value">The numerical value.</param>
        /// <param name="targetType">Hopefully a string.</param>
        /// <param name="parameter">NULL for no formatting.  Double.NaN for auto. 
        /// Or the specified number of decimal places.</param>
        /// <param name="culture">Yes please.</param>
        /// <returns>A formatted numerical value.</returns>
        public object Convert(object value, Type targetType, object parameter, 
                              System.Globalization.CultureInfo culture)
        {
            try
            {
                if (parameter != null)
                {
                    double digits = Double.Parse(parameter.ToString());
                    double number = Double.Parse(value.ToString());

                    if (digits.Equals(Double.NaN) && Math.Abs(number) < 
                                    Math.Pow(10, SignificantNonZeroDigits) && number != 0)
                    {
                        return Math.Round(number, (int)Math.Floor
                                     (SignificantNonZeroDigits - Math.Log10(Math.Abs(number))));
                    }
                    else if (digits.Equals(Double.NaN) && Math.Abs(number) >= 
                                      Math.Pow(10, SignificantNonZeroDigits))
                    {
                        return Math.Round(number, 0);
                    }
                    else if (!digits.Equals(Double.NaN))
                    {
                        return Math.Round(number, (int)digits);
                    }
                }

                return value;
            }
            catch
            {
                return value;
            }
        }

        public object ConvertBack(object value, Type targetType, 
                object parameter, System.Globalization.CultureInfo culture)
        {
            // Converting back could be very dangerous because of loss of precision - so don't do it.
            throw new NotImplementedException();
        }
    }
}

Because I'm writing this as a tip/trick, I don't intend to include a sample project - for now anyway.

Part of the reason why this handles fixed digit formatting as well is so you can have settings and/or menu item selection to dynamically adjust precision.

Points of Interest

Couple notable items with the implementation:

  • The value is passed as an object and converted by parsing its string conversion. Rather inefficient but allows the greatest flexibility for data types this could support.
  • I do catch all exceptions and return the original value in many of my converters just in case there's something unexpected the UI doesn't come crumbling down, but obviously you may want to throw this or handle it differently.

History

  • 2017-01-19: Initial revision

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here