Double formatting different on x86 and x64 - c#

Given the following test:
[Fact]
public void FactMethodName()
{
var d = 6.4133;
var actual = d.ToString("R");
Assert.Equal("6.4133", actual);
}
It is passed on x86 but not on Any CPU or x64:
Assert.Equal() Failure
Position: First difference is at position 5
Expected: 6.4133
Actual: 6.4132999999999996
The question is why that happens? Note that not all double values behave this way.
I understand about issues with floating point. No need to point me to wikipedia. No need to point out that test is incorrect -- it just illustrates the problem -- change it to Console.WriteLine(..); if you will.
UPDATE I removed mentions of test runners becasue those details turned out to be irrelevant.

I think the secret is in using "R" format string (see more about this)
"When a Single or Double value is formatted using this specifier, it is first tested using the general format, with 15 digits of precision for a Double and 7 digits of precision for a Single. If the value is successfully parsed back to the same numeric value, it is formatted using the general format specifier. If the value is not successfully parsed back to the same numeric value, it is formatted using 17 digits of precision for a Double and 9 digits of precision for a Single."

As Raj and ja72 point out, the issue is do to with numeric rounding, and I realise your test is just an illustration of the problem, but in a real world test, you should avoid these logic errors. In particular avoid casting to string or calling any other method that may have side effects that can taint your test's success.
Unfortunately this is commonly referred to as a fragile test. It works on some machines, some of the time. If you are working in a development team (particularly one with a build server or and offshore, or near shore team) then tests like this can be worthy of the
Works on my machine award.

Related

Failure of the Round-trip format specifier "R" in .Net

The documentation recommends that I use G17 rather than R, as R can sometimes fail to round-trip.
However, (1.0/10).ToString("G17") gives "0.10000000000000001", which is pretty horrible. while the round-trip format seems to work just fine (and gives "0.1"). I'm happy to spend a few cpu cycles in order to get a more aesthetic result. But potential round-trip failures are more concerning.
For what sort of (Double) values does R fail to round-trip? And how badly?
Does the .Net version (we run on both Net Framework 4.72 and NetCore 3.1) affect things? Could writing on one platform and reading on another make round-trip failure more frequent?
We are considering writing doubles to R first, parsing to check the round-trip, and falling back to G17 only if that fails. Is there a better way to get nicely formatted, reliable results?
Round trip here is a numeric value that is converted to a string is parsed back into the same numeric value.
OP's dismay with (1.0/10).ToString("G17") gives "0.10000000000000001", which is pretty horrible. is an incorrect assessment of round trip success. The intermediate string is only half of the round trip.
Double exactly encodes about 264 different values. All encodable values are some limited integer * 2some_power. 0.1 is not one of them. 1.0/10 makes a math quotient of 0.1, but a slightly different Double value. The closest Double value and it two closet Double neighbors:
Before 0.099999999999999991673...
0.100000000000000005551...
After 0.100000000000000019428...
OP report 0.10000000000000001
Digit count 12345678901234567
OP's example should then be <(0.100000000000000005551).ToString("G17") gives "0.10000000000000001"> which is good.
Printing a Double with G17 provides 17 significant digits, enough to successfully round trip.
For what sort of (Double) values does R fail to round-trip? And how badly?
For this, I go on memory. R sometimes used less than 17 significant digits, like 15, to form the intermediate string. The algorithm used to determine the digit count sometimes came up a bit short and hence "some cases fails to successfully round-trip the original value".
Using G17 always works. For some values, less than 17 also would have worked. The down-side to G17 is exactly in cases like this. Fewer than 17 digits would have worked and have provided a more pleasing, shorter intermediate string.
A pleasing human readable string is not the goal of round-tripping. The goal is to form the same Double after going form value to string to value, even if the intermediate string has extra digits in select cases.
Is there a better way to get nicely formatted, reliable results?
"nicely formatted" is an additional burden to round trip. MS attempted to do so with R and failed in some cases, preferring to retain the same broken functionality than to fix it.
OP would be wise to avoid that path and forego the goal of nicely formatted intermediate string and focus on the round-trip goal of getting the final same value back.
Use G17.
We are considering writing doubles to R first, parsing to check the round-trip, and falling back to G17 only if that fails.
That would work if done correctly. To assess correctness, test your code with many values and also post it and the test harness for code review.

