【问题标题】:printing the integral part of a floating point number打印浮点数的整数部分
【发布时间】:2012-06-10 08:17:41
【问题描述】:

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

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 的结果。如果我使用双精度而不是浮点数会更好:

340282346638528986604286022844204804240 (my own function)
340282346638528859811704183484516925440 (printf)

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

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

【问题讨论】:

  • 你为什么不看看一些printf() 的实现?
  • @wilx 我不想作弊 ;)
  • 很难准确有效地打印浮点值,除非基数是 2 的幂(对于二进制浮点,基数是 2 的幂和十进制浮点数的 5 的乘积)点表示)。在基数为 10 的情况下,如果使用大整数 (m*2^(-p) == (m*5^p)*10^(-p)),它会变得相当容易。在不使用大整数的情况下,您可以首先获取二进制表示并将其转换为十进制,整数部分使用double dabble,小数部分使用类似的东西。但这不是很有效。
  • 你可能会发现我关于这个主题的文章很有趣——从这里开始:exploringbinary.com/…

标签: c string floating-point floating-accuracy


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 同样,FLT_MAX = 2^128-2^104 = 340282346638528859811704183484516925440 而不是任何其他数字。
  • 不管你写的数字是什么,它根本不适合 23 位尾数,不幸的是......所以可以使用前 7 位数字之后的任何数字。其余的一切都被系统丢弃。
  • 我们来看一个更简单的值,比如 2^100。它只需要 1 位精度,准确值为 1267650600228229401496703205376。所有这些十进制数字都非常适合浮点数。 Here is the proof。如果 Visual C++ 产生不同的输出,它就会被破坏。
  • 添加另一条评论,因为上一条评论无法编辑。 “不幸的是,您在写入/计算时有多少位数字,它们根本不适合 23 位尾数……所以无论只能使用前 7 位数字。其余的一切都被系统丢弃。”
  • Visual C++ 给出 1267650600228229400000000000000.000000。不知道ideaone的解释。但是,我很想知道 23 位是如何存储这么多信息的。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 我认为这是部分正确的。如果原始数字本身可以被 10 整除,则除以 10 将给出正确答案。但是,这种情况不太可能发生。此外,只有 254 位精度,大约是 7 个十进制数字,所以在 7 除以 10 之后,所有的赌注都没有了。
    • @JeremyP 我认为如果你写 "double p = 10.0; 然后计算 p /= 10.0; p 将接近 1.0 但不精确(除非编译器进行了一些优化)。跨度>
    • @JeremyP 254 位精度一定是错字,对吧? float 有 24 位精度。
    • @FredOverflow 哈哈,是的,254 位的精度不能真正描述为“仅”。不幸的是,我现在无法修复它。
    • @RudyVelthuis 是的 10.0 是完全可表示的,但 3/10 不是。除非 FLT_MAX 可被 5 整除,否则 FLT_MAX/10 的除法结果将无法精确表示。
    【解决方案4】:

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

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

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

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

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

    或者我缺少一些浮点技巧?

    可能。

    【讨论】:

      【解决方案5】:

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

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

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

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

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

      【讨论】:

        【解决方案6】:

        这个程序适合你。

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

        【讨论】:

        • 不,这不适用于大型浮动。试试FLT_MAX 看看它不是OP 所期望的340282346638528859811704183484516925440
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多