[英]How do I print a floating-point value for later scanning with perfect accuracy?
Suppose I have a floating-point value of type float
or double
(ie 32 or 64 bits on typical machines).假设我有一个
float
或double
类型的浮点值(即典型机器上的 32 或 64 位)。 I want to print this value as text (eg to the standard output stream), and then later, in some other process, scan it back in - with fscanf()
if I'm using C, or perhaps with istream::operator>>()
if I'm using C++.我想将此值打印为文本(例如,打印到标准 output 流),然后在其他一些过程中,将其扫描回来 - 如果我使用的是 C,则使用
fscanf()
,或者可能使用istream::operator>>()
如果我使用的是 C++。 But - I need the scanned float to end up being exactly , identical to the original value (up to equivalent representations of the same value).但是 - 我需要扫描的浮点数最终与原始值完全相同(直到相同值的等效表示)。 Also, the printed value should be easily readable - to a human - as floating-point, ie I don't want to print 0x42355316 and reinterpret that as a 32-bit float.
此外,打印的值应该易于阅读 - 对人类来说 - 作为浮点数,即我不想打印 0x42355316 并将其重新解释为 32 位浮点数。
How should I do this?我该怎么做? I'm assuming the standard library of (C and C++) won't be sufficient, but perhaps I'm wrong.
我假设(C 和 C++)的标准库是不够的,但也许我错了。 I suppose that a sufficient number of decimal digits might be able to guarantee an error that's underneath the precision threshold - but that's not the same as guaranteeing the rounding/truncation will happen just the way I want it.
我想足够数量的十进制数字可能能够保证低于精度阈值的错误 - 但这与保证舍入/截断将按照我想要的方式发生不同。
Notes:笔记:
First, you should use the %a
format with fprintf
and fscanf
.首先,您应该将
%a
格式与fprintf
和fscanf
一起使用。 This is what it was designed for, and the C standard requires it to work (reproduce the original number) if the implementation uses binary floating-point.这就是它的设计目的,如果实现使用二进制浮点,C 标准要求它工作(重现原始数字)。
Failing that, you should print a float
with at least FLT_DECIMAL_DIG
significant digits and a double
with at least DBL_DECIMAL_DIG
significant digits.否则,您应该打印一个至少具有 FLT_DECIMAL_DIG 有效数字的
float
和一个至少FLT_DECIMAL_DIG
有效数字的double
DBL_DECIMAL_DIG
数。 Those constants are defined in <float.h>
and are defined:这些常量在
<float.h>
中定义并定义:
… number of decimal digits, n , such that any floating-point number with p radix b digits can be rounded to a floating-point number with n decimal digits and back again without change to the value,… [ b is the base used for the floating-point format, defined in
FLT_RADIX
, and p is the number of base- b digits in the format.]... 小数位数, n ,这样任何具有p基数b数字的浮点数都可以四舍五入为具有n十进制数字的浮点数,然后再次返回而无需更改值,... [ b是用于浮点格式,在
FLT_RADIX
中定义, p是格式中以b为基数的位数。]
For example:例如:
printf("%.*g\n", FLT_DECIMAL_DIG, 1.f/3);
or:或者:
#define QuoteHelper(x) #x
#define Quote(x) QuoteHelper(x)
…
printf("%." Quote(FLT_DECIMAL_DIG) "g\n", 1.f/3);
In C++, these constants are defined in <limits>
as std::numeric_limits<Type>::max_digits10
, where Type
is float
or double
or another floating-point type.在 C++ 中,这些常量在
<limits>
中定义为std::numeric_limits<Type>::max_digits10
,其中Type
是float
或double
或其他浮点类型。
Note that the C standard only recommends that such a round-trip through a decimal numeral work;请注意,C 标准仅建议通过十进制数字进行此类往返; it does not require it.
它不需要它。 For example, C 2018 5.2.4.2.2 15 says, under the heading “Recommended practice”:
例如,C 2018 5.2.4.2.2 15 在“推荐做法”标题下说:
Conversion from (at least)
double
to decimal withDECIMAL_DIG
digits and back should be the identity function.使用
DECIMAL_DIG
数字从(至少)double
精度数到十进制数的转换应该是标识 function。 [DECIMAL_DIG
is the equivalent ofFLT_DECIMAL_DIG
orDBL_DECIMAL_DIG
for the widest floating-point format supported in the implementation.][
DECIMAL_DIG
等效于FLT_DECIMAL_DIG
或DBL_DECIMAL_DIG
实现中支持的最宽浮点格式。]
In contrast, if you use %a
, and FLT_RADIX
is a power of two (meaning the implementation uses a floating-point base that is two, 16, or another power of two), then C standard requires that the result of scanning the numeral produced with %a
equals the original number.相反,如果您使用
%a
,并且FLT_RADIX
是 2 的幂(意味着实现使用的浮点基数是 2、16 或其他 2 的幂),则 C 标准要求扫描数字的结果用%a
产生的数字等于原始数字。
I need the scanned float to end up being exactly , identical to the original value.
我需要扫描的浮点数最终与原始值完全相同。
As already pointed out in the other answers, that can be achieved with the %a
format specifier.正如其他答案中已经指出的那样,可以使用
%a
格式说明符来实现。
Also, the printed value should be easily readable - to a human - as floating-point, ie I don't want to print 0x42355316 and reinterpret that as a 32-bit float.
此外,打印的值应该易于阅读 - 对人类来说 - 作为浮点数,即我不想打印 0x42355316 并将其重新解释为 32 位浮点数。
That's more tricky and subjective.这更加棘手和主观。 The first part of the string that
%a
produces is in fact a fraction composed by hexadecimal digits, so that an output like 0x1.4p+3
may take some time to be parsed as 10
by a human reader. %a
生成的字符串的第一部分实际上是由十六进制数字组成的分数,因此像0x1.4p+3
这样的 output 可能需要一些时间才能被人类阅读器解析为10
。
An option could be to print all the decimal digits needed to represent the floating-point value, but there may be a lot of them.一个选项可能是打印表示浮点值所需的所有十进制数字,但可能有很多。 Consider, for example the value 0.1, its closest representation as a 64-bit float may be
例如,考虑值 0.1,它最接近的表示为 64 位浮点数可能是
0x1.999999999999ap-4 == 0.1000000000000000055511151231257827021181583404541015625
While printf("%.*lf\n", DBL_DECIMAL_DIG, 01);
而
printf("%.*lf\n", DBL_DECIMAL_DIG, 01);
(see eg Eric's answer ) would print (参见例如 Eric 的回答)将打印
0.10000000000000001 // If DBL_DECIMAL_DIG == 17
My proposal is somewhere in the middle.我的建议介于中间。 Similarly to what
%a
does, we can exactly represent any floating-point value with radix 2 as a fraction multiplied by 2 raised to some integer power.与
%a
所做的类似,我们可以将基数为 2 的任何浮点值精确地表示为分数乘以 2 的 integer 幂。 We can transform that fraction into a whole number (increasing the exponent accordingly) and print it as a decimal value.我们可以将该分数转换为整数(相应地增加指数)并将其打印为十进制值。
0x1.999999999999ap-4 --> 1.999999999999a16 * 2-4 --> 1999999999999a16 * 2-56 --> 720575940379279410 * 2-56
That whole number has a limited number of digits (it's < 2 53 ), but the result it's still an exact representation of the original double
value.该整数的位数有限(它 < 2 53 ),但结果仍然是原始
double
值的精确表示。
The following snippet is a proof of concept, without any check for corner cases.下面的代码片段是一个概念证明,没有对极端情况进行任何检查。 The format specifier
%a
separates the mantissa and the exponent with a p
character (as in "... multiplied by two raised to the Power of..."), I'll use a q
instead, for no particular reason other than using a different symbol.格式说明符
%a
用p
字符分隔尾数和指数(如“...乘以 2 的...的幂”),我将使用q
代替,除了使用不同的符号。
The value of the mantissa will also be reduced (and the exponent raised accordingly), removing all the trailing zero-bits.尾数的值也将减少(并且指数相应地提高),删除所有尾随零位。 The idea beeing that
5q+1
(parsed as 5 10 * 2 1 ) should be more "easily" identified as 10
, rather than 2814749767106560q-48
.认为
5q+1
(解析为 5 10 * 2 1 )应该更“容易”识别为10
的想法,而不是2814749767106560q-48
。
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void to_my_format(double x, char *str)
{
int exponent;
double mantissa = frexp(x, &exponent);
long long m = 0;
if ( mantissa ) {
exponent -= 52;
m = (long long)scalbn(mantissa, 52);
// A reduced mantissa should be more readable
while (m && m % 2 == 0) {
++exponent;
m /= 2;
}
}
sprintf(str, "%lldq%+d", m, exponent);
// ^
// Here 'q' is used to separate the mantissa from the exponent
}
double from_my_format(char const *str)
{
char *end;
long long mantissa = strtoll(str, &end, 10);
long exponent = strtol(str + (end - str + 1), &end, 10);
return scalbn(mantissa, exponent);
}
int main(void)
{
double tests[] = { 1, 0.5, 2, 10, -256, acos(-1), 1000000, 0.1, 0.125 };
size_t n = (sizeof tests) / (sizeof *tests);
char num[32];
for ( size_t i = 0; i < n; ++i ) {
to_my_format(tests[i], num);
double x = from_my_format(num);
printf("%22s%22a ", num, tests[i]);
if ( tests[i] != x )
printf(" *** %22a *** Round-trip failed\n", x);
else
printf("%58.55g\n", x);
}
return 0;
}
Generally, the improvement in readability is admitedly little to none, surely a matter of opinion.一般来说,可读性的提高几乎没有,当然是见仁见智。
You can use the %a
format specifier to print the value as hexadecimal floating point.您可以使用
%a
格式说明符将值打印为十六进制浮点。 Note that this is not the same as reinterpreting the float
as an integer and printing the integer value.请注意,这与将
float
重新解释为 integer 并打印 integer 值不同。
For example:例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
float x;
scanf("%f", &x);
printf("x=%.7f\n", x);
char str[20];
sprintf(str, "%a", x);
printf("str=%s\n", str);
float y;
sscanf(str, "%f", &y);
printf("y=%.7f\n", y);
printf("x==y: %d\n", (x == y));
return 0;
}
With an input of 4, this outputs:输入为 4,输出:
x=4.0000000
str=0x1p+2
y=4.0000000
x==y: 1
With an input of 3.3, this outputs:输入为 3.3,输出:
x=3.3000000
str=0x1.a66666p+1
y=3.3000000
x==y: 1
As you can see from the output, the %a
format specifier prints in exponential format with the significand in hex and the exponent in decimal.从 output 中可以看出,
%a
格式说明符以指数格式打印,有效数字为十六进制,指数为十进制。 This format can then be converted directly back to the exact same value as demonstrated by the equality check.然后可以将此格式直接转换回与相等性检查所证明的完全相同的值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.