简体   繁体   中英

Csharp - Convert single to decimal without converting to a string

I am getting a value from an OdcDataReader that comes across as a Single type. I do not want to work with a single in code and so I want to convert it into a decimal. However, it seems that anytime I try to cast it to a decimal (or anything for that matter) it loses several decimal places. I need to keep as much accuracy as possible for my calculations. The only way I could find is to convert the single to a string with a 'R' parameter (Round Trip) and then parse the string into a decimal. I created some Extension Methods to convert them but I was hoping there was a way to convert them without the string conversion and parsing. Keep in mind I need to keep the precision.

Single sng = 1.32397970f;       // 1.32397974
decimal d = (decimal)sng;       // 1.32398M
d = Convert.ToDecimal(sng);     // 1.32398M
string xxx = sng.ToString("G"); // "1.32398"
xxx = sng.ToString("C");        // "$1.32"
xxx = sng.ToString("E");        // "1.323980E+000"
xxx = sng.ToString("e");        // "1.323980e+000"
xxx = sng.ToString("F");        // "1.32"
xxx = sng.ToString("G");        // "1.32398"
xxx = sng.ToString("N");        // "1.32"
xxx = sng.ToString("P");        // "132.40 %"
xxx = sng.ToString("R");        // "1.32397974" - Bingo!

// My extension methods
static class ExtensionMethods
{
    public static decimal ToDecimal(this Single value)
    {
        decimal d = decimal.Parse(value.ToString("R"));
        return d;
    }

    public static decimal ToDecimal(this Single? value, decimal dflt)
    {
        if (!value.HasValue)
            return dflt;

        decimal d = decimal.Parse(value.Value.ToString("R"));
        return d;
    }

    public static decimal? ToDecimal(this Single? value)
    {
        if (!value.HasValue)
            return null;

        decimal d = decimal.Parse(value.Value.ToString("R"));
        return d;
    }
}

Convert it do a double first, then to a decimal and you will get more "precision".

decimal d = (decimal)Convert.ToDouble(sng);

You will get more precision than is actually in the single value to begin with, though.

You could do folowing:

decimal d = Convert.ToDecimal((double)sng);

that will give you result of :

1,32397973537445

I think that the following will satisfy your criteria, even though I find it strange to use a Single for a currency value. The method has successfully round tripped the value based on limited testing. The technique is based on limiting the maximum number of significant digits to 9 as that is the maximum possible for IEEE 754 single-precision binary floating-point value.

Single sng = 1.32397970f;
Decimal dec = SingleToDecimal(sng);
Single sng2 = Convert.ToSingle(dec);
bool b = sng.Equals(sng2);  

 private static decimal SingleToDecimal(float s)
 {
    // From: IEEE 754 single-precision binary floating-point format: binary32
    // https://en.wikipedia.org/wiki/Single-precision_floating-point_format#IEEE_754_single-precision_binary_floating-point_format:_binary32

    // The IEEE 754 standard specifies a binary32 as having:

    //    Sign bit: 1 bit
    //    Exponent width: 8 bits
    //    Significand precision: 24 bits (23 explicitly stored)

    //This gives from 6 to 9 significant decimal digits precision
    const int maxSignificantDigits = 9;

    decimal sign = (s < 0F) ? decimal.MinusOne : decimal.One;
    s = Math.Abs(s);

    double whole = Convert.ToDouble(Math.Floor(s));
    double fraction = s - whole;

    int roundOffDigits = 0;
    if (whole > 0)
    {
        int numDigitsInWhole = Convert.ToInt32(Math.Floor(Math.Log10(whole))) + 1;
        roundOffDigits = maxSignificantDigits - numDigitsInWhole;
    }
    else
    {
        int numLeadZerosInFraction = Convert.ToInt32(Math.Floor(-Math.Log10(fraction)));
        roundOffDigits = maxSignificantDigits + numLeadZerosInFraction;
    }

    fraction = Math.Round(fraction, roundOffDigits, MidpointRounding.ToEven);

    return new decimal(fraction + whole) * sign;
 }

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