부동 소수점 값의 정밀도를 유지하기 위한 폭 지정자 인쇄
a가 있는가printf
문자열을 다시 스캔할 때 원래 부동소수 값을 획득할 수 있도록 필요한 수의 유의한 숫자로 출력을 자동 포맷할 수 있는 부동소수 지정자에 적용할 수 있는 폭 지정기
예를 들어, a를 인쇄한다고 가정합시다.float
2
소수 자릿수:
float foobar = 0.9375;
printf("%.2f", foobar); // prints out 0.94
출력을 스캔할 때0.94
수 .0.9375
부동 소수점 값 되돌리기(이 예에서는 아마도 안 그럴 것이다).
나는 말할 수 있는 방법을 원한다.printf
부동 소수점 값을 필요한 숫자의 중요한 숫자로 자동 인쇄하여 전달된 원래 값으로 다시 스캔할 수 있도록 함printf
.
매크로가 좀 있어야겠어float.h
에 전달되는 최대 너비를 끌어내다printf
, 그러나 필요한 수의 유의한 숫자로 자동 인쇄하거나 적어도 최대 너비로 인쇄할 수 있는 지정자가 이미 있는가?
나는 @Jens Gustedt 16진법 용액을 추천한다: %a를 사용한다.
OP는 "최대의 정밀도를 가진 인쇄(또는 최소한 가장 중요한 소수점까지 인쇄)"를 원한다.
간단한 예는 다음과 같이 7분의 1을 인쇄하는 것이다.
#include <float.h>
int Digs = DECIMAL_DIG;
double OneSeventh = 1.0/7.0;
printf("%.*e\n", Digs, OneSeventh);
// 1.428571428571428492127e-01
하지만 좀 더 깊이 파고들자...
수학적으로 답은 "0.142857 142857 142857..."이지만, 우리는 유한정밀 부동소수를 사용하고 있다.IEEE 754 이중 정밀한 바이너리를 가정해 보자.그래서 더OneSeventh = 1.0/7.0
아래 값을 산출한다.또한 선행 및 다음의 표현가능성이 표시된다.double
부동 소수점 수
OneSeventh before = 0.1428571428571428 214571170656199683435261249542236328125
OneSeventh = 0.1428571428571428 49212692681248881854116916656494140625
OneSeventh after = 0.1428571428571428 769682682968777953647077083587646484375
a의 정확한 소수점 표시 인쇄double
용도가 제한되어 있다.
C에는 2개의 매크로가 있다.<float.h>
우리를 돕기 위해
첫 번째 세트는 10진법으로 문자열로 인쇄할 유의 자릿수로, 문자열을 다시 스캔하면 원래 부동 소수점이 나온다.C 규격의 최소값과 샘플 C11 컴파일러와 함께 표시된다.
FLT_DECIMAL_DIG 6, 9 (float) (C11)
DBL_DECIMAL_DIG 10, 17 (double) (C11)
LDBL_DECIMAL_DIG 10, 21 (long double) (C11)
DECIMAL_DIG 10, 21 (widest supported floating type) (C99)
두 번째 집합은 문자열을 부동 소수점까지 스캔한 다음 FP 인쇄할 수 있는 유의 자릿수로, 동일한 문자열 표시를 유지한다.C 규격의 최소값과 샘플 C11 컴파일러와 함께 표시된다.나는 C99 이전이 가능하다고 믿는다.
FLT_DIG 6, 6 (float)
DBL_DIG 10, 15 (double)
LDBL_DIG 10, 18 (long double)
첫 번째 매크로 세트는 OP의 목표인 유의한 숫자에 부합하는 것 같다.그러나 그 매크로가 항상 이용 가능한 것은 아니다.
#ifdef DBL_DECIMAL_DIG
#define OP_DBL_Digs (DBL_DECIMAL_DIG)
#else
#ifdef DECIMAL_DIG
#define OP_DBL_Digs (DECIMAL_DIG)
#else
#define OP_DBL_Digs (DBL_DIG + 3)
#endif
#endif
'+3'은 나의 이전 대답의 핵심이었다.왕복 변환 문자열-FP 문자열(세트 #2 매크로 사용 가능 C89)을 알고 있는 경우 FP 문자열-FP(세트 #1 매크로 사용 가능 C89 이후)의 자릿수를 어떻게 결정할 것인가?일반적으로 3을 더하면 결과가 나왔다.
이제 인쇄할 중요 자릿수를 알려주고 이를 통해 인쇄<float.h>
.
N개의 유의한 소수 자릿수를 인쇄하려면 다양한 형식을 사용할 수 있다.
와 함께"%e"
, 정밀도 필드는 납 자릿수와 소수점 뒤의 자릿수 입니다.그렇게- 1
정리가 되어 있다.참고: 이 항목-1
시작되지 않음int Digs = DECIMAL_DIG;
printf("%.*e\n", OP_DBL_Digs - 1, OneSeventh);
// 1.4285714285714285e-01
와 함께"%f"
, 정밀도 필드는 소수점 뒤의 자릿수 입니다.다음과 같은 숫자에 대해OneSeventh/1000000.0
, 사람은 필요할 것이다.OP_DBL_Digs + 6
중요한 숫자를 모두 보려면
printf("%.*f\n", OP_DBL_Digs , OneSeventh);
// 0.14285714285714285
printf("%.*f\n", OP_DBL_Digs + 6, OneSeventh/1000000.0);
// 0.00000014285714285714285
참고: 다수는 다음 작업에 사용됨"%f"
뒤에 하는 것; 소수점 뒤에 6자리를 표시하는 것; 6은 숫자의 정밀도가 아니라 표시 디폴트다.
부동 소수점 번호를 손실 없이 인쇄하는 간단한 대답(NaN과 Infinity를 제외한 정확히 동일한 번호로 다시 읽어 들이기 가능):
- 인 경우: :: 플로트인드 float float: :사를 한다.
printf("%.9g", number)
. - 유형이 이중인 경우: 사용
printf("%.17g", number)
.
사용 안 함%f
이는 소수점 이후 유의한 자릿수만 명시하고 작은 숫자는 잘리기 때문이다.참고로, 마법의 숫자 9와 17은 에서 찾을 수 있다.float.h
어떤 것이 정의되어 있는지FLT_DECIMAL_DIG
그리고DBL_DECIMAL_DIG
.
아니, 부동 소수점을 최대한 정밀하게 인쇄할 수 있는 인쇄 f 너비 지정자가 없다.왜 그런지 설명해 줄게.
최 의 float
그리고double
가변성이며, 실제 값에 따라 달라짐float
, 또는double
.
리콜float
그리고double
sign.content.csibissa 형식으로 저장됨.이것은 큰 숫자에 비해 작은 숫자에 대한 부분 성분에 사용되는 비트가 더 많다는 것을 의미한다.
예를 들어,float
0.0과 0.1을 쉽게 구별할 수 있다.
float r = 0;
printf( "%.6f\n", r ) ; // 0.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // 0.100000
그렇지만float
1e27
그리고1e27 + 0.1
.
r = 1e27;
printf( "%.6f\n", r ) ; // 999999988484154753734934528.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // still 999999988484154753734934528.000000
모든 정밀도(맨티사 비트 수에 의해 제한되는)가 십진수 왼쪽의 큰 숫자에 다 소모되기 때문이다.
그%.f
수식어는 형식에 따라 부동 소수점 숫자에서 인쇄할 소수점 값을 말한다.이용 가능한 정확도가 숫자의 크기에 따라 달라진다는 사실은 프로그래머로서 당신에게 달려있다. printf
당신을 위해 그것을 처리할 수 없다/그것을 할 수 없다.
내가 알기로는, 문자열을 다시 스캔할 때 원래의 부동소수 값이 다음에서 획득되도록 필요한 수의 유의한 숫자로 출력할 수 있는 잘 확산된 알고리즘이 있다.dtoa.c
여기 Netlib에서 이용할 수 있는 David Gay에 의해 쓰여졌다(관련 논문도 참조).이 코드는 Python, MySQL, Scilab 및 기타 여러 곳에서 사용된다.
대답에 대한 나의 논평 중 하나에서 나는 오랫동안 모든 중요한 숫자를 십진수 형태로, 질문이 요구하는 것과 같은 방식으로, 부동 소수점 값으로 인쇄할 수 있는 어떤 방법을 원해왔다고 한탄했다.흠, 마침내 나는 앉아서 그것을 썼다.완벽하진 않아. 그리고 이건 추가 정보를 출력하는 데모 코드야. 하지만 대부분 내 테스트에 효과가 있어.테스트를 위해 포장지 프로그램을 구동하는 전체 사본을 원하시는 분(즉, 누구라도)이 있으시면 알려주십시오.
static unsigned int
ilog10(uintmax_t v);
/*
* Note: As presented this demo code prints a whole line including information
* about how the form was arrived with, as well as in certain cases a couple of
* interesting details about the number, such as the number of decimal places,
* and possibley the magnitude of the value and the number of significant
* digits.
*/
void
print_decimal(double d)
{
size_t sigdig;
int dplaces;
double flintmax;
/*
* If we really want to see a plain decimal presentation with all of
* the possible significant digits of precision for a floating point
* number, then we must calculate the correct number of decimal places
* to show with "%.*f" as follows.
*
* This is in lieu of always using either full on scientific notation
* with "%e" (where the presentation is always in decimal format so we
* can directly print the maximum number of significant digits
* supported by the representation, taking into acount the one digit
* represented by by the leading digit)
*
* printf("%1.*e", DBL_DECIMAL_DIG - 1, d)
*
* or using the built-in human-friendly formatting with "%g" (where a
* '*' parameter is used as the number of significant digits to print
* and so we can just print exactly the maximum number supported by the
* representation)
*
* printf("%.*g", DBL_DECIMAL_DIG, d)
*
*
* N.B.: If we want the printed result to again survive a round-trip
* conversion to binary and back, and to be rounded to a human-friendly
* number, then we can only print DBL_DIG significant digits (instead
* of the larger DBL_DECIMAL_DIG digits).
*
* Note: "flintmax" here refers to the largest consecutive integer
* that can be safely stored in a floating point variable without
* losing precision.
*/
#ifdef PRINT_ROUND_TRIP_SAFE
# ifdef DBL_DIG
sigdig = DBL_DIG;
# else
sigdig = ilog10(uipow(FLT_RADIX, DBL_MANT_DIG - 1));
# endif
#else
# ifdef DBL_DECIMAL_DIG
sigdig = DBL_DECIMAL_DIG;
# else
sigdig = (size_t) lrint(ceil(DBL_MANT_DIG * log10((double) FLT_RADIX))) + 1;
# endif
#endif
flintmax = pow((double) FLT_RADIX, (double) DBL_MANT_DIG); /* xxx use uipow() */
if (d == 0.0) {
printf("z = %.*s\n", (int) sigdig + 1, "0.000000000000000000000"); /* 21 */
} else if (fabs(d) >= 0.1 &&
fabs(d) <= flintmax) {
dplaces = (int) (sigdig - (size_t) lrint(ceil(log10(ceil(fabs(d))))));
if (dplaces < 0) {
/* XXX this is likely never less than -1 */
/*
* XXX the last digit is not significant!!! XXX
*
* This should also be printed with sprintf() and edited...
*/
printf("R = %.0f [%d too many significant digits!!!, zero decimal places]\n", d, abs(dplaces));
} else if (dplaces == 0) {
/*
* The decimal fraction here is not significant and
* should always be zero (XXX I've never seen this)
*/
printf("R = %.0f [zero decimal places]\n", d);
} else {
if (fabs(d) == 1.0) {
/*
* This is a special case where the calculation
* is off by one because log10(1.0) is 0, but
* we still have the leading '1' whole digit to
* count as a significant digit.
*/
#if 0
printf("ceil(1.0) = %f, log10(ceil(1.0)) = %f, ceil(log10(ceil(1.0))) = %f\n",
ceil(fabs(d)), log10(ceil(fabs(d))), ceil(log10(ceil(fabs(d)))));
#endif
dplaces--;
}
/* this is really the "useful" range of %f */
printf("r = %.*f [%d decimal places]\n", dplaces, d, dplaces);
}
} else {
if (fabs(d) < 1.0) {
int lz;
lz = abs((int) lrint(floor(log10(fabs(d)))));
/* i.e. add # of leading zeros to the precision */
dplaces = (int) sigdig - 1 + lz;
printf("f = %.*f [%d decimal places]\n", dplaces, d, dplaces);
} else { /* d > flintmax */
size_t n;
size_t i;
char *df;
/*
* hmmmm... the easy way to suppress the "invalid",
* i.e. non-significant digits is to do a string
* replacement of all dgits after the first
* DBL_DECIMAL_DIG to convert them to zeros, and to
* round the least significant digit.
*/
df = malloc((size_t) 1);
n = (size_t) snprintf(df, (size_t) 1, "%.1f", d);
n++; /* for the NUL */
df = realloc(df, n);
(void) snprintf(df, n, "%.1f", d);
if ((n - 2) > sigdig) {
/*
* XXX rounding the integer part here is "hard"
* -- we would have to convert the digits up to
* this point back into a binary format and
* round that value appropriately in order to
* do it correctly.
*/
if (df[sigdig] >= '5' && df[sigdig] <= '9') {
if (df[sigdig - 1] == '9') {
/*
* xxx fixing this is left as
* an exercise to the reader!
*/
printf("F = *** failed to round integer part at the least significant digit!!! ***\n");
free(df);
return;
} else {
df[sigdig - 1]++;
}
}
for (i = sigdig; df[i] != '.'; i++) {
df[i] = '0';
}
} else {
i = n - 1; /* less the NUL */
if (isnan(d) || isinf(d)) {
sigdig = 0; /* "nan" or "inf" */
}
}
printf("F = %.*s. [0 decimal places, %lu digits, %lu digits significant]\n",
(int) i, df, (unsigned long int) i, (unsigned long int) sigdig);
free(df);
}
}
return;
}
static unsigned int
msb(uintmax_t v)
{
unsigned int mb = 0;
while (v >>= 1) { /* unroll for more speed... (see ilog2()) */
mb++;
}
return mb;
}
static unsigned int
ilog10(uintmax_t v)
{
unsigned int r;
static unsigned long long int const PowersOf10[] =
{ 1LLU, 10LLU, 100LLU, 1000LLU, 10000LLU, 100000LLU, 1000000LLU,
10000000LLU, 100000000LLU, 1000000000LLU, 10000000000LLU,
100000000000LLU, 1000000000000LLU, 10000000000000LLU,
100000000000000LLU, 1000000000000000LLU, 10000000000000000LLU,
100000000000000000LLU, 1000000000000000000LLU,
10000000000000000000LLU };
if (!v) {
return ~0U;
}
/*
* By the relationship "log10(v) = log2(v) / log2(10)", we need to
* multiply "log2(v)" by "1 / log2(10)", which is approximately
* 1233/4096, or (1233, followed by a right shift of 12).
*
* Finally, since the result is only an approximation that may be off
* by one, the exact value is found by subtracting "v < PowersOf10[r]"
* from the result.
*/
r = ((msb(v) * 1233) >> 12) + 1;
return r - (v < PowersOf10[r]);
}
비트((각 hex턴 반복)을할 수 .%a
형식을 갖추다이를 통해 다음을 보장할 수 있다.
베이스 2에 정확한 표현이 존재하며, 그 외의 경우 타입 2의 값을 구별할 수 있을 정도로 충분히 큰 경우, 값을 정확하게 표현할 수 있는 기본 정밀도가 충분하다.
이건 C99 이후에만 사용 가능하다는 점을 덧붙여야겠습니다.
는 작은 하는 것이 은색인쇄라는 을 증명한다.DBL_DECIMAL_DIG
숫자의 이중 표현을 정확히 보존하고 있다.알고 보니 내가 시도한 컴파일러와 C도서관의 경우DBL_DECIMAL_DIG
실제로 필요한 자릿수이며, 한 자릿수가 더 적은 인쇄는 심각한 문제를 야기한다.
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
union {
short s[4];
double d;
} u;
void
test(int digits)
{
int i, j;
char buff[40];
double d2;
int n, num_equal, bin_equal;
srand(17);
n = num_equal = bin_equal = 0;
for (i = 0; i < 1000000; i++) {
for (j = 0; j < 4; j++)
u.s[j] = (rand() << 8) ^ rand();
if (isnan(u.d))
continue;
n++;
sprintf(buff, "%.*g", digits, u.d);
sscanf(buff, "%lg", &d2);
if (u.d == d2)
num_equal++;
if (memcmp(&u.d, &d2, sizeof(double)) == 0)
bin_equal++;
}
printf("Tested %d values with %d digits: %d found numericaly equal, %d found binary equal\n", n, digits, num_equal, bin_equal);
}
int
main()
{
test(DBL_DECIMAL_DIG);
test(DBL_DECIMAL_DIG - 1);
return 0;
}
마이크로소프트의 C 컴파일러 19.00.24215.1 및 gcc 버전 7.4.0 20170516(Debian 6.3.0-18+deb9u1)과 함께 이것을 실행한다.소수점 이하 1자리 사용으로 정확히 비교하는 숫자의 반을 반반으로 한다.(또한 확인했다.rand()
실제로 사용된 것과 같이 약 백만 개의 서로 다른 숫자를 산출한다.)자세한 결과는 다음과 같다.
마이크로소프트 C
17자리 숫자로 999507 값 테스트: 999507은 숫자, 999507은 이진수가 같음16자리 999507 값 테스트: 545389는 숫자, 545389는 2진수 동일함
GCC
17자리 숫자 999485 테스트: 999485는 숫자, 999485는 2진수 동일함16자리 숫자로 999485개 값 테스트: 545402는 숫자, 545402는 2진수 동일함
다음 매크로만 사용하십시오.<float.h>
및 가변 폭 변환 지정자(".*"
):
float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
'programing' 카테고리의 다른 글
오류 메시지."Object/Array 타입의 제안서는 기본값을 반환하기 위해 공장 기능을 사용해야 한다." (0) | 2022.05.12 |
---|---|
Java에서 가져오기 이름 변경 또는 이름이 같은 클래스 두 개 가져오기 (0) | 2022.05.12 |
Vuex - 모듈 상태에 따라 계산된 속성이 디스패치 시 업데이트되지 않는가? (0) | 2022.05.12 |
플러그인용 Vue CLI 3 vue.config.js 대 webpack.config.js (0) | 2022.05.12 |
"char s[static 10]"와 같은 함수의 배열 매개변수에서 정적 키워드의 목적은? (0) | 2022.05.12 |