简体   繁体   中英

How to implement a robust truncate method in C#?

I know .NET has one built-in but it's an external call. Anyone knows why?

But the actual question is how to implement a truncate from scratch where the user will be able to specify how many digits to keep? Is multiplying a number by say 100 and then dividing it by the same amount enough? Or is there a better implementation?

Something like:

Truncate(12.3456789, 3);
// returns 12.345

You'd probably want to look at IEEE floating-point integers.

You can then use unsafe code to modify the numbers, like:

unsafe
{
    double* pValue = &value;
    var asLong = *(long*)pValue;
    do whatever you want with asLong, e.g. bit-masking it, etc.; 
}

As to the 'why': I have no idea, though the Shared Source CLI may provide clues. My guess would be that it might be because of performance optimizations.

The classic way:

var x = 1.2345678;
var tr = 4;

var truncated = (int) (x * Math.Pow(10, tr)) / Math.Pow(10, tr);

would give 1.2345;

Here is how I would do it. In C++, and I think in C# as well, you could get the integer part of a floating point number by casting it to an integer type.

double Truncate (double num, int dig)
{
    if (dig > 15) dig = 15; // Don't overflow
    long p = Math.Pow (10, dig);

    // Save the integer part, so that we don't overflow
    long integer_part = (long)num;

    // Fractional part * 10^dig
    double frac = (num - Convert.ToDouble(integer_part)) * p;
    long frac_trunc = (long)frac;

    // Final result
    double result = Convert.ToDouble(integer_part) + (Convert.ToDouble(frac_trunc) / p);
    return result;
}

Is multiplying a number by say 100 and then dividing it by the same amount enough?

That should work, but be careful because with large numbers, or high number of digits, you can easily overflow, and it will give you weird results.

var result = Math.Round(12.3456789, 3);

圆形数学方法(Double,Int32)

It is not clear the reason you think that Truncate should keep the decmial value.

The default method within .NET is described by the following statement:

The integral part of d; that is, the number that remains after any fractional digits have been discarded.

It seems like what you want to use is either to format the output string of an double/decmial value and/or use the Math.Round(double, int) function instead.

You could just use:

double num = 2.22939393; num  = Convert.ToDouble(num.ToString("#0.000")); 

From one of the duplicate questions:

public static decimal TruncateToDecimalPlace(this decimal numberToTruncate, int decimalPlaces) 
{     
       decimal power = (decimal)(Math.Pow(10.0, (double)decimalPlaces));      
       return Math.Truncate((power * numberToTruncate)) / power; 
} 

I understand this still uses the Truncate method. I only provided this code since you wanted a Truncate method that would keep the decmial value of a number and the default built-in Truncate method does not.

You could always just use this:

Math.Round does NOT call the SplitFractionDouble from what I can tell

     private static unsafe double InternalRound(double value, int digits, MidpointRounding mode) {
            if (Abs(value) < doubleRoundLimit) {
                Double power10 = roundPower10Double[digits];
                value *= power10;
                if (mode == MidpointRounding.AwayFromZero) {                
                    double fraction = SplitFractionDouble(&value); 
                    if (Abs(fraction) >= 0.5d) {
                        value += Sign(fraction);
                    }
                }
                else {
                    // On X86 this can be inlined to just a few instructions
                    value = Round(value);
                }
                value /= power10;
            }
            return value;
          }           


     public static double Round(double value, int digits)
      {
           if ((digits < 0) || (digits > maxRoundingDigits))
               throw new ArgumentOutOfRangeException("digits", Environment.GetResourceString("ArgumentOutOfRange_RoundingDigits"));
           return InternalRound(value, digits, MidpointRounding.ToEven);                     
      }

  public static double Round(double value, MidpointRounding mode) {
         return Round(value, 0, mode);
      }

      public static double Round(double value, int digits, MidpointRounding mode) {
          if ((digits < 0) || (digits > maxRoundingDigits))
              throw new ArgumentOutOfRangeException("digits", Environment.GetResourceString("ArgumentOutOfRange_RoundingDigits"));
          if (mode < MidpointRounding.ToEven || mode > MidpointRounding.AwayFromZero) {            
              throw new ArgumentException(Environment.GetResourceString("Argument_InvalidEnumValue", mode, "MidpointRounding"), "mode");
          }
          return InternalRound(value, digits, mode);                           
      }

      public static Decimal Round(Decimal d) {
        return Decimal.Round(d,0);
      }

      public static Decimal Round(Decimal d, int decimals) {
        return Decimal.Round(d,decimals);
      }

      public static Decimal Round(Decimal d, MidpointRounding mode) {
        return Decimal.Round(d, 0, mode);
      }

      public static Decimal Round(Decimal d, int decimals, MidpointRounding mode) {
        return Decimal.Round(d, decimals, mode);
      }


public static Decimal Floor(Decimal d) { return Decimal.Floor(d); }

[MethodImplAttribute(MethodImplOptions.InternalCall)] public static extern double Floor(double d);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM