Q:

Round vs. Floor vs. Ceiling for non-integers non-orders-of-magnitude

I understand that Round give the nearest even integer for cases where the number is between two integers, i.e. Round[2.5] = 2 and Round[3.5] = 4 (see this question). This carries over to rounding to non-integers that are not orders of magnitude as well:
Round[1.3, 0.2] = 1.2
Round[1.5, 0.2] = 1.6

Round[Range[1, 2, 0.1], 0.2] = {1., 1.2, 1.2, 1.2, 1.4, 1.6, 1.6, 1.6, 1.8, 1.8, 2.}

However, the Floor function seems to behave oddly in a similar situation:
Floor[Range[1, 2, 0.1], 0.2] = {1., 1., 1., 1.2, 1.2, 1.4, 1.6, 1.6, 1.8, 1.8, 2.}

but Ceiling does what I would expect it to do:
Ceiling[Range[1, 2, 0.1], 0.2] = {1., 1.2, 1.2, 1.4, 1.4, 1.6, 1.6, 1.8, 1.8, 2., 2.}

My question therefore is why does Floor[1.2, 0.2] = 1.? I understand the behavior of Round for this test set, but I do not understand Floor. Then, with Floor giving something I don't expect, I am very surprised that Ceiling gives exactly what I would expect.

Background for my Problem
I have a regular array of 2D data ({x, y, probability}) that I want to bin by summing, so Round doesn't work because different numbers of elements are included in consecutive bins because it rounds to evens, but I expected Floor to work.
gathered = GatherBy[predictedProbabilityDist,
   { Round[#[[1]], newGridSpacing],
     Round[#[[2]], newGridSpacing] } &];

versus
gathered = GatherBy[predictedProbabilityDist,
   { Floor[#[[1]], newGridSpacing],
     Floor[#[[2]], newGridSpacing] } &];

or the nearly equivalent expression with Ceiling, followed by something like
predictedProbDist = Map[
   { Min[#[[All, 1]]],
     Min[#[[All, 2]]],
     Total[#[[All, 3]]] } &,
   gathered];

Based on the testing with the simple Range above, it looks like I need to use Ceiling, but I don't know why for sure that is the case.

A:

Welcome in the amazing world of machine precision arithmetic!
If you examine the binary representation of both numbers you see the following:
RealDigits[1.2, 2]
(* {{1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
   1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 
  0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1}, 1} *)

RealDigits[.2, 2]
(* {{1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
   1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 
  0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0}, -2} *)

The .2 part is represented by an infinite series of 0,0,1,1,.. that necessarily has to be truncated to fit into the allocated space. Since the fractional part has slightly more space in the representation of 0.2 than in 1.2 (because of the missing integer part) it is therefore slightly larger than the fractional part of 1.2 and therefore 1.2 is seen as smaller than a multiple of 0.2. Compare also
Floor[1.20000000000000001, .2]
(* 1. *)

and
Floor[1.200000000000001, .2]
(* 1.2 *).

Using arbitrary precison arithmetic you can make the representation of the bit series in 1.2 longer than in 0.2 and the result is now:
Floor[1.2`200, .2`10]
(* 1.200000000 *)

A:

Sjoerd C. de Vries correctly diagnoses the problem and provides a resolution.  An alternative is to work in exact numbers and convert to floating point at the end.
Floor[Range[1, 2, 1/10], 2/10]
(* Output:  {1, 1, 6/5, 6/5, 7/5, 7/5, 8/5, 8/5, 9/5, 9/5, 2} *)

N[Floor[Range[1, 2, 1/10], 2/10]]
(* Output:  {1., 1., 1.2, 1.2, 1.4, 1.4, 1.6, 1.6, 1.8, 1.8, 2.} *)

Note that you may still have trouble with your binning at one or both endpoints of your range.
Alternatively, since you have a (full?) regular grid, you may be happier using Partition[] since it does what you want structurally instead of by means of comparisons.  (The following is in no sense optimal.  For instance, if your {x,y,prob} triples are sorted in y and then by x, partitioning [[All,3]] would give you the dataTable directly.)
inData = (* linear array of {x,y,probability} triples *)
((intermediateData[#[[1]], #[[2]]] = #[[3]]) &) /@ inData
dataTable = Table[intermediateData[x,y],{x,(* min x *), (* max x *)}, {y, (* min y *), (* max y *)}]  (* the minimum and maximum values can be extracted from inData or just supplied by hand. *)
Total[Flatten[#]] & /@ # & /@ Partition[dataTable, {(* columns per grid *), (* rows per grid *)}]

for instance (my random numbers might not match your random numbers), with a 10x10 initial grid and 5x5 cells...
inData = Flatten[Table[{x, y, Random[]}, {x, 1, 10}, {y, 1, 10}], 1]
((intermediateData[#[[1]], #[[2]]] = #[[3]]) &) /@ inData
dataTable = Table[intermediateData[x, y], {x, 1, 10}, {y, 1, 10}]
Total[Flatten[#]] & /@ # & /@ Partition[dataTable, {5, 5}]
(* last Output:  {{12.0149, 15.3413}, {12.7288, 11.8542}} *)

Partition[] will provide partial cells at the upper ends of the x and y ranges if the cell size does not exactly divide the number of rows or columns.

