繁体   English   中英

打印浮点数的整数部分

[英]printing the integral part of a floating point number

我想弄清楚如何在不使用库函数的情况下打印浮点数。 结果证明打印浮点数的小数部分非常容易。 打印组成部分更难:

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 设施? 还是我缺少一些浮点技巧?

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

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

/编辑:首先阅读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

如有疑问,请向其扔更多的 printf ;)

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

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

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

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

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

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

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

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

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

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

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

可能。

让我们再解释一次。 在整数部分被打印(完全)之后,除了向 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 ...
...

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

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

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

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

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

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

另一种方式在这里描述

这个程序会为你工作。

#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);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM