简体   繁体   English

如何打印浮点值以便以后以完美的精度进行扫描?

[英]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).假设我有一个floatdouble类型的浮点值(即典型机器上的 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:笔记:

  • The scanning does not having to be perfectly accurate w.r.t.扫描不必非常准确 w.r.t。 the value it scans, only the original value.它扫描的值,只有原始值。
  • If it makes it easier, you may assume the value is a number and is not infinity.如果它更容易,您可以假设该值是一个数字而不是无穷大。
  • denormal support is desired but not required;需要非正规支持,但不是必需的; still if we get a denormal, failure should be conspicuous.仍然,如果我们得到一个非正常的,失败应该是显着的。

First, you should use the %a format with fprintf and fscanf .首先,您应该将%a格式与fprintffscanf一起使用。 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 ,其中Typefloatdouble或其他浮点类型。

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 with DECIMAL_DIG digits and back should be the identity function.使用DECIMAL_DIG数字从(至少) double精度数到十进制数的转换应该是标识 function。 [ DECIMAL_DIG is the equivalent of FLT_DECIMAL_DIG or DBL_DECIMAL_DIG for the widest floating-point format supported in the implementation.] [ DECIMAL_DIG等效于FLT_DECIMAL_DIGDBL_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.格式说明符%ap字符分隔尾数和指数(如“...乘以 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;
}

Testable here .在此处测试。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM