おそらくこれ以上簡単な方法はありません。かなり込み入った問題です。
あなたのコードはいくつかの理由でそれを正しく解決していません:
- 浮動小数点演算の最も実用的な実装は 10 進数ではなく、2 進数です。そのため、浮動小数点数を 10 倍または 10 で割ると、精度が失われる可能性があります (これは数値によって異なります)。
- 標準の
64-bit IEEE-754
浮動小数点形式では仮数のビットが予約されていますが、これは= 10 進数に53
相当しますが、この形式の有効な数値を正確に印刷すると、小数部に最大で 10 進数が必要になる場合があります。について尋ねます。floor(log10(2 ^ 53))
15
1080
これを解決する 1 つの方法は、 で%a
フォーマット型指定子を使用することですsnprintf()
。これは、仮数に 16 進数を使用して浮動小数点値を出力します。1999 年からの C 標準では、浮動小数点形式がradix-2 (AKA base-2 または単にバイナリ) です。したがって、これを使用すると、数値の仮数部のすべての 2 進数を取得できます。そしてここから、小数部分が何桁になるかを知ることができます。
ここで、次のことを観察します。
1.00000 = 2 +0 = 1.00000 (バイナリ)
0.50000 = 2 -1 = 0.10000
0.25000 = 2 -2 = 0.01000
0.12500 = 2 -3 = 0.00100 0.06250
= 2 -4 = 0.00010 0.03125
= 0.0 -105
等々。
ここで、2 進数表現の点の右側の - 番目の位置にある 2進数が、10 進数表現の点の右側の - 番目の位置にi
もゼロ以外の最後の 10 進数を生成することが明確にわかります。i
したがって、2 進浮動小数点数のゼロ以外の最下位ビットがどこにあるかがわかれば、数値の小数部分を正確に出力するために必要な 10 進数の桁数を計算できます。
そして、これが私のプログラムが行っていることです。
コード:
// file: PrintFullFraction.c
//
// compile with gcc 4.6.2 or better:
// gcc -Wall -Wextra -std=c99 -O2 PrintFullFraction.c -o PrintFullFraction.exe
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <assert.h>
#if FLT_RADIX != 2
#error currently supported only FLT_RADIX = 2
#endif
int FractionalDigits(double d)
{
char buf[
1 + // sign, '-' or '+'
(sizeof(d) * CHAR_BIT + 3) / 4 + // mantissa hex digits max
1 + // decimal point, '.'
1 + // mantissa-exponent separator, 'p'
1 + // mantissa sign, '-' or '+'
(sizeof(d) * CHAR_BIT + 2) / 3 + // exponent decimal digits max
1 // string terminator, '\0'
];
int n;
char *pp, *p;
int e, lsbFound, lsbPos;
// convert d into "+/- 0x h.hhhh p +/- ddd" representation and check for errors
if ((n = snprintf(buf, sizeof(buf), "%+a", d)) < 0 ||
(unsigned)n >= sizeof(buf))
return -1;
//printf("{%s}", buf);
// make sure the conversion didn't produce something like "nan" or "inf"
// instead of "+/- 0x h.hhhh p +/- ddd"
if (strstr(buf, "0x") != buf + 1 ||
(pp = strchr(buf, 'p')) == NULL)
return 0;
// extract the base-2 exponent manually, checking for overflows
e = 0;
p = pp + 1 + (pp[1] == '-' || pp[1] == '+'); // skip the exponent sign at first
for (; *p != '\0'; p++)
{
if (e > INT_MAX / 10)
return -2;
e *= 10;
if (e > INT_MAX - (*p - '0'))
return -2;
e += *p - '0';
}
if (pp[1] == '-') // apply the sign to the exponent
e = -e;
//printf("[%s|%d]", buf, e);
// find the position of the least significant non-zero bit
lsbFound = lsbPos = 0;
for (p = pp - 1; *p != 'x'; p--)
{
if (*p == '.')
continue;
if (!lsbFound)
{
int hdigit = (*p >= 'a') ? (*p - 'a' + 10) : (*p - '0'); // assuming ASCII chars
if (hdigit)
{
static const int lsbPosInNibble[16] = { 0,4,3,4, 2,4,3,4, 1,4,3,4, 2,4,3,4 };
lsbFound = 1;
lsbPos = -lsbPosInNibble[hdigit];
}
}
else
{
lsbPos -= 4;
}
}
lsbPos += 4;
if (!lsbFound)
return 0; // d is 0 (integer)
// adjust the least significant non-zero bit position
// by the base-2 exponent (just add them), checking
// for overflows
if (lsbPos >= 0 && e >= 0)
return 0; // lsbPos + e >= 0, d is integer
if (lsbPos < 0 && e < 0)
if (lsbPos < INT_MIN - e)
return -2; // d isn't integer and needs too many fractional digits
if ((lsbPos += e) >= 0)
return 0; // d is integer
if (lsbPos == INT_MIN && -INT_MAX != INT_MIN)
return -2; // d isn't integer and needs too many fractional digits
return -lsbPos;
}
const double testData[] =
{
0,
1, // 2 ^ 0
0.5, // 2 ^ -1
0.25, // 2 ^ -2
0.125,
0.0625, // ...
0.03125,
0.015625,
0.0078125, // 2 ^ -7
1.0/256, // 2 ^ -8
1.0/256/256, // 2 ^ -16
1.0/256/256/256, // 2 ^ -24
1.0/256/256/256/256, // 2 ^ -32
1.0/256/256/256/256/256/256/256/256, // 2 ^ -64
3.14159265358979323846264338327950288419716939937510582097494459,
0.1,
INFINITY,
#ifdef NAN
NAN,
#endif
DBL_MIN
};
int main(void)
{
unsigned i;
for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++)
{
int digits = FractionalDigits(testData[i]);
assert(digits >= 0);
printf("%f %e %.*f\n", testData[i], testData[i], digits, testData[i]);
}
return 0;
}
出力 ( ideone ):
0.000000 0.000000e+00 0
1.000000 1.000000e+00 1
0.500000 5.000000e-01 0.5
0.250000 2.500000e-01 0.25
0.125000 1.250000e-01 0.125
0.062500 6.250000e-02 0.0625
0.031250 3.125000e-02 0.03125
0.015625 1.562500e-02 0.015625
0.007812 7.812500e-03 0.0078125
0.003906 3.906250e-03 0.00390625
0.000015 1.525879e-05 0.0000152587890625
0.000000 5.960464e-08 0.000000059604644775390625
0.000000 2.328306e-10 0.00000000023283064365386962890625
0.000000 5.421011e-20 0.0000000000000000000542101086242752217003726400434970855712890625
3.141593 3.141593e+00 3.141592653589793115997963468544185161590576171875
0.100000 1.000000e-01 0.1000000000000000055511151231257827021181583404541015625
inf inf inf
nan nan nan
0.000000 2.225074e-308 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201383090232717332404064219215980462331830553327416887204434813918195854283159012511020564067339731035811005152434161553460108856012385377718821130777993532002330479610147442583636071921565046942503734208375250806650616658158948720491179968591639648500635908770118304874799780887753749949451580451605050915399856582470818645113537935804992115981085766051992433352114352390148795699609591288891602992641511063466313393663477586513029371762047325631781485664350872122828637642044846811407613911477062801689853244110024161447421618567166150540154285084716752901903161322778896729707373123334086988983175067838846926092773977972858659654941091369095406136467568702398678315290680984617210924625396728515625
π
およびは 10 進数までのみ0.1
真で15
あり、残りの桁は数値が実際に丸められたものを示しています。これらの数値は 2 進浮動小数点形式では正確に表すことができないためです。
DBL_MIN
また、正規化された正の最小double
値である には1022
、小数部分に数字があり、その中に有効数字があることもわかり715
ます。
このソリューションで考えられる問題:
- コンパイラの
printf()
関数は%a
、精度によって要求されたすべての数字をサポートしていないか、正しく出力しません (これは非常に可能性があります)。
- お使いのコンピュータは非バイナリ浮動小数点形式を使用しています (これは非常にまれです)。