64 vs 32 bit double parsing issue with the round-trip format specifier "R"

I've got a scenario in .net (4.6.1) where parsing a string representation of a 6dp floating point value produces different results in 32 and 64 bit mode.
[Fact]
public void ParseTest()
{
var numText = "51.580133";
double.Parse(numText)
.ToString("R")
.Should().Be(numText);
}
This test passes in 32 bit mode, but for 64 bit mode fails as the text generated is: "51.580132999999996"
I'd expect rounding issues like this with irrational numbers or numbers derived via an equation, but there is no ambiguity about the length and accuracy of the floating point here.
This is inside an older system so changing everything to decimal would take a significant effort.
Questions:
Why does this happen?
What options are there to reliably round / truncate this value to 6dp?
Update
This works, and produces a different output to ToString("G6"):
[Fact]
public void ParseText()
{
var numText = "51.580133";
double.Parse(numText)
.ToString("G8")
.Should().Be(numText);
}
I found this interesting point from Microsoft which might explain the issue
In some cases, Double values formatted with the "R" standard numeric
format string do not successfully round-trip if compiled using the
/platform:x64 or /platform:anycpu switches and run on 64-bit systems.
To work around this problem, you can format Double values by using the
"G17" standard numeric format string. The following example uses the
"R" format string with a Double value that does not round-trip
successfully, and also uses the "G17" format string to successfully
round-trip the original value.
The comment and an example can be found here: https://msdn.microsoft.com/en-us/library/kfsatb94(v=vs.110).aspx

Strange behavior when casting decimal to double

I'm experiencing strange issue when casting decimal to double.
Following code returns true:
Math.Round(0.010000000312312m, 2) == 0.01m //true
However, when I cast this to double it returns false:
(double)Math.Round(0.010000000312312m, 2) == (double)0.01m //false
I've experienced this problem when I wanted to use Math.Pow and was forced to cast decimal to double since there is no Math.Pow overload for decimal.
Is this documented behavior? How can I avoid it when I'm forced to cast decimal to double?
Screenshot from Visual Studio:
Casting Math.Round to double me following result:
(double)Math.Round(0.010000000312312m, 2) 0.0099999997764825821 double
(double)0.01m 0.01 double
UPDATE
Ok, I'm reproducing the issue as follows:
When I run WPF application and check the output in watch just after it started I get true like on empty project.
There is a part of application that sends values from the slider to the calculation algorithm. I get wrong result and I put breakpoint on the calculation method. Now, when I check the value in watch window I get false (without any modifications, I just refresh watch window).
As soon as I reproduce the issue in some smaller project I will post it here.
UPDATE2
Unfortunately, I cannot reproduce the issue in smaller project. I think that Eric's answer explains why.
People are reporting in the comments here that sometimes the result of the comparison is true and sometimes it is false.
Unfortunately, this is to be expected. The C# compiler, the jitter and the CPU are all permitted to perform arithmetic on doubles in more than 64 bit double precision, as they see fit. This means that sometimes the results of what looks like "the same" computation can be done in 64 bit precision in one calculation, 80 or 128 bit precision in another calculation, and the two results might differ in their last bit.
Let me make sure that you understand what I mean by "as they see fit". You can get different results for any reason whatsoever. You can get different results in debug and retail. You can get different results if you make the compiler do the computation in constants and if you make the runtime do the computation at runtime. You can get different results when the debugger is running. You can get different results in the runtime and the debugger's expression evaluator. Any reason whatsoever. Double arithmetic is inherently unreliable. This is due to the design of the floating point chip; double arithmetic on these chips cannot be made more repeatable without a considerable performance penalty.
For this and other reasons you should almost never compare two doubles for exact equality. Rather, subtract the doubles, and see if the absolute value of the difference is smaller than a reasonable bound.
Moreover, it is important that you understand why rounding a double to two decimal places is a difficult thing to do. A non-zero, finite double is a number of the form (1 + f) x 2e where f is a fraction with a denominator that is a power of two, and e is an exponent. Clearly it is not possible to represent 0.01 in that form, because there is no way to get a denominator equal to a power of ten out of a denominator equal to a power of two.
The double 0.01 is actually the binary number 1.0100011110101110000101000111101011100001010001111011 x 2-7, which in decimal is 0.01000000000000000020816681711721685132943093776702880859375. That is the closest you can possibly get to 0.01 in a double. If you need to represent exactly that value then use decimal. That's why its called decimal.
Incidentally, I have answered variations on this question many times on StackOverflow. For example:
Why differs floating-point precision in C# when separated by parantheses and when separated by statements?
Also, if you need to "take apart" a double to see what its bits are, this handy code that I whipped up a while back is quite useful. It requires that you install Solver Foundation, but that's a free download.
http://ericlippert.com/2011/02/17/looking-inside-a-double/
This is documented behavior. The decimal data type is more precise than the double type. So when you convert from decimal to double there is the possibility of data loss. This is why you are required to do an explicit conversion of the type.
See the following MSDN C# references for more information:
decimal data type: http://msdn.microsoft.com/en-us/library/364x0z75(v=vs.110).aspx
double data type: http://msdn.microsoft.com/en-us/library/678hzkk9(v=vs.110).aspx
casting and type conversion: http://msdn.microsoft.com/en-us/library/ms173105.aspx

