4

我试图弄清楚如何在不使用库函数的情况下打印浮点数。打印浮点数的小数部分非常容易。打印整体部分更难:

static const int base = 2;
static const char hex[] = "0123456789abcdef";

void print_integral_part(float value)
{
    assert(value >= 0);
    char a[129]; // worst case is 128 digits for base 2 plus NUL
    char * p = a + 128;
    *p = 0;
    do
    {
        int digit = fmod(value, base);
        value /= base;
        assert(p > a);
        *--p = hex[digit];
    } while (value >= 1);
    printf("%s", p);
}

FLT_MAX使用 base 2 和 base 16 完美打印作品的组成部分:

11111111111111111111111100000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000 (base 2)

ffffff00000000000000000000000000 (base 16)

但是,以 10 为基数打印会导致前 7 位数字后出现错误:

340282368002860660002286082464244022240 (my own function)
340282346638528859811704183484516925440 (printf)

我认为这是除以 10 的结果。如果我使用 double 而不是 float,效果会更好:

340282346638528986604286022844204804240 (my own function)
340282346638528859811704183484516925440 (printf)

(如果您不相信printf,请进入2^128-2^104Wolfram Alpha。它是正确的。)

现在,如何printf设法打印正确的结果?它是否在内部使用了一些 bigint 设施?还是我缺少一些浮点技巧?

4

6 回答 6

2

我相信问题出在value /= base;. 不要忘记 10 在二进制系统中不是有限分数,因此这种计算永远不会正确。fmod我还假设由于相同的原因会发生一些错误。

printf将首先计算积分部分,然后将其转换为十进制(如果我printf正确理解了积分部分)。

于 2012-06-06T10:34:35.690 回答
2

/编辑:首先阅读Unni的答案。此结果来自http://codepad.org/TLqQzLO3

void print_integral_part(float value)
{
    printf("input : %f\n", value);
    char a[129]; // worst case is 128 digits for base 2 plus NUL
    char * p = a + 128;
    *p = 0;
    do
    {
        int digit = fmod(value, base);
        value /= base;
        printf("interm: %f\n", value);
        *--p = hex[digit];
    } while (value >= 1);
    printf("result: %s\n", p);
}

print_integral_part(3.40282347e+38F);

看看你的价值是如何被操作弄乱的value /= base

input : 340282346638528859811704183484516925440.000000
interm: 34028234663852885981170418348451692544.000000
interm: 3402823466385288480057879763104038912.000000
interm: 340282359315034876851393457419190272.000000
interm: 34028234346940236846450271659753472.000000
interm: 3402823335658820218996583884128256.000000
interm: 340282327376181848531187106054144.000000
interm: 34028232737618183051678859657216.000000
interm: 3402823225404785588136713388032.000000
interm: 340282334629736780292710989824.000000
interm: 34028231951816403862828351488.000000
interm: 3402823242405304929106264064.000000
interm: 340282336046446683592065024.000000
interm: 34028232866774907300610048.000000
interm: 3402823378911210969759744.000000
interm: 340282332126513595416576.000000
interm: 34028233212651357863936.000000
interm: 3402823276229139890176.000000
interm: 340282333252413489152.000000
interm: 34028234732616232960.000000
interm: 3402823561222553600.000000
interm: 340282356122255360.000000
interm: 34028235612225536.000000
interm: 3402823561222553.500000
interm: 340282366859673.625000
interm: 34028237357056.000000
interm: 3402823735705.600098
interm: 340282363084.799988
interm: 34028237619.200001
interm: 3402823680.000000
interm: 340282368.000000
interm: 34028236.800000
interm: 3402823.600000
interm: 340282.350000
interm: 34028.234375
interm: 3402.823438
interm: 340.282349
interm: 34.028235
interm: 3.402824
interm: 0.340282
result: 340282368002860660002286082464244022240

如有疑问,请向其添加更多 printfs ;)

于 2012-06-06T10:45:49.787 回答
2

根据 IEEE 单精度浮点实现,任何时候都只有 24 位数据存储在浮点变量中。这意味着浮点数中最多只能存储 7 个十进制数字。

其余的巨大数字存储在指数中。FLT_MAX 初始化为 3.402823466e+38F。因此,在第 10 位精度之后,应该打印哪个数字在任何地方都没有定义。

从 Visual C++ 2010 编译器,我得到这个输出 340282346638528860000000000000000000000.000000,这是唯一有效的输出。

所以,最初我们有这么多有效数字 3402823466 所以在第一次除法之后我们只有 0402823466 所以,系统需要去掉左边的 0 并在右边引入一个新数字。在理想的整数除法中,它是 0。因为您正在进行浮点除法 (value /= base;) ,系统正在获取其他数字来填充该位置。

因此,在我看来,printf 可以将上述可用的有效数字分配给一个整数并使用它。

于 2012-06-06T11:17:47.627 回答
1

看起来浮点到字符串转换的工作马是dtoa()函数。请参阅newlib 中的dtoa.c了解他们是如何做到的。

现在, printf 如何打印正确的结果?

我认为它接近于魔法。至少来源看起来像是某种黑暗的咒语。

它是否在内部使用了一些 bigint 设施?

是的,_Bigint在链接的源文件中搜索。

还是我缺少一些浮点技巧?

可能。

于 2012-06-06T10:50:33.347 回答
1

让我们再解释一次。在整数部分被打印(准确)之后,除了向 0 切入之外没有任何舍入,现在是小数位的时候了。

从一串包含二进制零的字节(比如 100 开始)开始。如果 fp 值中小数点右边的第一位被设置,则意味着 0.5 (2^-1 或 1/(2^1) 是分数的一个组成部分。所以在第一个字节上加 5。如果下一位设置为 0.25 (2^-2 或 1/(2^2)) 是分数的一部分,第二个字节加 5,第一个字节加 2(哦,别忘了进位,它们会发生 -初中数学)。下一个位集表示 0.125,因此第三个字节加 5,第二个字节加 2,第一个字节加 1。依此类推:

      value          string of binary 0s
start 0              0000000000000000000 ...
bit 1 0.5            5000000000000000000 ...
bit 2 0.25           7500000000000000000 ...
bit 3 0.125          8750000000000000000 ...
bit 4 0.0625         9375000000000000000 ...
bit 5 0.03125        9687500000000000000 ...
bit 6 0.015625       9843750000000000000 ...
bit 7 0.0078125      9921875000000000000 ...
bit 8 0.00390625     9960937500000000000 ...
bit 9 0.001953125    9980468750000000000 ...
...

我是手工完成的,所以我可能遗漏了一些东西,但是在代码中实现它是微不足道的。

因此,对于所有那些“无法使用浮点数获得精确结果”的人来说,不知道他们在这里谈论什么的人证明了浮点分数是完全精确的。极其精确。但是二进制。

对于那些花时间了解其工作原理的人来说,更好的精度是触手可及的。至于其他人......好吧,我想他们会继续不浏览论坛来回答以前已经回答过无数次的问题,老实说,他们已经发现了“破碎的浮点”(或任何他们所说的)并每天发布同一问题的新变体。

“接近魔法”、“黑暗咒语”——太搞笑了!

于 2012-06-07T18:48:44.293 回答
0

该程序将为您工作。

#include<stdio.h>
int main()
{
    float num;
    int z;
    scanf("%f",&num);
    z=(int)num;
    printf("the integral part of the floating point number is %d",z);
}
于 2018-08-12T05:25:11.000 回答