简体   繁体   English

打印浮点数的整数部分

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

I am trying to figure out how to print floating point numbers without using library functions.我想弄清楚如何在不使用库函数的情况下打印浮点数。 Printing the decimal part of a floating point number turned out to be quite easy.结果证明打印浮点数的小数部分非常容易。 Printing the integral part is harder:打印组成部分更难:

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

Printing the integral part of FLT_MAX works flawlessly with base 2 and base 16:打印FLT_MAX的组成部分可以完美地使用基数 2 和基数 16:

11111111111111111111111100000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000 (base 2)

ffffff00000000000000000000000000 (base 16)

However, printing in base 10 results in errors after the first 7 digits:但是,以基数 10 打印会导致前 7 位数字后出现错误:

340282368002860660002286082464244022240 (my own function)
340282346638528859811704183484516925440 (printf)

I assume this is a result of the division by 10. It gets better if I use double instead of float:我认为这是除以 10 的结果。如果我使用 double 而不是 float 会更好:

340282346638528986604286022844204804240 (my own function)
340282346638528859811704183484516925440 (printf)

(If you don't believe printf , enter 2^128-2^104 into Wolfram Alpha. It is correct.) (如果您不相信printf ,请在 Wolfram Alpha 中输入2^128-2^104 。它是正确的。)

Now, how does printf manage to print the correct result?现在, printf如何设法打印正确的结果? Does it use some bigint facilities internally?它是否在内部使用了一些 bigint 设施? Or is there some floating point trick I am missing?还是我缺少一些浮点技巧?

I believe the problem lies in value /= base;我认为问题在于value /= base; . . Do not forget that 10 is not a finite fraction in binary system and thus this calculation is never correct.不要忘记 10 不是二进制系统中的有限分数,因此这个计算永远不会正确。 I also assume some error will occur in fmod due to the same reason.由于同样的原因,我还假设fmod会发生一些错误。

printf will first compute the integral part and then convert it to decimal (if I get the way you printf the integral part correctly). printf将首先计算整数部分,然后将其转换为十进制(如果我得到了正确printf整数部分的方法)。

/Edit: Read Unni's answer first. /编辑:首先阅读Unni的回答 This results come from http://codepad.org/TLqQzLO3 .这个结果来自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);

to see how messed up your value gets by the value /= base operation:查看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

When in doubt, throw more printfs at it ;)如有疑问,请向其扔更多的 printf ;)

According to IEEE single precision float implementation, only 24 bits of data is stored at any time in a float variable.根据 IEEE 单精度浮点实现,任何时候都只能在浮点变量中存储 24 位数据。 This means only maximum 7 decimal digits are stored in the floating number.这意味着浮点数中最多只能存储 7 位十进制数字。

Rest of the hugeness of the number is stored in the exponent.数字的其余部分存储在指数中。 FLT_MAX is initialized as 3.402823466e+38F. FLT_MAX 被初始化为 3.402823466e+38F。 So, after the 10th precision, which digit should get printed is not defined anywhere.因此,在第 10 个精度之后,没有在任何地方定义应该打印哪个数字。

From Visual C++ 2010 compiler, I get this output 340282346638528860000000000000000000000.000000, which is the only vaild output.从 Visual C++ 2010 编译器中,我得到这个输出 340282346638528860000000000000000000000.000000,这是唯一有效的输出。

So, initially we have these many valid digits 3402823466 So after the 1st division we have only 0402823466 So, the system need to get rid of the left 0 and introduce a new digit at the right.所以,最初我们有这么多有效数字 3402823466 所以在第一次除法之后我们只有 0402823466 所以,系统需要去掉左边的 0 并在右边引入一个新数字。 In ideal integer division, it is 0. Because you are doing floating division (value /= base;) , system is getting some other digit to fill in that location.在理想的整数除法中,它是 0。因为您正在执行浮动除法 (value /= base;) ,系统正在获取一些其他数字来填充该位置。

