.net 如何打印存储在浮点数中的精确值?

qxsslcnc  于 2023-06-25  发布在  .NET
关注(0)|答案(4)|浏览(126)

如果我把0.1赋值给一个float:

float f = 0.1;

存储在内存中的实际值不是0.1的精确表示,因为0.1不是一个可以用单精度浮点格式精确表示的数字。如果我计算正确的话,存储的实际值是

0.100000001490116119384765625

但是我无法确定一种方法来让C#打印出这个值。即使我要求它将数字打印到小数点后许多位,它也不会给予正确答案:

// prints 0.10000000000000000000000000000000000000000000000000
Console.WriteLine(f.ToString("F50"));

如何打印存储在浮点数中的精确值;存储器中位模式实际代表的值?
编辑:在其他地方已经引起了我的注意,你可以使用标准格式字符串得到我要求的行为....NET Core和.NET 5.0所以这个问题是.NET Framework特定的,我猜。

u91tlkcl

u91tlkcl1#

这里的基本思想是将float值转换为有理数,然后将有理数转换为小数。
下面的代码(提供BitConverter.SingleToUInt32Bits方法的.Net 6)将打印float的确切值(包括NaN值是否安静/信令、NaN的有效载荷以及是否设置符号位)。请注意,WriteRational方法 * 不是 * 一般适用于所有有理数,因为它没有尝试检测非终止十进制表示:这在这里不是问题,因为float中的所有值都具有2的幂分母。

using System; // not necessary with implicit usings
using System.Globalization;
using System.Numerics;
using System.Text;

static string ExactStringSingle(float value)
{
    const int valueBits = sizeof(float) * 8;
    const int fractionBits = 23; // excludes implicit leading 1 in normal values

    const int exponentBits = valueBits - fractionBits - 1;
    const uint signMask = 1U << (valueBits - 1);
    const uint fractionMask = (1U << fractionBits) - 1;

    var bits = BitConverter.SingleToUInt32Bits(value);
    var result = new StringBuilder();

    if ((bits & signMask) != 0) { result.Append('-'); }

    var biasedExponent = (int)((bits & ~signMask) >> fractionBits);
    var fraction = bits & fractionMask;

    // Maximum possible value of the biased exponent: infinities and NaNs
    const int maxExponent = (1 << exponentBits) - 1;

    if (biasedExponent == maxExponent)
    {
        if (fraction == 0)
        {
            result.Append("inf");
        }
        else
        {
            // NaN type is stored in the most significant bit of the fraction
            const uint nanTypeMask = 1U << (fractionBits - 1);
            // NaN payload
            const uint nanPayloadMask = nanTypeMask - 1;
            // NaN type, valid for x86, x86-64, 68000, ARM, SPARC
            var isQuiet = (fraction & nanTypeMask) != 0;
            var nanPayload = fraction & nanPayloadMask;
            result.Append(isQuiet
                ? FormattableString.Invariant($"qNaN(0x{nanPayload:x})")
                : FormattableString.Invariant($"sNaN(0x{nanPayload:x})"));
        }

        return result.ToString();
    }

    // Minimum value of biased exponent above which no fractional part will exist
    const int noFractionThreshold = (1 << (exponentBits - 1)) + fractionBits - 1;

    if (biasedExponent == 0)
    {
        // zeroes and subnormal numbers
        // shift for the denominator of the rational part of a subnormal number
        const int denormalDenominatorShift = noFractionThreshold - 1;
        WriteRational(fraction, BigInteger.One << denormalDenominatorShift, result);
        return result.ToString();
    }

    // implicit leading one in the fraction part
    const uint implicitLeadingOne = 1U << fractionBits;
    var numerator = (BigInteger)(fraction | implicitLeadingOne);
    if (biasedExponent >= noFractionThreshold)
    {
        numerator <<= biasedExponent - noFractionThreshold;
        result.Append(numerator.ToString(CultureInfo.InvariantCulture));
    }
    else
    {
        var denominator = BigInteger.One << (noFractionThreshold - (int)biasedExponent);
        WriteRational(numerator, denominator, result);
    }

    return result.ToString();
}

