提问人:Matthew Layton 提问时间:5/18/2023 更新时间:5/23/2023 访问量:122
从 C 中的双精度中提取“真实”科学指数#
Extract the "real" scientific exponent from a double in C#
问:
在 C# 中,IEEE-754 数字(在本例中)可以以 10 为基数的科学记数法声明;例如:1.234e+9 (1234000000),但由于浮点数存储方式的限制,这些值的基数 2 浮点表示通常只是一个近似值。请注意下表:System.Double
科学记数法 | IEEE-754 近似 |
---|---|
1E-1型 | 0.10000000000000001 |
1E-2型 | 0.01 |
1E-3型 | 0.001 |
1E-4型 | 0.0001 |
1E-5型 | 1.00000000000000001E-05 |
1E-6型 | 9.99999999999999995E-07 |
1E-7型 | 9.999999999999999995E-08 |
1E-8型 | 1E-8型 |
1E-9型 | 1.0000000000000001E-09 |
1E-10型 | 1E-10型 |
忽略 IEEE-754 近似值的格式都不同这一事实。这些值都是直接从调试器中获取的。以不同的形式查看它们很有用,因为它有助于解释问题。
我想做的是从数字中提取指数。到目前为止,我有这个功能:
private static int GetScientificExponent(double value)
{
return (int)double.Floor(double.Log10(double.Abs(value)));
}
结合上面的结果表,我们可以确定每种情况的科学指数:
科学记数法 | IEEE-754 近似 | 科学指数 |
---|---|---|
1E-1型 | 0.10000000000000001 | -1 |
1E-2型 | 0.01 | -2 |
1E-3型 | 0.001 | -3 |
1E-4型 | 0.0001 | -4 |
1E-5型 | 1.00000000000000001E-05 | -5 |
1E-6型 | 9.99999999999999995E-07 | -6 |
1E-7型 | 9.999999999999999995E-08 | -7 |
1E-8型 | 1E-8型 | -8 |
1E-9型 | 1.0000000000000001E-09 | -9 |
1E-10型 | 1E-10型 | -10 |
科学指数与每种情况的科学记数法都匹配,但它们并不与每种情况的 IEEE-754 近似值匹配,正如我在此表中所示。请注意异常值,以粗体突出显示:
科学记数法 | IEEE-754 表示 | 科学指数 | 笔记 |
---|---|---|---|
1E-1型 | 0.10000000000000001 | -1 | 还行 |
1E-2型 | 0.01 | -2 | 还行 |
1E-3型 | 0.001 | -3 | 还行 |
1E-4型 | 0.0001 | -4 | 还行 |
1E-5型 | 1.00000000000000001E-05 | -5 | 还行 |
1E-6型 | 9.99999999999999995E-07 | -6 | E-6 与 E-7 |
1E-7型 | 9.999999999999999995E-08 | -7 | E-7 与 E-8 |
1E-8型 | 1E-8型 | -8 | 还行 |
1E-9型 | 1.0000000000000001E-09 | -9 | 还行 |
1E-10型 | 1E-10型 | -10 | 还行 |
考虑到如果您以实际形式表示这些数字,而不是依赖 .NET的底层舍入机制(Dragon4左右,我被引导相信),那么你最终会得到这个:
科学记数法 | 以 10 为基数表示 | IEEE-754 近似 |
---|---|---|
1E-6型 | 0.000001 | 0.000000999999999999999954748111825886258685613938723690807819366455078125 |
值得注意的是,在以 10 为基数的 the 中,它位于小数点后第 6 位,而在 IEEE-754 近似中,它位于第 7 位,因此 -6 与 -7...这是有道理的。1
9
问题
给定我的函数,是否可以从近似值中获得指数?例如; 应该像现在一样返回,但应该返回,因为这是近似值的指数?GetScientificExponent
1e-2
-2
1e-6
-7
为了完整起见,下面是我用来测试的代码:
using static System.Double;
internal static class Program
{
private static void Main(string[] args)
{
double[] values = { 3e-1, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, 1e-10 };
foreach (double value in values)
{
Console.WriteLine($"|{value:F10}|{GetScientificExponent(value)}|");
}
}
private static int GetScientificExponent(double value)
{
return (int)Floor(Log10(Abs(value)));
}
}
答:
一种方法是让你的库的文本例程来执行所需的更高精度的数学运算。使用用户数学是不够的/不方便的。double
double
“诀窍”是以足够高的精度打印,以便接近 10 的幂的值不会导致指数在四舍五入时发生变化,然后简单地解析指数。
下面是 C 代码来说明。
特别是,在 的情况下,打印到 17 () 位有效数字是不够的。相反,代码打印重要的十进制数字。在某种程度上,使用更多数字几乎不会有任何损失。我推荐。除此之外,图书馆无需打印正确的数字并保持 IEEE-754 合规性。DBL_DECIMAL_DIG
1e-79
DBL_DECIMAL_DIG + 1
DBL_DECIMAL_DIG + 3
示例代码
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int GetScientificExponent(double value) {
return (int) floor(log10(fabs(value)));
}
// 0.0, infinity and NAN return INT_MIN. Adjust as desired.
int GetScientificExponent_alt1(double value) {
#define PREC ((DBL_DECIMAL_DIG - 1) + 1 /* or + 3 */)
if (isfinite(value)) {
// - d . ddddd e - eee \0
char buf[1 + 1 + 1 + PREC + 1 + 1 + 5 + 1];
sprintf(buf, "%.*e", PREC, value);
printf("<%-30s>", buf);
// Look for the exponent start
const char *e = strchr(buf, 'e');
if (e) {
return atoi(e + 1);
}
}
return INT_MIN;
}
#include <stdio.h>
#include <stdlib.h>
void test1(double x) {
printf("%30.*e %4d %4d\n", //
DBL_DECIMAL_DIG * 3 / 2, x,
GetScientificExponent_alt1(x), GetScientificExponent(x));
}
void test3(int power10) {
double x = pow(10.0, power10);
//test1(nextafter(x, +INFINITY));
test1(x);
//test1(nextafter(x, -INFINITY));
//puts("");
}
int main() {
for (int power10 = 0; power10 <= DBL_DECIMAL_DIG - DBL_MIN_10_EXP; power10++) {
test3(-power10);
}
}
输出
<1.00000000000000000e+00 >1.0000000000000000000000000e+00 0 0
<1.00000000000000006e-01 >1.0000000000000000555111512e-01 -1 -1
<1.00000000000000002e-02 >1.0000000000000000208166817e-02 -2 -2
<1.00000000000000002e-03 >1.0000000000000000208166817e-03 -3 -3
<1.00000000000000005e-04 >1.0000000000000000479217360e-04 -4 -4
<1.00000000000000008e-05 >1.0000000000000000818030539e-05 -5 -5
<9.99999999999999955e-07 >9.9999999999999995474811183e-07 -7 -6
<9.99999999999999955e-08 >9.9999999999999995474811183e-08 -8 -7
<1.00000000000000002e-08 >1.0000000000000000209225608e-08 -8 -8
<1.00000000000000006e-09 >1.0000000000000000622815915e-09 -9 -9
<1.00000000000000004e-10 >1.0000000000000000364321973e-10 -10 -10
<9.99999999999999939e-12 >9.9999999999999993949696928e-12 -12 -11
<9.99999999999999980e-13 >9.9999999999999997988664763e-13 -13 -12
<1.00000000000000003e-13 >1.0000000000000000303737456e-13 -13 -13
<9.99999999999999999e-15 >9.9999999999999999881930935e-15 -15 -14
<1.00000000000000008e-15 >1.0000000000000000777053999e-15 -15 -15
<9.99999999999999979e-17 >9.9999999999999997909778672e-17 -17 -16
<1.00000000000000007e-17 >1.0000000000000000715424241e-17 -17 -17
<1.00000000000000007e-18 >1.0000000000000000715424241e-18 -18 -18
<9.99999999999999975e-20 >9.9999999999999997524592684e-20 -20 -19
<9.99999999999999945e-21 >9.9999999999999994515327145e-21 -21 -20
<9.99999999999999908e-22 >9.9999999999999990753745223e-22 -22 -21
<1.00000000000000005e-22 >1.0000000000000000485967743e-22 -22 -22
<9.99999999999999960e-24 >9.9999999999999996043469801e-24 -24 -23
<9.99999999999999924e-25 >9.9999999999999992370049955e-25 -25 -24
<1.00000000000000004e-25 >1.0000000000000000384948697e-25 -25 -25
<1.00000000000000004e-26 >1.0000000000000000384948697e-26 -26 -26
<1.00000000000000004e-27 >1.0000000000000000384948697e-27 -27 -27
<9.99999999999999971e-29 >9.9999999999999997123254346e-29 -29 -28
<9.99999999999999943e-30 >9.9999999999999994320657418e-30 -30 -29
<1.00000000000000008e-30 >1.0000000000000000833364206e-30 -30 -30
...
<9.99999999999999958e-76 >9.9999999999999995765001370e-76 -76 -75
<9.99999999999999927e-77 >9.9999999999999992696817954e-77 -77 -76
<9.99999999999999927e-78 >9.9999999999999992696817954e-78 -78 -77
<9.99999999999999999e-79 >9.9999999999999999887872835e-79 -79 -78
<9.99999999999999999e-80 >9.9999999999999999887872835e-80 -80 -79
<9.99999999999999961e-81 >9.9999999999999996142531751e-81 -81 -80
<9.99999999999999961e-82 >9.9999999999999996142531751e-82 -82 -81
<9.99999999999999961e-83 >9.9999999999999996142531751e-83 -83 -82
...
<9.99999983659714433e-317 >9.9999998365971443346061921e-317 -317 -317
<1.00000023069253735e-317 >1.0000002306925373540838913e-317 -317 -317
<9.99998748495599830e-319 >9.9999874849559983034425877e-319 -319 -319
<9.99988867182683005e-320 >9.9998886718268300541337524e-320 -320 -320
<9.99988867182683005e-321 >9.9998886718268300541337524e-321 -321 -321
<9.98012604599318019e-322 >9.9801260459931801923666896e-322 -322 -322
<9.88131291682493088e-323 >9.8813129168249308835313759e-323 -323 -323
<9.88131291682493088e-324 >9.8813129168249308835313759e-324 -324 -324
<0.00000000000000000e+00 >0.0000000000000000000000000e+00 0 -2147483648
备选方案2
使用高精度数学。当精度超过至少 10 位时,OP 的原始方法也运行良好。long double
double
// Needs work for 0.0, infinity, NAN
private static int GetScientificExponent_alt2(double value) {
long double dv = value;
return (int)floor(log10(abs(dv)));
}
当然,如果不是特别精确的话,这和OP的原版有同样的麻烦。long double
评论
double.ToString()
.ToString("G17")