簡體   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