So, in my opinion, the printf could be assigning the above available significant digits to an integer and working with this.因此,在我看来, printf 可以将上述可用有效数字分配给一个整数并使用它。

It appears that the work horse for the float to string conversion is the dtoa() function.似乎浮点到字符串转换的工作马是dtoa()函数。 See dtoa.c in newlib for how they do it.请参阅 newlib 中的dtoa.c了解他们是如何做到的。

Now, how does printf manage to print the correct result?现在, printf 如何设法打印正确的结果?

I think it is close to magic.我认为它接近魔术。 At least the source looks like some kind of dark incantation.至少源头看起来像是某种黑暗的咒语。

Does it use some bigint facilities internally?它是否在内部使用了一些 bigint 设施?

Yes, search for _Bigint in the linked source file.是的,在链接的源文件中搜索_Bigint

Or is there some floating point trick I am missing?还是我缺少一些浮点技巧?

Likely.可能。

Let's explain this one more time.让我们再解释一次。 After the integer part has been printed (exactly) without any rounding other than chop towards 0 it's time for the decimal bits.在整数部分被打印(完全)之后,除了向 0 切碎之外没有任何舍入,是十进制位的时候了。

Start with a string of bytes (say 100 for starters) containing binary zeros.从包含二进制零的一串字节(对于初学者来说是 100)开始。 If the first bit to the right of the decimal point in the fp value is set that means that 0.5 (2^-1 or 1/(2^1)is a component of the fraction. So add 5 to the first byte. If the next bit is set 0.25 (2^-2 or 1/(2^2)) is part of the fraction add 5 to the second byte and add 2 to the first (oh, don't forget the carry, they happen - lower school math). The next bit set means 0.125 so add 5 to the third byte, 2 to the second and 1 to the first. And so on:如果设置了 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 ...
...

I did this by hand so I may have missed something but to implement this in code is trivial.我是手工完成的,所以我可能错过了一些东西,但在代码中实现它是微不足道的。

So for all those SO "can't get an exact result using float" people who don't know what they're talking about here is proof that floating point fraction values are perfectly exact.因此,对于所有那些“无法使用浮点数获得确切结果”的人来说,不知道他们在说什么的人证明了浮点分数值是完全准确的。 Excruciatingly exact.极其准确。 But binary.但是二进制。

For those who take the time to get their heads around how this works, better precision is well within reach.对于那些花时间了解其工作原理的人来说,更高的精度是触手可及的。 As for the others ... well I guess they'll keep on not browsing the fora for the answer to a question which has been answered numerous times previously, honestly believe they have discovered "broken floating point" (or whatever thay call it) and post a new variant of the same question every day.至于其他人......好吧,我想他们会继续不浏览论坛来回答一个以前已经回答过多次的问题,老实说,他们相信他们已经发现了“破碎的浮点”(或者随便你怎么称呼它)并每天发布同一问题的新变体。

"Close to magic," "dark incantation" - that's hilarious! “接近魔法”,“黑暗咒语”——太搞笑了!

Like Agent_L's answer, you're suffering from the false result caused by dividing the value by 10. Float, like any binary floating point type, cannot express correctly most rational number in decimal. 就像Agent_L的答案一样,你会遇到因将值除以10而导致的错误结果.Float与任何二进制浮点类型一样,无法正确表达十进制中的最有理数。 After division, most of the case the result cannot be fitted into binary, so it'll be rounded. 除法之后,大多数情况下结果都不能拟合成二进制,所以它将被舍入。 Hence the more you divide, the more error you'll realize. 因此,你划分越多,你就会意识到越多的错误。

If the number is not very large, a quick solution would be multiplying it with 10 or a power of 10 depending on how many digits after decimal point you need. 如果数字不是很大,快速解决方案是将其乘以10或10的幂,具体取决于您需要的小数点后的位数。

Another way was described here 另一种方式在这里描述

This program will work for you.这个程序会为你工作。

#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