-1

I use a dictionary to store price and volume info of a chart. Price is used as Key and a class containing all the volume info as Value. For some reason, when using a for loop with calculated price steps (taking the highest price and subtracting it with each iteration by the step size and then using Math.Round() to make sure it doesn't have unwanted decimals), it appears that the dictionary can't find some price levels / Keys which are actually existent. Then it creates a duplicate Key according to my code. Just as if the price Key, a double, would have invisible decimals. With a foreach loop, though, there is no problem, apart from that in some cases it has to be a for loop. Here is some code

double highestTick = Math.Round(ChartPanel.Y);
double lowestTick = Math.Round(ChartPanel.Y + ChartPanel.H);
for (double i = highestTick; i >= lowestTick; i -= TickSize)
    double price = RoundToTickSize(i);
    if (!traderLevelYDict.ContainsKey(price))
    {
        traderLevelYDict.Add(price, new volClass{});
    }
    else { ...... }

Where RoundToTickSize() is simply:

public double RoundToTickSize(double x)
{
   return Math.Round(x / TickSize, MidpointRounding.ToEven) * TickSize;   // TickSize is < 1
}

And in the end, I end up with a duplicate Key in the dictionary, as you can see in the picture. Note that there isn't an empty space or so behind the price, and that I don't round the value before printing.

Duplicate Keys (price) in Dictionary

Any idea why it does it, and how to stop it? I appreciate all answers!

FtoB
  • 25
  • 6
  • 1
    is function `RoundToTickSize` reliable/stable? – Lei Yang Feb 16 '22 at 08:31
  • 1
    Can you show the creation of traderLevelYDict? – sr28 Feb 16 '22 at 08:32
  • Possibly due to rounding errors in the calculation of the `double` key. – Matthew Watson Feb 16 '22 at 08:33
  • `double` should not be used as a key in a dictionary, as it is prone to rounding errors. You'll almost never get two absolutely identical values for a double after a multiplication or division. Since double is also not a good choice for a price, I'd suggest changing your key type to `decimal` instead. – PMF Feb 16 '22 at 08:36
  • Could you print double key with more decimal places? – ElConrado Feb 16 '22 at 08:41
  • You call `double price = RoundToTickSize(i)`, but check `if (!traderLevelYDict.ContainsKey(i))`, which is not rounded. (Your dictionary most definitely doesn't contain duplicate keys, but if you always display the key value as a rounded number when printing its content, it may look like it does.) – Astrid E. Feb 16 '22 at 08:49
  • @sr28 This is how I create the Dict: `SortedDictionary> traderLevelYDict = new SortedDictionary>(new DecendingKeyComparer());` @PMF and with a decimal you can get absolute identical values? I will give it a try anyways. @ElConrado I didn't round the Key before printing it. before I applied the rounding it looked like 1895.00000002 @AstridE. You're right. That was a careless mistake when writing the post. In my code it's correct. I didn't round the Key before printing it. – FtoB Feb 16 '22 at 09:13
  • 1
    @FtoB Okay, that happens easily! I agree with @ElConrado; printing the `double` keys with all decimal places displaying may uncover that they aren't duplicates after all. – Astrid E. Feb 16 '22 at 09:33
  • @AstridE. How can I print them with all decimal places when they are printed like this: `Print("price: " + price);` – FtoB Feb 16 '22 at 09:46
  • @FtoB Perhaps you could print all dictionary keys at some point in your code after the dictionary has been populated, to get an overview? E.g. like this: `Console.WriteLine(string.Join(Environment.NewLine, traderLevelYDict.Keys));`. – Astrid E. Feb 16 '22 at 09:52
  • The point I wanted to make is that I don't round the values before printing them. It prints them as they are. @PMF I wrote a test script and tried the difference between double and decimal, and in fact using decimal instead of double solves the problem! Now I'll rewrite my main script and lets see. Thanks!!! to you all – FtoB Feb 16 '22 at 10:03
  • @FtoB I understand that, but (1) We don't know _where_ you print `price`, which means `price` _could_ be a rounded value at the time of printing; and (2) We don't know anything about the value of `TickSize` other than your comment `// TickSize is < 1`. `< 1` could mean `0.871432` for all we know, which would lead to `RoundToTickSize()` returning a value with more than one decimal place. None of it is shown in the code you have provided. That's why we ask, we cannot just take your word for it without seeing more of your code, when trying to help. Glad you seem to have found a solution, though. – Astrid E. Feb 16 '22 at 10:15
  • @AstridE. I understand. That makes sense, you can't know. I wanted to keep it short and not post 100 lines of code. TickSize has at maximum 2 decimal places. I print price directly by using the Key itself as variable. From here comes the screenshot: `foreach (var levelPair in traderLevelYDict) { List levelList = levelPair.Value; Print("Level: " + levelPair.Key);` I tested the exact same code once with foreach and once with for loop. The only difference was the loop and `item.Value` instead of the `dict[price]` – FtoB Feb 16 '22 at 12:11
  • Using decimals instead of doubles turned out to not change anything. Also in the test script not. Before seemed like but in the end it didn't. – FtoB Feb 16 '22 at 12:12
  • Actually using a double as Dictionary key is a bad idea, If I were you, I would use an int based on Price(rounded to 2 decimals) * 100 (or the number of decimals you want) – J.Salas Feb 16 '22 at 13:15
  • This seems to be indeed a solution. Many thanks! Just wondering why can't I use double? If it is rounded to the exact same value, proven by printing the variable and key itself, only the possibility of additional decimal places shouldn't hinder it, should it? – FtoB Feb 16 '22 at 14:17
  • I don't know the mechanisms behind `double` type well enough myself, but there are some insights in [this post's answers](https://stackoverflow.com/questions/9230393/best-collection-to-use-with-type-double-as-key). – Astrid E. Feb 16 '22 at 16:57
  • 1
    Thank you @AstridE. for the link and support. The final result is that double seems to be really imprecise. Why I couldn't find. – FtoB Feb 16 '22 at 18:31

1 Answers1

0

Thanks to great help, I found out that double is really imprecise and might carry decimal places that won't show up because of internal rounding errors. If you google for something like "issue with double error" or "double precision issue", you will find many who tried to solve it by writing custom methods to round the doubles by parsing them or the like. Many suggest to use decimal instead. In my case, the single one thing that worked is converting the price / key double to an integer and then dividing it by the number of decimal places when needed.

Here a Wikipedia link about the why.

FtoB
  • 25
  • 6
  • Just take care to not add unnecessary zeros to the `int` otherwise it may cause again a calculation error. – FtoB Feb 17 '22 at 12:57