如何在C#中获取IEEE-754浮点数的精确无缩放尾数
我所说的“精确”是指IEEE-754无法精确表示十进制数,这似乎是问题的核心。
所有十进制数都可以用科学计数法表示,如下所示。我添加了“无缩放值”列,即把所有有效数字移到小数点左侧后的十进制值;例如:
| 常规形式 | 科学计数法 | 无缩放值 |
|---|---|---|
| 0.01 | 1.01e+0 | 101 |
| 0.025 | 2.5e-2 | 25 |
| 123 | 1.23e+2 | 123 |
| 12345678 | 1.2345678e+7 | 12345678 |
| 123.456 | 1.23456e+2 | 123456 |
| 12345.678901234567 | 1.2345678901234567e+4 | 12345678901234567 |
| System.Double.MaxValue | 1.7976931348623157e+308 | 17976931348623157 * (10 ^ 308) |
以下代码利用.NET 7.0泛型数学分别从IEEE-754值中获取指数和尾数。请注意,它们获取的是十进制(基10)的指数和尾数,而非其二进制(基2)表示:
GetExponent
private static T GetExponent<T>(T value) where T : IBinaryFloatingPointIeee754<T> { if (T.IsNaN(value) || T.IsInfinity(value) || T.IsZero(value)) return T.Zero; T absValue = T.Abs(value); T log10 = T.Log10(absValue); return T.Floor(log10); }
GetMantissa
private static T GetMantissa<T>(T value) where T : IBinaryFloatingPointIeee754<T> { T ten = T.CreateChecked(10); T exponent = GetExponent(value); T factor = T.Pow(ten, exponent); return value / factor; }
基于上述表格,这些函数会生成以下值:
| 科学计数法 | 尾数 | 指数 |
|---|---|---|
| 1.01e+0 | 1.01 | 0 |
| 2.5e-2 | 2.5 | -2 |
| 1.23e+2 | 1.23 | 2 |
| 1.2345678e+7 | 1.2345678 | 7 |
| 1.23456e+2 | 1.23456 | 2 |
| 1.2345678901234567e+4 | 1.2345678901234567 | 4 |
| 1.7976931348623157e+308 | 1.7976931348623157 | 308 |
目前一切顺利!现在我希望将尾数乘以10,直到所有有效数字都移到小数点左侧。以下函数用于获取无缩放尾数:
private static BigInteger GetUnscaledMantissa<T>(T value) where T : IBinaryFloatingPointIeee754<T> { T ten = T.CreateChecked(10); T mantissa = GetMantissa(value); T factor = T.One; // While the remainder isn't zero... while (mantissa * factor % T.One != T.Zero) { // ..multiply factor by 10. factor *= ten; } BigInteger result = BigInteger.CreateChecked(mantissa * factor); // trim any trailing zeros, which sometimes occurs. while (result % 10 == 0) result /= 10; return result; }
我们来看一下结果:
| 尾数 | 无缩放值 |
|---|---|
| 1.01 | 101 |
| 2.5 | 25 |
| 1.23 | 123 |
| 1.2345678 | 12345678 |
| 1.23456 | 123456 |
| 1.2345678901234567 | 12345678901234566 |
| 1.7976931348623157 | 17976931348623158 |
总体而言,GetUnscaledMantissa函数返回的值是正确的,但请注意加粗标注的异常值;它们并不完全正确,似乎在某些情况下数值会被向上或向下舍入。
问题
虽然我理解这是IEEE-754二进制浮点数的固有特性,但有没有办法,或者说我该如何修改GetUnscaledMantissa函数,使其能够在所有情况,或至少更多/大多数情况下准确返回无缩放尾数?
(注:我知道这是可行的,只是并不简单)
更新
鉴于关于该主题的深入讨论,似乎大家对我的目标存在一些误解,希望以下内容能澄清这一点。
暂时忘掉IEEE-754!我们只关注纯数学。
以下数字以科学计数法表示,及其对应的完整形式:
表A
| 科学计数法 | 完整形式 |
|---|---|
| 1.00001e+50 | 100001000000000000000000000000000000000000000000000 |
| -3.345233391e+45 | -3345233391000000000000000000000000000000000000 |
| 1.7976931348623157E+308 | 1797693134862315700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |
这些科学计数法表示的值数学上是准确的!
现在,误解似乎出现在用IEEE-754表示相同数字时的情况。
以下数字以科学计数法表示,及其对应的完整形式,但这次完整数字是基于IEEE-754计算得出的:
表B
| 科学计数法 | 完整形式 |
|---|---|
| 1.00001e+50 | 100000999999999993488106414884841393099338118332416 |
| -3.345233391e+45 | -3345233391000000093465768949128568879123005440 |
| 1.7976931348623157E+308 | 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368 |
我希望GetUnscaledMantissa返回的是数学上正确的无缩放数字(表A中的值),而非IEEE-754“正确”的无缩放数字(表B中的值)。
内容的提问来源于stack exchange,提问作者Matthew Layton

