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;
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)
{
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