0

I can't find a clear, simple answer to a very simple question! I'll split my question into two versions.

In C#, suppose I run the following:

double x = 76239.78362194721;
double y = -3;
y = x;  // copied by value, as doubles are literals
//y = y + 1.4;  // Version 1 of my question has these commented-out
//x = x + 1.4;  // Version 2 of my question has these not commented-out
bool b = (x == y);

will the boolean b always be true? That is, although equality measures between doubles are inaccurate, will the computer never add noise on purpose?

And can b depend on whether I'm asking version 1 or version 2 of my question? (We can assume that x and y are not near the max or min values of double, nor are they close to plus/minus epsilon).

thanks! ~Greg

Gregory Fenn
  • 460
  • 2
  • 13
  • See also [Is floating point arithmetic stable](https://stackoverflow.com/questions/48384335/is-floating-point-arithmetic-stable), a duplicate question that matches your question a little more closely. – hatchet - done with SOverflow Oct 23 '18 at 16:21
  • In short: *any* double computation can be done in *any* level of precision of 64 bits *or higher* at *any* time for *any* reason, and this can affect equality. C# is only required to "round off" to 64 bits upon assignment to a field or array element; locals and temporaries can move in and out of 80 or 128 bit precision at the whim of the jitter. Yes, this is terrible, and you should assign the blame to the chip designers who made chips where it is more expensive to do deterministic math. – Eric Lippert Oct 23 '18 at 17:00

1 Answers1

-1

Copying with y = x; does not cause any precision loss.

After the assignment, x == y is true.

Since x and y are equal, after

y = y + 1.4;
x = x + 1.4;

x == y is most likely true; however, as Eric Lippert pointed out (see comments below), this is not guaranteed.

Always compare doubles with a desired precision:

const double Eps = 1e-10;
if (Math.Abs(x - y) < Eps) {
    // x and y are equal enough
} else {
    // x and y are not equal
}

Of course, if you comment out only one of these, x and y will differ by approximately 1.4. They might differ by 1.399999999999999 or 1.400000000000001 or so. If both are very big (e.g. 1.0e25) the addition will have no effect, as it would affect decimals that are truncated. double has a precision of 15-16 digits. The same is true, if both are very small (e.g. 1.0e-25), because then their original values will be lost and the result will be 1.4.

In the C# Interactive window you can test that

> 1e+25 + 1.4 == 1e+25
true

and that

> 1e-25 + 1.4 == 1.4
true

... copied by value, as doubles are literals

Well, not quite. System.Double (C# alias double) is a value type. 3.141592654 is a double literal. In double x; the identifier x is a double variable (or field if it is declared at the type level).

But even if you copy ...

var p1 = new Person { Name = "John" }; // Where Person is a class.
var p2 = p1;
// Now, p1 and p2 reference the same object. I.e. they have the same value.

... the value of the variable is copied by value. The difference is that this value is a reference. The value is not a person object but a reference to it.

p2.Name = "Mark";

Now p1.Name == "Mark" is true, but the value of p1 did not change. It is still the same reference. The referenced object did change.


double d1 = 5;
double d2 = d1;
d2 = 77;

d1 is still 5. Because double is a value type, the variables contain the numbers directly. No references are involved.


In this other example the reference is passed by reference

var p1 = new Person{ Name = "John" };
AssignAnotherPerson(ref p1);

void AssignAnotherPerson(ref Person p)
{
    p = new Person{ Name = "Sue" }; // Here p is an alias for p1.
}

After this method is called, p1 will contain the person Sue.

You can have four cases: (1) A value type passed by value. (2) A value type passed by reference. (3) A reference type passed by value. (4) A reference type passed by reference.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • 1
    Note that if x or y is a NAN than the == operator will evaluate to false even with x==x. – TommyD Oct 23 '18 at 16:35
  • 1
    Sadly, this answer is wrong. C# is not required to make any two computations happen with the same level of precision, and that can affect equality. – Eric Lippert Oct 23 '18 at 16:52
  • @EricLippert: This is very suprising! Can you explain this? – Olivier Jacot-Descombes Oct 23 '18 at 17:19
  • 1
    This question has been closed as a duplicate; see the duplicate questions for details. The short version is: floating point *registers* are permitted to be 80 or 128 or whatever bits but fields and stack slots are guaranteed to be only 64 bits, and C# is permitted to move floats in and out of registers however the jitter feels is most performant. This means that one computation can be done in 128 bit arithmetic and another in 64 bit arithmetic, and they need not be equal when compared in 128 bits. In *practice* this does not happen a lot, but it *can* happen. – Eric Lippert Oct 23 '18 at 17:23
  • 1
    For example, there was a question a few years back about why some computation like `double x = 123.45 + 7.89; double y = 123.45; double z = 7.89; bool b = x == y + z;` had `b` equal to `false`, and the answer was that the C# *compiler* did the addition to do the assignment to `x` but the *runtime* did the addition in the comparison, and they did them at different levels of precision. That one was a pain to investigate. – Eric Lippert Oct 23 '18 at 17:26
  • 1
    The easiest way to demonstrate the issue is to do a complicated floating point arithmetic operation, but run the program twice, once in release mode with optimizations on, and once in debug mode with optimizations off. The compiler and jitter turn off many of the optimizations that move floating point computations into registers when optimizations are off, and so you're most likely to see a difference between the debug and release versions. That's the considerable bulk of the cases that were reported to the compiler team, and on SO. – Eric Lippert Oct 23 '18 at 17:33