哪个C#双精度文字不能完全表示为double? [英] Which C# double literal is not exactly representable as double?

查看:80
本文介绍了哪个C#双精度文字不能完全表示为double?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

维基百科说


例如,十进制数字0.1在任何有限精度的二进制
浮点数中都无法表示

For example, the decimal number 0.1 is not representable in binary floating-point of any finite precision

但是,在C#中

string s = 0.1.ToString(CultureInfo.InvariantCulture);
Console.WriteLine(s);

写入 0.1

我本来期望像 0.099999999999999999 之类的东西。

I would have expected something like 0.099999999999999999 or so.

我是寻找至少一个不能完全表示为双精度的双精度常量示例。

I am looking for at least one example double literal that is not exactly representable as a double.

编辑:

正如其他人指出的那样, 0.1 确实是我一直在寻找的文字,如以下经典代码示例所示:

As others have pointed out, 0.1 is indeed the literal I have been looking for, as the following classic code example shows:

double sum = 0.0;

for (int i = 0; i < 10; i++)
{
    sum += 0.1;
}

Console.WriteLine(sum.Equals(1.0)); // false

将double转换为其他数据类型时,发生的事情很奇怪。不仅是 string 的情况,因为此表达式为true: 0.1m.Equals((decimal)0.1)

There are just weird things going on when doubles are converted to other datatypes. That is not only the case for string, as this expression is true: 0.1m.Equals((decimal)0.1)

推荐答案

我有一个小型源文件,它会打印存储在 double 中的 exact 值。答案末尾有代码,以防万一链接消失。基本上,它获取 double 的确切位,然后从那里开始。它不是很漂亮,也不是很有效,但是它可以起作用:)

I have a small source file which print the exact value stored in a double. Code at the end of the answer, just in case the link goes away. Basically, it fetches the exact bits of the double, and goes from there. It's not pretty or efficent, but it works :)

string s = DoubleConverter.ToExactString(0.1);
Console.WriteLine(s);

输出:

0.1000000000000000055511151231257827021181583404541015625

当您只使用 0.1.ToString()时 BCL会为您截断文本表示形式。

When you just use 0.1.ToString() the BCL truncates the textual representation for you.

对于 double 值,精确可表示-基本上,您需要确定最接近的二进制表示形式是什么,并查看该是否的确切值。基本上,它必须由正确范围和精度内的2的幂(包括2的负幂)组成。

As for which double values are exactly representable - basically, you'd need to work out what the closest binary representation is, and see whether that is the exact value. Basically it needs to be composed of powers of two (including negative powers of two) within the right range and precision.

例如,可以精确地表示4.75,因为它是2 2 + 2 -1 + 2 -2

For example, 4.75 can be represented exactly, as it's 22 + 2-1 + 2-2

源代码:

using System;
using System.Globalization;

/// <summary>
/// A class to allow the conversion of doubles to string representations of
/// their exact decimal values. The implementation aims for readability over
/// efficiency.
/// </summary>
public class DoubleConverter
{    
    /// <summary>
    /// Converts the given double to a string representation of its
    /// exact decimal value.
    /// </summary>
    /// <param name="d">The double to convert.</param>
    /// <returns>A string representation of the double's exact decimal value.</return>
    public static string ToExactString (double d)
    {
        if (double.IsPositiveInfinity(d))
            return "+Infinity";
        if (double.IsNegativeInfinity(d))
            return "-Infinity";
        if (double.IsNaN(d))
            return "NaN";

        // Translate the double into sign, exponent and mantissa.
        long bits = BitConverter.DoubleToInt64Bits(d);
        // Note that the shift is sign-extended, hence the test against -1 not 1
        bool negative = (bits < 0);
        int exponent = (int) ((bits >> 52) & 0x7ffL);
        long mantissa = bits & 0xfffffffffffffL;

        // Subnormal numbers; exponent is effectively one higher,
        // but there's no extra normalisation bit in the mantissa
        if (exponent==0)
        {
            exponent++;
        }
        // Normal numbers; leave exponent as it is but add extra
        // bit to the front of the mantissa
        else
        {
            mantissa = mantissa | (1L<<52);
        }

        // Bias the exponent. It's actually biased by 1023, but we're
        // treating the mantissa as m.0 rather than 0.m, so we need
        // to subtract another 52 from it.
        exponent -= 1075;

        if (mantissa == 0) 
        {
            return "0";
        }

        /* Normalize */
        while((mantissa & 1) == 0) 
        {    /*  i.e., Mantissa is even */
            mantissa >>= 1;
            exponent++;
        }

        /// Construct a new decimal expansion with the mantissa
        ArbitraryDecimal ad = new ArbitraryDecimal (mantissa);

        // If the exponent is less than 0, we need to repeatedly
        // divide by 2 - which is the equivalent of multiplying
        // by 5 and dividing by 10.
        if (exponent < 0) 
        {
            for (int i=0; i < -exponent; i++)
                ad.MultiplyBy(5);
            ad.Shift(-exponent);
        } 
        // Otherwise, we need to repeatedly multiply by 2
        else
        {
            for (int i=0; i < exponent; i++)
                ad.MultiplyBy(2);
        }

        // Finally, return the string with an appropriate sign
        if (negative)
            return "-"+ad.ToString();
        else
            return ad.ToString();
    }