How to combine float representation with discontinous function?

I have read tons of things about floating error, and floating approximation, and all that.
The thing is : I never read an answer to a real world problem. And today, I came across a real world problem. And this is really bad, and I really don't know how to escape.
Take a look at this example :
[TestMethod]
public void TestMethod1()
{
float t1 = 8460.32F;
float t2 = 5990;
var x = t1 - t2;
var y = F(x);
Assert.AreEqual(x, y);
}
float F(float x)
{
if (x <= 2470.32F) { return x; }
else { return -x; }
}
x is supposed to be 2470.32. But in fact, due to rounding error, its value is 2470.32031.
Most of the time, this is not a problem. Functions are continuous, and all is good, the result is off by a little value.
But here, we have a discontinous function, and the error is really, really big. The test failed exactly on the discontinuous point.
How could I handle the rounding error with discontinuous functions?
The key problem here is:
The function has a large (and significant) change in output value in certain cases when there is a small change in input value.
You are passing an incorrect input value to the function.
As you write, “due to rounding error, [x’s value] is 2470.32031”. Suppose you could write any code you desire—simply describe the function to be performed, and a team of expert programmers will provide complete, bug-free source code within seconds. What would you tell them?
The problem you are posing is, “I am going to pass a wrong value, 2470.32031, to this function. I want it to know that the correct value is something else and to provide the result for the correct value, which I did not pass, instead of the incorrect value, which I did pass.”
In general, that problem is impossible to solve, because it is impossible to distinguish when 2470.32031 is passed to the function but 2470.32 is intended from when 2470.32031 is passed to the function and 2470.32031 is intended. You cannot expect a computer to read your mind. When you pass incorrect input, you cannot expect correct output.
What this tells us is that no solution inside of the function F is possible. Therefore, we must zoom out and look at the larger problem. You must examine whether the value passed to F can be improved (calculated in a better way or with higher precision or with supplementary information) or whether the nature of the problem is such that, when 2470.32031 is passed, 2470.32 is always intended, so that this knowledge can be incorporated into F.
NOTE: this answer is essentially the same as the one of Eric
It just enlighten the test point of view, since a test is a form of specification.
The problem here is that testMethod1 does not test F.
It rather tests that conversion of decimal quantity 8460.32 to float and float subtraction are inexact.
But is it the intention of the test?
All you can say is that in certain bad conditions (near discontinuity), a small error on input will result in a large error on output, so the test could express that it is an expected result.
Note that function F is almost perfect, except maybe for the float value 2470.32F itself.
Indeed, the floating point approximation will round the decimal by excess (1/3200 exactly).
So the answer should be:
Assert.AreEqual(F(2470.32F), -2470.32F); /* because 2470.32F exceed the decimal 2470.32 */
If you want to test such low level requirements, you'll need a library with high (arbitrary/infinite) precision to perform the tests.
If you can't afford such imprecision on function F, then Float is a mismatch., and you'll have to find another implementation with increased, arbitrary or infinite precision.
It's up to you to specify your needs, and testMethod1 shall explicit this specification better than it does right now.
If you need the 8460.32 number to be exactly that without rounding error, you could look at the .NET Decimal type which was created explicitly to represent base 10 fractional numbers without rounding error. How they perform that magic is beyond me.
Now, I realize this may be impractical for you to do because the float presumably comes from somewhere and refactoring it to Decimal type could be way too much to do, but if you need it to have that much precision for the discontinuous function that relies on that value you'll either need a more precise type or some mathematical trickery. Perhaps there is some way to always ensure that a float is created that has rounding error such that it's always less than the actual number? I'm not sure if such a thing exists but it should also solve your issue.
You have three numbers represented in your application, you have accepted imprecision in each of them by representing them as floats.
So I think you can reasonably claim that your program is working correctly
(oneNumber +/- some imprecision ) - (another number +/- some imprecision)
is not quite bigger than another number +/- some imprecision
when viewed in decimal representation on paper it looks wrong but that's not what you've implemented. What's the origin of the data? How precisely was 8460.32 known? Had it been 8460.31999 what should have happened? 8460.32001? Was the original value known to such precision?
In the end if you want to model more accuracy use a different data type, as suggested elsewhere.
I always just assume that when comparing floating point values a small margin of error is needed because of rounding issues. In your case, this would most likely mean choosing values in your test method that aren't quite so stringent--e.g., define a very small error constant and subtract that value from x. Here's a SO question that relates to this.
Edit to better address the concluding question: Presumably it doesn't matter what the function outputs on the discontinuity exactly, so test just slightly on either side of it. If it does matter, then really about the best you can do is allow either of two outputs from the function at that point.

