-2

In .Net 7, there is IFloatingPoint. I read the following code for .Net 6. It uses struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>. Are all these constraint necessary?

Code:

using System;

namespace MyExtensions
{
    public static class NumericExtensions
    {
        public static T Round<T>(this T value, int decimalPlaces) where T : 
            struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
        {
            decimal decimalValue = Convert.ToDecimal(value);
            decimal rounded = Math.Round(decimalValue, decimalPlaces);
            return (T)Convert.ChangeType(rounded, typeof(T));
        }
    }
}
ca9163d9
  • 27,283
  • 64
  • 210
  • 413
  • 4
    (1) It certainly compiles and runs without the type constraints. Necessary for what? (2) It's already a garbage method that trashes large `double` values. I don't see the merit in making this generic if it's just going to trash the data stored in the generic type. Surely conceived of by a C++ programmer. – Wyck Dec 09 '22 at 20:23
  • (1) Adding type constraint `struct` can prevent the method from being used as `"abc".Round(3)`. What's the purpose of adding `IFormattable, IConvertible`, etc? (this is not my code) (2) I don't want to write multiple functions for `float`, `double`, `decimal`, etc. – ca9163d9 Dec 09 '22 at 20:42
  • If you're looking for performance, a generic method isn't the way to do it. I would just get rid of the extension method and call `Math.Round()` where you need to. – Gabriel Luci Dec 09 '22 at 20:52
  • What does this mean: _"The AI gave the following method"_? – Flydog57 Dec 09 '22 at 20:53
  • 2
    Related: [Is there a constraint that restricts my generic method to numeric types?](https://stackoverflow.com/q/32664/1563833) And .NET 7's [IFloatingPoint.Round](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.ifloatingpoint-1.round?view=net-7.0#system-numerics-ifloatingpoint-1-round(-0)) – Wyck Dec 09 '22 at 20:55
  • 2
    There are already `Math.Round` overloads for `double` and `decimal`. What else do you want to round? If you need `float`, write a simply method that casts the float to double and casts the result back to a float. Your generic results in every call walking around the block before returning – Flydog57 Dec 09 '22 at 20:59
  • 3
    _"the code is actually from chat GCP"_ - `posting code from ChatGPT is not allowed`. https://stackoverflow.com/help/gpt-policy. How do we know your code isn't being sourced from another's post on SO? You don't even have a citation to ChatGPT in your post let alone to the ultimate author which for all we know is a ChatGPT-web-scanned e-book. –  Dec 09 '22 at 21:22
  • Re: ChatGPT. That's pretty good code considering it was written by a computer that doesn't have a clue what it's doing. Of course, it reads like it was written by someone (/something) that didn't have a clue what it was doing (and definitely not by a C++ programmer :-)) – Flydog57 Dec 09 '22 at 21:59
  • @Flydog57 Yes, probably heat of the moment. ;) _"ChatGP"_ - lolz agreed! :D –  Dec 09 '22 at 22:44
  • OK updated the question. It really is not that important where the code originally comes from. – ca9163d9 Dec 10 '22 at 22:54

1 Answers1

3

Rounding only makes sense for floating point numbers. In C#, we only have three types of floating point numbers (float, double, decimal). The Math.Round methods exists in two variants, one for double and one for decimal. Both use different rounding algorithms internally. Using the rounding method for doubles on decimals or vice versa can lead to unexpected results.

Since we only have three relevant types, the simplest approach for generating extension methods is to write one for each type. One line is enough per type:

public static class NumericExtensions
{
    public static double Round(this double value, int decimalPlaces) => Math.Round(value, decimalPlaces);
    public static decimal Round(this decimal value, int decimalPlaces) => Math.Round(value, decimalPlaces);
    public static float Round(this float value, int decimalPlaces) => (float)Math.Round(value, decimalPlaces);
}

Starting with .NET 7, the numeric types in the framework were extended to implement generic interfaces that make it easier to write generic methods that deal with numbers. There is now also Round() methods on the floating point types themselves, so that we don't have to use Math.Round() anymore but can use the Round() method from the actual type.

With this new support, we can write a single extension method that supports all floating point types (also including the new Half type):

public static class NumericExtensions
{
    public static T Round<T>(this T value, int decimalPlaces) where T : IFloatingPoint<T> => T.Round(value, decimalPlaces);
}

NineBerry
  • 26,306
  • 3
  • 62
  • 93
  • I totally agree with the rounding approach you gave. And in .Net 7, `IFloatingPoint` is a perfect constraint for the generic. Let's assume we need a generic method for floating point method before .Net 7, is `struct, IComparable, IFormattable, IConvertible, IComparable, IEquatable` the best try for `IFloatingPoint`? – ca9163d9 Dec 10 '22 at 22:42
  • Even with those restrictions, the extension method will be available for types like `int` and `DateTime` where rounding makes no sense. In older versions, there is no generic type restriction that will match floating point types but no other types. – NineBerry Dec 10 '22 at 22:59