    /// <summary>Private class used for manipulating
    class ArbitraryDecimal
    {
        /// <summary>Digits in the decimal expansion, one byte per digit
        byte[] digits;
        /// <summary> 
        /// How many digits are *after* the decimal point
        /// </summary>
        int decimalPoint=0;

        /// <summary> 
        /// Constructs an arbitrary decimal expansion from the given long.
        /// The long must not be negative.
        /// </summary>
        internal ArbitraryDecimal (long x)
        {
            string tmp = x.ToString(CultureInfo.InvariantCulture);
            digits = new byte[tmp.Length];
            for (int i=0; i < tmp.Length; i++)
                digits[i] = (byte) (tmp[i]-'0');
            Normalize();
        }

        /// <summary>
        /// Multiplies the current expansion by the given amount, which should
        /// only be 2 or 5.
        /// </summary>
        internal void MultiplyBy(int amount)
        {
            byte[] result = new byte[digits.Length+1];
            for (int i=digits.Length-1; i >= 0; i--)
            {
                int resultDigit = digits[i]*amount+result[i+1];
                result[i]=(byte)(resultDigit/10);
                result[i+1]=(byte)(resultDigit%10);
            }
            if (result[0] != 0)
            {
                digits=result;
            }
            else
            {
                Array.Copy (result, 1, digits, 0, digits.Length);
            }
            Normalize();
        }

        /// <summary>
        /// Shifts the decimal point; a negative value makes
        /// the decimal expansion bigger (as fewer digits come after the
        /// decimal place) and a positive value makes the decimal
        /// expansion smaller.
        /// </summary>
        internal void Shift (int amount)
        {
            decimalPoint += amount;
        }

        /// <summary>
        /// Removes leading/trailing zeroes from the expansion.
        /// </summary>
        internal void Normalize()
        {
            int first;
            for (first=0; first < digits.Length; first++)
                if (digits[first]!=0)
                    break;
            int last;
            for (last=digits.Length-1; last >= 0; last--)
                if (digits[last]!=0)
                    break;

            if (first==0 && last==digits.Length-1)
                return;

            byte[] tmp = new byte[last-first+1];
            for (int i=0; i < tmp.Length; i++)
                tmp[i]=digits[i+first];

            decimalPoint -= digits.Length-(last+1);
            digits=tmp;
        }

        /// <summary>
        /// Converts the value to a proper decimal string representation.
        /// </summary>
        public override String ToString()
        {
            char[] digitString = new char[digits.Length];            
            for (int i=0; i < digits.Length; i++)
                digitString[i] = (char)(digits[i]+'0');

            // Simplest case - nothing after the decimal point,
            // and last real digit is non-zero, eg value=35
            if (decimalPoint==0)
            {
                return new string (digitString);
            }

            // Fairly simple case - nothing after the decimal
            // point, but some 0s to add, eg value=350
            if (decimalPoint < 0)
            {
                return new string (digitString)+
                       new string ('0', -decimalPoint);
            }

            // Nothing before the decimal point, eg 0.035
            if (decimalPoint >= digitString.Length)
            {
                return "0."+
                    new string ('0',(decimalPoint-digitString.Length))+
                    new string (digitString);
            }

            // Most complicated case - part of the string comes
            // before the decimal point, part comes after it,
            // eg 3.5
            return new string (digitString, 0, 
                               digitString.Length-decimalPoint)+
                "."+
                new string (digitString,
                            digitString.Length-decimalPoint, 
                            decimalPoint);
        }
    }
}

这篇关于哪个C#双精度文字不能完全表示为double?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