10
\$\begingroup\$

I am trying to account for the errors in double arithmetic in a C# program I am writing, and came up with this first solution that seems to work well for most cases:

    double errorMargins = returnDouble * Double.Parse(Unit_Class_Library.Properties.Resources.AcceptedDeviationConstant);
    double roundedValue = 0;
    for (int i = 1; i <= returnDoubleAsString.Length; i++)
    {
        roundedValue = RoundToSignificantDigits(returnDouble, i);
        if (Math.Abs(roundedValue - returnDouble) < Double.Parse(Unit_Class_Library.Properties.Resources.AcceptedDeviationConstant))
        {
            returnDouble = roundedValue;
            i = returnDoubleAsString.Length;
        }

    }

    return returnDouble;
}

public static double RoundToSignificantDigits(double d, int digits)
{
    if (d == 0)
        return 0;

    bool isNegative = false;
    if (d < 0)
        isNegative = true;

    double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);
    double returnValue =  scale * Math.Round(d / scale, digits);

    if (isNegative)
        returnValue = -returnValue;

    return returnValue;
}

However, I found that when you convert a number to a string it rounds the number making the above function not work.

For example, when 720.72499999999991 is inputted (with a AcceptedDeviationConstant of 0.000000001), the double as string would be "720.725" which causes the above loop to exit prematurely and not do its job leaving the number as 720.72499999999991. So what I did was convert the double to a string and then back to a double (the code block below) and this seems to work how I wanted it too, returning 720.725 in the above example.

string returnDoubleAsString = "" + returnDouble;
returnDouble = double.Parse(returnDoubleAsString);

I was wondering if it is okay to just leave it as the second one but it seems "improper" to do it like this. If it is okay in practice, what kind of comment should I leave to make it clear to other programmers what is going on and is there a link I can put in the comment that explains why this works?

\$\endgroup\$
  • \$\begingroup\$ if you are working witch such a big amount of precision, have you considered using decimal data type instead? \$\endgroup\$ – elios264 Sep 13 '14 at 13:34
  • \$\begingroup\$ This kind of operations on float values gives me the impression that you are working within a bad design. Why do you want to round a float value to a float value? Usually only when converting floats to string (for user consumption) this is useful. \$\endgroup\$ – Emanuele Paolini Sep 13 '14 at 13:56
2
\$\begingroup\$

Updated:

Seems like C# has its own format specifier "R" Round trip

The round-trip ("R") format specifier guarantees that a numeric value that is converted to a string will be parsed back into the same numeric value. This format is supported only for the Single, Double, and BigInteger types.

public static double RoundTrip(double d)
{
    return Double.Parse(d.ToString("R"));
}

Original:

The way to represent floating point numbers exactly on a stream in readable format is to print out there representation in hex format (rather than decimal):

Not sure how it is done in C# or Java but I bet if you dig the exact same method is used down deep in these languages.

In C++ and C the methods are the same.
The C++ version.

double x = 5.6789;

std::cout << x << "\n";   // Human readable dot format.
                          // Has loss of precision as number may not 
                          // be exactly representable in decimal.


// This the equivalent of using the "%a" string formatter.
// fprintf(stdout, "%a\n", x);
//
std::cout.flags(std::ios_base::fixed | std::ios_base::scientific);
std::cout << x << "\n";   // Hex Format human readable (well nearly human readable)
                          // Prints each part of the float as a hex value
                          // That can be represented exactly.
                          // on a stream of char

Updated after reading: Printing double without losing precision

// In C++11 there is an additional manipulator
// that achieves the same affect.
std::cout << std::hexfloat << x << "\n";

The results:

5.6789                    // Easy to read (not exact)

0x1.6b7318fc50481p+2      // Difficult to read (but exact).

0x1.6b7318fc50481p+2      // Difficult to read (but exact).
\$\endgroup\$
1
\$\begingroup\$

You can use the exponential (scientific) notation format specifier when converting to string. E.g. (12345678).ToString("E2") produces "1.23E+007", returning one additional significant digit.

public static double RoundToSignificantDigits(double d, int digits)
{
    return Double.Parse(d.ToString("E" + (digits - 1).ToString()));
}

Note that the AcceptedDeviationConstant of 0.000000001 might fail for big numbers, as this accepted derivation may be smaller as the precision of large doubles. A Double value has up to 15 decimal digits of precision.

123456789.123456         (15 digits)
        0.000000001 
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.