1

Is it save to compare the result of Math.Round(double,int) with == and use it for example as the key of a HashSet<Double> or a GroupBy(d=>Math.Round(d,1))?

In other words, are there any doubles x and y for which the following assertion will fail?

double x = ...;
double y = ...;
double xRound = Math.Round(x, 1);
double yRound = Math.Round(y, 1);

Debug.Assert(xRound==yRound || Math.Abs(xRound-yRound)>=0.1);

Let's say that I would like to group a list of doubles:

List<double> values = ...;
List<double> keys = values.GroupBy(d=>Math.Round(d,1)).Select(kv=>kv.Key).ToList();

Is there a chance that I would get a key with the value 0.100000000 and another key with the value 0.09999999999?

(I tried parsing the disassembled net framework Math.cs source, but Round() eventually calls a native function.)

HugoRune
  • 13,157
  • 7
  • 69
  • 144
  • 1
    Using a `double` as a key to a collection is always dubious. Is there a reason you have to use a floating point number as your key? If you only care about the first decimal place I would rather multiply by 10 then round and cast to a `int`. – Scott Chamberlain May 16 '14 at 13:41
  • I'd use `decimal`: `Decimal d = Math.Round((Decimal)x, 1);` – Tim Schmelter May 16 '14 at 13:43
  • you may find this link useful jon Skeet explains round, personally I would not trust round in .net for a hash. http://stackoverflow.com/questions/977796/why-does-math-round2-5-return-2-instead-of-3-in-c – Bit May 16 '14 at 13:45
  • @Scott Using a double has the advantage that I can later use the numbers without remembering the conversion factor. For example if I have a distance in meter and want to group these distances to whole centimeters, I would prefer for the key to have the same unit as the values. – HugoRune May 16 '14 at 13:56
  • *Nothing* involving double is reliable. Consider using `decimal` everywhere, which is reproducible. – CodesInChaos May 16 '14 at 14:29
  • 1
    I really like this question. I would *hope* that anything that theoretically rounds to, say, `0.1` will result in `Math.Round(..., 1)` giving the exact same double that `0.1d` does... but does it? – Rawling May 16 '14 at 14:42
  • @CodesInChaos Decimal is **[not a fixed point format](http://stackoverflow.com/questions/5069750/net-why-is-there-no-fixed-point-numeric-data-type-in-c)**, it is just a floating point format with more precision than double. Given these conceptual similarities between float, double and decimal, I presume it will have the same problems with Math.Round, if any. Do you have evidence to the contrary? – HugoRune May 16 '14 at 14:47
  • 1
    @HugoRune Decimal is a floating point format, but it is a *decimal* floating point format. – Rawling May 16 '14 at 14:51
  • @Rawling I do not yet see how being a decimal floating point format will guarantee that decimal can be used safely as a hash situations, whereas Double cannot, an answer detailing that would be useful to me. Nevertheless I would very much like to clear first whether *double* exhibits this particular problem in regards to Math.Round. Are there any sample values that exhibit this problem? – HugoRune May 16 '14 at 15:35
  • By being a decimal floating point value, `decimal` can represent any real value rounded to any number of decimal places (within reason) *precisely*, and so we can reasonably expect that `decimal.Round` will return the expected value. On the other hand, `double` cannot even represent `0.1` precisely, and so it's reasonable to ask "does `double.Round(0.09, 1)` necessarily return the same imprecise `double` representation of `0.1` as `double.Round(0.11, 1)` does?". – Rawling May 16 '14 at 15:51
  • I found [this question](http://stackoverflow.com/questions/1832335/does-math-rounddouble-decimal-always-return-consistent-results) which asks something similar but uses `ToString("Nx")` rather than `GetHashCode()` as a measure of equality, and as far as I can tell the answer given reveals a flaw with `ToString` rather than with `Round`. – Rawling May 16 '14 at 15:52
  • 1
    The brute force approach mentioned in that answer seems like a reasonable solution. I am currently running a program to try all possible values, however I may have to do some more optimisations so that it will finish in a reasonable time – HugoRune May 16 '14 at 16:41

1 Answers1

1

Generally, given the same starting value and the same operations, the same result would be obtained.

However, if you (for example) did:

double d1 = 10;
d1 /= 0.1;
double d2 = 25;
d2 /= 0.25;

then you may well find that d1 and d2 do not have the same value (because IEEE754 can represent 0.25 exactly but not 0.1.

So, given that and the rather large number of issues people seem to have with floating point, I'd say your best bet would be to choose a different method of hashing.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953