static void WriteRational(BigInteger numerator, BigInteger denominator, StringBuilder result)
{
    // precondition: denominator contains only factors of 2 and 5
    var intPart = BigInteger.DivRem(numerator, denominator, out numerator);
    result.Append(intPart.ToString(CultureInfo.InvariantCulture));
    if (numerator.IsZero) { return; }
    result.Append('.');
    do
    {
        numerator *= 10;
        var gcd = BigInteger.GreatestCommonDivisor(numerator, denominator);
        denominator /= gcd;
        intPart = BigInteger.DivRem(numerator / gcd, denominator, out numerator);
        result.Append(intPart.ToString(CultureInfo.InvariantCulture));
    } while (!numerator.IsZero);
}

我已经用valueBitsfractionBits(在方法的第一行中定义)编写了代码中的大部分常量,以便尽可能简单地将此方法适用于double。要执行此操作:

  • valueBits更改为sizeof(double) * 8
  • fractionBits更改为52
  • 将所有uint更改为ulong(包括将1U转换为1UL
  • 调用BitConverter.DoubleToUInt64Bits而不是BitConverter.SingleToUInt32Bits

让代码具有文化性,留给读者的练习是:-)

gmol1639

gmol16392#

是的,这是C#(或.net)中非常有趣的挑战。恕我直言,最简单的解决方案是将float/double与某个巨大的数字相乘,然后将浮点结果转换为BigInteger。例如,这里我们尝试计算1e+51*0.1的结果:

using System.Numerics;
class HelloWorld {
  static void Main() {
    // Ideally, 1e+51*0.1 should be 1 followed by 50 zeros, but =>
    System.Console.WriteLine(new BigInteger(1e+51*0.1));
    // Outputs 100000000000000007629769841091887003294964970946560
  }
}

因为浮点格式的0.1只是近似表示,带有机器ε误差。这就是为什么我们得到这个奇怪的结果而不是100...(50个零)。

qnyhuwrf

qnyhuwrf3#

这个答案与C有关,而不是C#。
留下它,因为它可能提供C#洞察力,因为语言在这方面有相似之处。
如何打印存储在浮点数中的精确值?

// Print exact value with a hexadecimal significant.
printf("%a\n", some_float);
// e.g. 0x1.99999ap-4 for 0.1f

要打印float的十进制值,并具有与所有其他float不同的小数位数:

int digits_after_the_decimal_point = FLT_DECIMAL_DIG - 1;  // e.g. 9 -1
printf("%.*e\n", digits_after_the_decimal_point, some_float);
// e.g. 1.00000001e-01 for 0.1f

要打印小数位数的值是很困难的-而且很少需要。代码可以使用更高的精度。经过某个点(例如20位有效数字),则big_value可能会丢失低位数的正确性,而printf()。在C和IEEE 754中允许这种错误:

int big_value = 19; // More may be a problem.
printf("%.*e\n", big_value, some_float);
// e.g. 1.0000000149011611938e-01 for 0.1f
// for FLT_TRUE_MIN and big_value = 50, not quite right
// e.g. 1.40129846432481707092372958328991613128026200000000e-45

要以十进制形式打印所有float的所有小数位数,请编写一个辅助函数。Example

// Using custom code
// -FLT_TRUE_MIN 
-0.00000000000000000000000000000000000000000000140129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125
kninwzqo

kninwzqo4#

对于.NET Framework,请使用格式字符串G。不完全是,但足以解决浮点错误。

> (0.3d).ToString("G70")
0.29999999999999999
> (0.1d+0.2d).ToString("G70")
0.30000000000000004

投票否决...好吧,我找到了dmath, a math library

> new Deveel.Math.BigDecimal(0.3d).ToString()
0.299999999999999988897769753748434595763683319091796875

相关问题