打印浮点数的整数部分

时间:2012-06-06 10:07:44

标签: c string floating-point floating-accuracy

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

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的整体部分与基座2和基座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,请在Wolfram Alpha中输入2^128-2^104。这是正确的。)

现在,printf如何设法打印正确的结果?它在内部使用一些bigint设施吗?或者是否有一些我失踪的浮点技巧?

7 个答案:

答案 0 :(得分:3)

我认为问题在于价值/ =基数;不要忘记10在二元系统中不是有限分数,因此这种计算永远不正确。我还假设由于同样的原因在fmod中会发生一些错误。

printf将首先计算积分部分,然后将其转换为十进制(如果我得到正确打印整数部分的方式)。

答案 1 :(得分:3)

/编辑:首先阅读Unni's answer。此结果来自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;)

答案 2 :(得分:2)

根据IEEE单精度浮点实现,浮点变量中任何时候只存储24位数据。这意味着浮动数中只存储最多7位小数。

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

从Visual C ++ 2010编译器中,我得到了这个输出 340282346638528860000000000000000000000.000000,这是唯一的虚荣输出。

所以,最初我们有这么多有效数字3402823466 所以在第一次分裂之后我们只有0402823466 因此,系统需要摆脱左边0并在右边引入一个新数字。 在理想的整数除法中,它为0。 因为你正在进行浮动除法(值/ =基数;),系统会得到一些其他数字来填充该位置。

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

答案 3 :(得分:1)

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

  

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

我认为它接近魔术。至少这个来源看起来像某种黑暗的咒语。

  

它是否在内部使用了一些bigint工具?

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

  

或者是否有一些我失踪的浮点技巧?

可能的。

答案 4 :(得分: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 ...
...

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

因此对于所有那些“无法使用浮点数获得精确结果”的人不知道他们在这里谈论的是浮点分数值非常精确的证据。令人难以置信的确切。但是二进制。

对于那些花时间了解其工作原理的人来说,更好的精确度是可以实现的。至于其他人......好吧我猜他们会继续不去浏览这个问题的答案,这个问题已经多次被回答,老实说相信他们已经发现了“破碎的浮动点”(或者无论如何称之为)并且每天都会发布同一个问题的新变体。

“接近魔法”,“黑暗咒语” - 这很有趣!

答案 5 :(得分:0)

与Agent_L的答案一样,你会遇到因将值除以10而导致的错误结果。浮点数与任何二进制浮点类型一样,无法正确表达十进制中最有理数的数字。除法之后,大多数情况下结果都不能拟合成二进制,所以它将被舍入。因此,你划分的越多,你就会意识到的错误越多。

如果数字不是很大,快速解决方案是将其乘以10或10的幂,具体取决于您需要的小数点后的位数。

另一种方式是here

答案 6 :(得分: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);
}