Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ std::setprecision in C#

Tags:

c#

I'm picking up C# by porting some legacy C++ code and would like to keep the output identical. What used to be something along the lines of

output << std::setprecision(10) << (double) value;

I figured would now be

output.Write("{0:F10}", value);

But this didn't do the trick. Specifically values > 1 would get more digits. A common online suggestion was to Math.Round first, but this appends zeroes if the total length was < 10.

So I put together:

    // std::setprecision is not exactly the same as ":F10", mirror original behavior
    static string setPrecision(double value) {
        string ret = value.ToString();

        // Don't just Substring(0, 11), we need to apply rounding,
        // and don't always do this, we don't want to append zeroes,
        // for 10 digits + period, with 0.. not counting for total
        if(ret.Length > digits + 1)
            ret = Math.Round(value, digits + (value < 1 ? 1 : 0) - ret.IndexOf('.')).ToString();

        return ret;
    }

where digits is a static constant; I could certainly make this a variable, but for this project in particular it makes little sense to do so.

Still, this seems overly complicated. Is there a more elegant way to get the traditional behavior?

As requested some example I/O

// C++
double test = 0; out << std::setprecision(10);
test = 0.123456780;   out << test << '\n';
test = 0.0123456781;  out << test << '\n';
test = 0.11234567819; out << test << '\n';
test = 1.00234567899; out << test << '\n';

// C#
double test = 0;            
test = 0.123456780;   output.WriteLine(setPrecision(test));
test = 0.0123456781;   output.WriteLine(setPrecision(test));
test = 0.11234567819; output.WriteLine(setPrecision(test));
test = 1.00234567899; output.WriteLine(setPrecision(test));

Both produce:

0.12345678
0.0123456781
0.1123456782
1.002345679

And meanwhile I noticed all heading zeroes don't seem to count towards the total rather than just the first;

// C++
test = 0.012345678906;    out << test << '\n'; // 0.01234567891
test = 0.0012345678906;   out << test << '\n'; // 0.001234567891
test = 0.00012345678906;  out << test << '\n'; // 0.0001234567891

// C#
test = 0.012345678906;   output.WriteLine(setPrecision(test)); // 0.0123456789
test = 0.0012345678906;  output.WriteLine(setPrecision(test)); // 0.0012345679
test = 0.00012345678906; output.WriteLine(setPrecision(test)); // 0.0001234568

I'll have to correct that if there isn't a more straightforward solution.

like image 945
Meeuwisse Avatar asked Aug 30 '15 16:08

Meeuwisse


2 Answers

It sounds like you just want to print the number with a specific number of significant digits. You can simply use the G format string to specify the number of digits to use.

output.Write("{0:G10}", value);
like image 144
Mumbo Avatar answered Oct 30 '22 07:10

Mumbo


What you are referring to is significant figures. It's actually pretty easy to calculate:

    public static string FormatSignificantFigures(double number, int figures)
    {
        int e = 0;

        while (number >= 10.0)
        {
            e += 1;
            number /= 10;
        }

        while (number < 1.0)
        {
            e -= 1;
            number *= 10;
        }

        figures--;

        number = Math.Round(number, figures);

        figures += 0 - e;
        while (e > 0)
        {
            number *= 10;
            e -= 1;
        }

        while (e < 0)
        {
            number /= 10;
            e += 1;
        }

        if (figures < 0)
        {
            figures = 0;
        }

        return number.ToString($"f{figures}");
    }

Basically, the first two while loops normalize our number to a value between [1,10).

Then, we round the number to the number of significant figures (minus 1, remembering that we already have a significant figure of 1 at the front). We then restore it, and the last line is a C#6.0 string interpolation for:

return number.ToString("f" + figures);

The test code was:

    public static void _Main(string[] args)
    {
        double[] numbers = new double[] { 0.012345678906, 0.0012345678906, 0.00012345678906, 0.123456789012, 1.234567890124, 12.345678901234, 123.45678901234, 1234.5678901234, 12345.678901234 };

        foreach (double number in numbers)
        {
            Console.WriteLine($"{number}: {FormatSignificantFigures(number, 3)}");
        }
    }

Results:

0.012345678906: 0.0123
0.0012345678906: 0.00123
0.00012345678906: 0.000123
0.123456789012: 0.123
1.234567890124: 1.23
12.345678901234: 12.3
123.45678901234: 123
1234.5678901234: 1230
12345.678901234: 12300

Note: this was a quick answer, I'm going to extract some of this out to another function which doesn't return a string momentarily, but this should get you started.

like image 39
Der Kommissar Avatar answered Oct 30 '22 07:10

Der Kommissar