4

I am serializing values to an XML document. When I serialize a double, I want to make sure I am not losing any precision, and that culture will not interfere. Or any other "gotchas" that I haven't thought of.

Similarly for DateTime, I use ToBinary() to get a value that's very safe to serialize.

So, what is the best method to serialize / deserialize a double value in C#?

tenfour
  • 36,141
  • 15
  • 83
  • 142
  • 2
    If precision is important shouldn't you be using a decimal (not double)? http://stackoverflow.com/questions/1165761/decimal-vs-double-which-one-should-i-use-and-when – Kane Jun 28 '12 at 00:09
  • Datatype precision is not relevant; I just want to make sure nothing is lost in conversion to / from string. – tenfour Jun 28 '12 at 00:17
  • 1
    @Kane that very much depends on the numbers being encoded. If they are of form x/2^y (e.g. binary fraction), decimal would be a bad choice. OTOH for numbers satisfying x/10^y (e.g. decimal fraction), double would be a bad choice. – spender Jun 28 '12 at 00:35

4 Answers4

8

In order to parse back the exact value, you should use the G17 format specifier, as documented here:

d.ToString("G17")

or

$"{d:G17}"

Note that this can fail to roundtrip on versions below .NET Core 3.0.


Original answer below; the "r" format specifier mentioned actually has a bug so the documentation was incorrect.


The round-trip format specifier "r" guarantees that if you use double.Parse on the string, you will get back the exact same value:

d.ToString("r")
porges
  • 30,133
  • 4
  • 83
  • 114
  • Ah I didn't know about this one. But if the reading culture is different than the writing culture, it can still cause problems. But I will just add `CultureInfo.InvaliantCulture.NumberFormat` and it should solve things. – tenfour Jun 28 '12 at 00:30
  • 1
    I don't know if this is the case when you read the article, but I see no such guarantee. To the contrary, it says: *For Double values, the "R" format specifier in some cases fails to successfully round-trip the original value.* – hypehuman Aug 31 '20 at 10:07
  • @hypehuman yes, that is a recent addition. The page as it was in 2012 read "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." https://web.archive.org/web/20120613220729/http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx – porges Sep 01 '20 at 20:23
  • @hypehuman I'll update the answer with this information. – porges Sep 01 '20 at 20:23
2

Checkout XmlConvert.ToString(double value)

This handles infinity too:

If value is Double.PositiveInfinity or Double.NegativeInfinity, this method returns the string INF or -INF respectively.

saille
  • 9,014
  • 5
  • 45
  • 57
  • I don't think this will always work. According to the [source code](https://referencesource.microsoft.com/#system.xml/System/Xml/XmlConvert.cs) (.NET Framework version 4.8, at the time of this comment), 'XmlConvert.ToString(double value)' uses the Round-trip ("R") format specifier, which, according [the documentation](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings?redirectedfrom=MSDN#RFormatString), doesn't always successfully round-trip the value. – hypehuman Aug 31 '20 at 16:03
1

BitConverter.GetBytes(myValue) will give a Byte array which could then be serialized with no loss of precision.

TheEvilPenguin
  • 5,634
  • 1
  • 26
  • 47
  • 1
    Wouldn't this require that the machine that printed the string and the machine that read the string have the same endianness? Otherwise the bits could get read backwards. – hypehuman Aug 31 '20 at 10:12
1

It looks like this should work. According to my tests, it also round-trips special values like infinity and NaN just fine.

static string SerializeDouble(double d)
{
    return d.ToString("G17", CultureInfo.InvariantCulture);
}

static double DeserializeDouble(string s)
{
    return double.Parse(s, CultureInfo.InvariantCulture);
}

Why "G17"?

Source: https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings?redirectedfrom=MSDN#GFormatString

Relevant quote (at the time of this post):

Note that, when used with a Double value, the "G17" format specifier ensures that the original Double value successfully round-trips. This is because Double is an IEEE 754-2008-compliant double-precision (binary64) floating point number that gives up to 17 significant digits of precision. We recommend its use instead of the "R" format specifier, since in some cases "R" fails to successfully round-trip double-precision floating point values.


Why CultureInfo.InvariantCulture?

This takes care of any culture-specific formatting that would otherwise break your round-trip. For example, this code snippet gives some very undesirable results!

double d0 = Math.PI;
Console.WriteLine("d0: " + d0); // prints 3.14159265358979
// Serialize in America
string s = d0.ToString("G17", new CultureInfo("en-us"));
Console.WriteLine("s: " + s); // prints 3.1415926535897931
// Deserialize in Germany
double d1 = double.Parse(s, new CultureInfo("de-de"));
Console.WriteLine("d1: " + d1); // prints 3.14159265358979E+16
hypehuman
  • 1,290
  • 2
  • 18
  • 37