printing float, preserving precision

北城以北 提交于 2019-12-17 18:35:04

问题


I am writing a program that prints floating point literals to be used inside another program.

How many digits do I need to print in order to preserve the precision of the original float?

Since a float has 24 * (log(2) / log(10)) = 7.2247199 decimal digits of precision, my initial thought was that printing 8 digits should be enough. But if I'm unlucky, those 0.2247199 get distributed to the left and to the right of the 7 significant digits, so I should probably print 9 decimal digits.

Is my analysis correct? Is 9 decimal digits enough for all cases? Like printf("%.9g", x);?

Is there a standard function that converts a float to a string with the minimum number of decimal digits required for that value, in the cases where 7 or 8 are enough, so I don't print unnecessary digits?

Note: I cannot use hexadecimal floating point literals, because standard C++ does not support them.


回答1:


In order to guarantee that a binary->decimal->binary roundtrip recovers the original binary value, IEEE 754 requires


The original binary value will be preserved by converting to decimal and back again using:[10]

    5 decimal digits for binary16
    9 decimal digits for binary32
    17 decimal digits for binary64
    36 decimal digits for binary128

For other binary formats the required number of decimal digits is

    1 + ceiling(p*log10(2)) 

where p is the number of significant bits in the binary format, e.g. 24 bits for binary32.

In C, the functions you can use for these conversions are snprintf() and strtof/strtod/strtold().

Of course, in some cases even more digits can be useful (no, they are not always "noise", depending on the implementation of the decimal conversion routines such as snprintf() ). Consider e.g. printing dyadic fractions.




回答2:


24 * (log(2) / log(10)) = 7.2247199

That's pretty representative for the problem. It makes no sense whatsoever to express the number of significant digits with an accuracy of 0.0000001 digits. You are converting numbers to text for the benefit of a human, not a machine. A human couldn't care less, and would much prefer, if you wrote

24 * (log(2) / log(10)) = 7

Trying to display 8 significant digits just generates random noise digits. With non-zero odds that 7 is already too much because floating point error accumulates in calculations. Above all, print numbers using a reasonable unit of measure. People are interested in millimeters, grams, pounds, inches, etcetera. No architect will care about the size of a window expressed more accurately than 1 mm. No window manufacturing plant will promise a window sized as accurate as that.

Last but not least, you cannot ignore the accuracy of the numbers you feed into your program. Measuring the speed of an unladen European swallow down to 7 digits is not possible. It is roughly 11 meters per second, 2 digits at best. So performing calculations on that speed and printing a result that has more significant digits produces nonsensical results that promise accuracy that isn't there.




回答3:


If the program is meant to be read by a computer, I would do the simple trick of using char* aliasing.

  • alias float* to char*
  • copy into an unsigned (or whatever unsigned type is sufficiently large) via char* aliasing
  • print the unsigned value

Decoding is just reversing the process (and on most platform a direct reinterpret_cast can be used).




回答4:


If you have a C library that is conforming to C99 (and if your float types have a base that is a power of 2 :) the printf format character %a can print floating point values without lack of precision in hexadecimal form, and utilities as scanf and strod will be able to read them.




回答5:


The floating-point-to-decimal conversion used in Java is guaranteed to be produce the least number of decimal digits beyond the decimal point needed to distinguish the number from its neighbors (more or less).

You can copy the algorithm from here: http://www.docjar.com/html/api/sun/misc/FloatingDecimal.java.html Pay attention to the FloatingDecimal(float) constructor and the toJavaFormatString() method.




回答6:


If you read these papers (see below), you'll find that there are some algorithm that print the minimum number of decimal digits such that the number can be re-interpreted unchanged (i.e. by scanf).

Since there might be several such numbers, the algorithm also pick the nearest decimal fraction to the original binary fraction (I named float value).

A pity that there's no such standard library in C.

  • http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf
  • http://grouper.ieee.org/groups/754/email/pdfq3pavhBfih.pdf



回答7:


You can use sprintf. I am not sure whether this answers your question exactly though, but anyways, here is the sample code

#include <stdio.h>
int main( void )
{
float d_n = 123.45;
char s_cp[13] = { '\0' };
char s_cnp[4] = { '\0' };
/*
* with sprintf you need to make sure there's enough space
* declared in the array
*/
sprintf( s_cp, "%.2f", d_n );
printf( "%s\n", s_cp );
/*
* snprinft allows to control how much is read into array.
* it might have portable issues if you are not using C99
*/
snprintf( s_cnp, sizeof s_cnp - 1 , "%f", d_n );
printf( "%s\n", s_cnp );
getchar();
return 0;
}
/* output :
* 123.45
* 123
*/



回答8:


With something like

def f(a):
    b=0
    while a != int(a): a*=2; b+=1
    return a, b

(which is Python) you should be able to get mantissa and exponent in a loss-free way.

In C, this would probably be

struct float_decomp {
    float mantissa;
    int exponent;
}

struct float_decomp decomp(float x)
{
    struct float_decomp ret = { .mantissa = x, .exponent = 0};
    while x != floor(x) {
        ret.mantissa *= 2;
        ret.exponent += 1;
    }
    return ret;
}

But be aware that still not all values can be represented in that way, it is just a quick shot which should give the idea, but probably needs improvement.



来源:https://stackoverflow.com/questions/10895243/printing-float-preserving-precision

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!