.NET some values multipled by large factor on some machines not others

I have a rather strange problem. I have a very simple application that reads some data from a csv formatted file, and draws a polar 'butterfly' to a form. However a few people in european countries get a very wierd looking curve instead, and when I modified the program to output some sample values to try and workout what is going on, it only gave me more questions!
Here is a sample of expected values, and what one particular user gets instead:
EXPECTED -> SEEN
0.00 0.00 -> 0,00 0,00
5.00 1.35 -> 5,00 1346431626488,41
10.00 2.69 -> 10,00 2690532522738,65
So all the values on the right (which are computed in my program) are multiplied by a factor of 10^12!! How on earth can that happen in the CLR? the first numbers - 0, 5, 10 - are just produced by the simple loop that writes the output, using: value += 5.
The code producing these computations does make use of interpolation using the alglib.net library, but the problem does also occur with 2 other values that are extracted from xml returned from a http get, and then converted from radians to degrees.
Also not exactly a problem, but why would decimal values print with commas instead of decimal points? The output code is a simple string.Format("{0:F}", value) where value is a double?
So why on earth would some values be shifted by 12 decimal places, but not others, and only in some countries? Yes others have run the app with no problems... Not sure if there is any relevance but this output came from Netherlands.
Different cultures use different thousands and decimal separators. en-US (US English) uses "," and "." but de-DE (German German) uses "." and ",". This means that when reading from or writing to strings you need to use the proper culture. When persisting information for later retrieval that generally means CultureInfo.InvariantCulture. When displaying information to the user that generally means CultureInfo.CurrentCulture.
You haven't provided the code that reads from the CSV file, but I imagine you're doing something like double.Parse(field) for each field. If the field has the value "5.0" and you parse it when the current culture is de-DE "." will be considered a thousands separator and the value gets read as 50.0 in en-US terms. What you should be doing is double.Parse(field, CultureInfo.InvariantCulture).
All of the Parse, TryParse, Format, and many ToString methods accept an IFormatProvider. Get in the habit of always providing the appropriate format provider and you wont get bitten by internationalization issues.
My personal guess would be that you have a string -> Number conversion somewhere that is not culture aware at all.
Why oh simple run this code :
var nl = System.Globalization.CultureInfo.GetCultureInfo("nl-NL");
var numberString = "1.000000000000000";
Console.WriteLine(float.Parse(numberString, nl));
The result is 1E+15 now you just have to find the places where you need to provide the CultureInfo.InvariantCulture (Simplified english, equivalent to the "C" culture in C) to Parse along with the string.
In some languages a decimal comma is used instead of the decimal point. This depends on the culture. You can force your own culture if it's important to you that only points are used.
One interesting thing of note is that if 1346431626488 were divided by 1,000,000,000,000, then you would get 1.35 rounded to two decimal places. And if 2.69 were divided by 1,000,000,000,000 then you would get 2.69 rounded to two decimal places. Just an observation.

Categories

Resources