From: Somebody on
I have a GUI something like this:

<column splitter> <data> < column splitter> < data > etc.

Each column has a vertical splitter where the user can adjust the columns.

One of the requirements was that when the user sizes the app, the columns
should scale accordingly.

For example... say the app was 100 pixels wide and column A was at 25,
column B at 50 and column C at 75... if the app got resized to 200 pixels
wide, column A would now be at 50, B at 100 and C at 150.

Since a column could be divided arbitrarily by the user (horizontally and
vertically), we implemented a tree structure that would hold all this
information. We used an "int nRatio" design. So for example, an even split,
the left node would get a nRatio=50 and the right node would get a
nRatio=50.

As we went along with the implementation, we found that we would get the
splitters jumping around by a few pixels here and there. Debugging showed
round off errors with our division. So we changed "int nRatio" to "float
fRatio". That improved a bit, but we would still get cases where the
splitters jumped around by a few pixels, so we tried "double dRatio". Still
same problem. Finally, we ended up multiplying the numerator by 10,000 to
get more decimal precision.

That seemed to work, but then we got a bug opened where in following
example:

A | B | C | D

moving splitter A|B or B|C would cause splitters to the right to move. Think
about it: A|B was 50%, B|C was 50%, C|D was 50%... so moving A|B without
adjusting the ratios for B|C and C|D would cause them to scale as if the app
was being resized :(.

We came up with a solution where if I moved A|B, we would recalculate the
ratios for the other splitters to keep "rectSplitter" in the same place...

Oops... you guessed it, round off errors popped up, even with it multiplied
by 10,000.

Our whole layout algorithms work based on this "left node gets 50%, right
node gets 50%" concept, so changing to something else might be a huge
re-write.

I understand that I can not have "fractional" pixels though. We need to
somehow recalculate the ratios because we can't layout by saying "make the
splitter appear at x,y" since as I said, its based around this ratio
calculations.

We could try bumping up our multiplier to something bigger then 10,000 like
100,000 or 1,000,000, but I'm concerned we'll always get round off errors.

Any ideas?


From: Joseph M. Newcomer on
See below...
On Sat, 17 May 2008 21:14:51 -0700, "Somebody" <somebody(a)cox.net> wrote:

>I have a GUI something like this:
>
><column splitter> <data> < column splitter> < data > etc.
>
>Each column has a vertical splitter where the user can adjust the columns.
>
>One of the requirements was that when the user sizes the app, the columns
>should scale accordingly.
>
>For example... say the app was 100 pixels wide and column A was at 25,
>column B at 50 and column C at 75... if the app got resized to 200 pixels
>wide, column A would now be at 50, B at 100 and C at 150.
>
>Since a column could be divided arbitrarily by the user (horizontally and
>vertically), we implemented a tree structure that would hold all this
>information. We used an "int nRatio" design. So for example, an even split,
>the left node would get a nRatio=50 and the right node would get a
>nRatio=50.
>
>As we went along with the implementation, we found that we would get the
>splitters jumping around by a few pixels here and there. Debugging showed
>round off errors with our division. So we changed "int nRatio" to "float
>fRatio". That improved a bit, but we would still get cases where the
>splitters jumped around by a few pixels, so we tried "double dRatio". Still
>same problem. Finally, we ended up multiplying the numerator by 10,000 to
>get more decimal precision.
****
Yes, this is typical of using integers. Double will not change the phenomenon, because
you are essentially working at about 1 decimal place, and going from 8 to 15 decimal
places isn't going to fix your problem.

Key here is that you need to keep the ratios strictly as percentages. If you are using
uniform spacing, this is easy. Otherwise, when the user resizes, you need to compute the
ratios of the column size to client area size, for example 0.25, 0.50, 0.25 (if the user
wants a wide column in the middle). Then you will always resize based on these ratios and
the client area. If the user resizes the columns (changes their ratio, adds columns,
etc.) then you compute new ratios. But at no point do you remember column sizes in
pixels, only in percentages. Otherwise, your use of flating point has created no real
additional precision.
****
>
>That seemed to work, but then we got a bug opened where in following
>example:
>
>A | B | C | D
>
>moving splitter A|B or B|C would cause splitters to the right to move. Think
>about it: A|B was 50%, B|C was 50%, C|D was 50%... so moving A|B without
>adjusting the ratios for B|C and C|D would cause them to scale as if the app
>was being resized :(.
****
You are confusing relative widths with absolute widths. You need to compute the position
from the left margin, and round THAT value to a pixel position. If you round to pixels
and add the pixels, you're back doing integer arithmetic. So if the widths are

0.25, 0.50, 0.25

then the first width would be 0.25*client.Width(), the second width would be
0.75 * client.Width() - 0.25 * client.Width(), and the third width would be 1.00 *
client.Width() - (0.75 * client.Width()). Otherwise, the "jumping" you are seeing is an
expected phenomenon, caused by roundoff when you compute the x position of the right of
the column as based on the computation in floating point plus the x position of the left
of the column (because truncation or rounding would change the left edge +/- 1 pixel, and
this error would then accumulate across the columns). You should expect, and cannot
prevent, +/- 1 pixel errors, the trick is to keep them from being cumulative.
****
>
>We came up with a solution where if I moved A|B, we would recalculate the
>ratios for the other splitters to keep "rectSplitter" in the same place...
>
>Oops... you guessed it, round off errors popped up, even with it multiplied
>by 10,000.
>
>Our whole layout algorithms work based on this "left node gets 50%, right
>node gets 50%" concept, so changing to something else might be a huge
>re-write.
****
Well, as soon as you convert to pixels, you lose precision, so as you move up the tree,
the 1-pixel errors accumulate. You have to propagate positions in floating point upwards,
and don't do roundoff until final rendering. Remember that every time you have an odd
number of pixels involved, one of the computations will always be off-by-1 if you truncate
or round (it doesn't matter which).
****
>
>I understand that I can not have "fractional" pixels though. We need to
>somehow recalculate the ratios because we can't layout by saying "make the
>splitter appear at x,y" since as I said, its based around this ratio
>calculations.
****
Yes, you keep fractional pixels in your tree; only when you go to render them will you
truncate or round to integers. This way, errors do not accumulate as you move up the
tree, and your maximum error will be +/- 1 pixel. You could provide some heuristic based
on tree depth to decide whether to truncate or round, or something like that, but I think
the problem is just cumulative roundoff errors.
****
>
>We could try bumping up our multiplier to something bigger then 10,000 like
>100,000 or 1,000,000, but I'm concerned we'll always get round off errors.
****
You will always get roundoff errors. The trick is to keep them from being cumulative. So
if two columns end up at 38 1/4 pixels wide, it doesn't matter; they will both be 38, but
there's a 1/2-pixel width that propagates upward. Eventually, these will cancel out.

I had problems like this in PostScript layout until I understood how to avoid cumulative
pixel errors.
joe
****
>
>Any ideas?
>
Joseph M. Newcomer [MVP]
email: newcomer(a)flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
From: Somebody on
Thanks for the feedback Joe...

Unfortunately, I can not calculate from the top/left corner because there is
no such concept in my UI. A "cell" knows nothing about its surrounding
cells, or where its located, etc.

The concept is:

start with rect A
split rect A horizontally OR vertically by x% to form rects A/B
split rect B horizontally OR vertically by x% to form rects B/C
etc.

Ie... a rect can only be split once in a single direction, but I can split
the split rectangles to form nested cells.

So in the above example:

start with rect A
split rect A horizontally by 50% to form rects A/B
split rect B horizontally by 50% to form rects B/C

So now... A|B's ratio is 50%, but so is B|C because its a recursive thing:

root is 100 pixels wide
A gets 50% of that... A's right node which is B|C gets 50%
B gets 50% of what A passed down (essentially 25% of the full width) and C
gets the other 50%...

The problem occurs if say, I move to pixel x = 100 and the width is 300. By
storing the ratio, I'll *never* recover 100 exactly.

So I'm thinking, instead of storing the ratio as a float, why not store the
numerator and denomenator as ints?

100 / 300 = 0.33
..33 * 300 = 99 (OFF BY 1 PIXEL)

BUT...

100 * 300 / 300 = 100 (exact)...

Sound like a reasonable idea?

"Joseph M. Newcomer" <newcomer(a)flounder.com> wrote in message
news:gibv2411fcurfaugkmod3gmi3sbcitj2dq(a)4ax.com...
> See below...
> On Sat, 17 May 2008 21:14:51 -0700, "Somebody" <somebody(a)cox.net> wrote:
>
>>I have a GUI something like this:
>>
>><column splitter> <data> < column splitter> < data > etc.
>>
>>Each column has a vertical splitter where the user can adjust the columns.
>>
>>One of the requirements was that when the user sizes the app, the columns
>>should scale accordingly.
>>
>>For example... say the app was 100 pixels wide and column A was at 25,
>>column B at 50 and column C at 75... if the app got resized to 200 pixels
>>wide, column A would now be at 50, B at 100 and C at 150.
>>
>>Since a column could be divided arbitrarily by the user (horizontally and
>>vertically), we implemented a tree structure that would hold all this
>>information. We used an "int nRatio" design. So for example, an even
>>split,
>>the left node would get a nRatio=50 and the right node would get a
>>nRatio=50.
>>
>>As we went along with the implementation, we found that we would get the
>>splitters jumping around by a few pixels here and there. Debugging showed
>>round off errors with our division. So we changed "int nRatio" to "float
>>fRatio". That improved a bit, but we would still get cases where the
>>splitters jumped around by a few pixels, so we tried "double dRatio".
>>Still
>>same problem. Finally, we ended up multiplying the numerator by 10,000 to
>>get more decimal precision.
> ****
> Yes, this is typical of using integers. Double will not change the
> phenomenon, because
> you are essentially working at about 1 decimal place, and going from 8 to
> 15 decimal
> places isn't going to fix your problem.
>
> Key here is that you need to keep the ratios strictly as percentages. If
> you are using
> uniform spacing, this is easy. Otherwise, when the user resizes, you need
> to compute the
> ratios of the column size to client area size, for example 0.25, 0.50,
> 0.25 (if the user
> wants a wide column in the middle). Then you will always resize based on
> these ratios and
> the client area. If the user resizes the columns (changes their ratio,
> adds columns,
> etc.) then you compute new ratios. But at no point do you remember column
> sizes in
> pixels, only in percentages. Otherwise, your use of flating point has
> created no real
> additional precision.
> ****
>>
>>That seemed to work, but then we got a bug opened where in following
>>example:
>>
>>A | B | C | D
>>
>>moving splitter A|B or B|C would cause splitters to the right to move.
>>Think
>>about it: A|B was 50%, B|C was 50%, C|D was 50%... so moving A|B without
>>adjusting the ratios for B|C and C|D would cause them to scale as if the
>>app
>>was being resized :(.
> ****
> You are confusing relative widths with absolute widths. You need to
> compute the position
> from the left margin, and round THAT value to a pixel position. If you
> round to pixels
> and add the pixels, you're back doing integer arithmetic. So if the
> widths are
>
> 0.25, 0.50, 0.25
>
> then the first width would be 0.25*client.Width(), the second width would
> be
> 0.75 * client.Width() - 0.25 * client.Width(), and the third width would
> be 1.00 *
> client.Width() - (0.75 * client.Width()). Otherwise, the "jumping" you
> are seeing is an
> expected phenomenon, caused by roundoff when you compute the x position of
> the right of
> the column as based on the computation in floating point plus the x
> position of the left
> of the column (because truncation or rounding would change the left edge
> +/- 1 pixel, and
> this error would then accumulate across the columns). You should expect,
> and cannot
> prevent, +/- 1 pixel errors, the trick is to keep them from being
> cumulative.
> ****
>>
>>We came up with a solution where if I moved A|B, we would recalculate the
>>ratios for the other splitters to keep "rectSplitter" in the same place...
>>
>>Oops... you guessed it, round off errors popped up, even with it
>>multiplied
>>by 10,000.
>>
>>Our whole layout algorithms work based on this "left node gets 50%, right
>>node gets 50%" concept, so changing to something else might be a huge
>>re-write.
> ****
> Well, as soon as you convert to pixels, you lose precision, so as you move
> up the tree,
> the 1-pixel errors accumulate. You have to propagate positions in
> floating point upwards,
> and don't do roundoff until final rendering. Remember that every time you
> have an odd
> number of pixels involved, one of the computations will always be off-by-1
> if you truncate
> or round (it doesn't matter which).
> ****
>>
>>I understand that I can not have "fractional" pixels though. We need to
>>somehow recalculate the ratios because we can't layout by saying "make the
>>splitter appear at x,y" since as I said, its based around this ratio
>>calculations.
> ****
> Yes, you keep fractional pixels in your tree; only when you go to render
> them will you
> truncate or round to integers. This way, errors do not accumulate as you
> move up the
> tree, and your maximum error will be +/- 1 pixel. You could provide some
> heuristic based
> on tree depth to decide whether to truncate or round, or something like
> that, but I think
> the problem is just cumulative roundoff errors.
> ****
>>
>>We could try bumping up our multiplier to something bigger then 10,000
>>like
>>100,000 or 1,000,000, but I'm concerned we'll always get round off errors.
> ****
> You will always get roundoff errors. The trick is to keep them from being
> cumulative. So
> if two columns end up at 38 1/4 pixels wide, it doesn't matter; they will
> both be 38, but
> there's a 1/2-pixel width that propagates upward. Eventually, these will
> cancel out.
>
> I had problems like this in PostScript layout until I understood how to
> avoid cumulative
> pixel errors.
> joe
> ****
>>
>>Any ideas?
>>
> Joseph M. Newcomer [MVP]
> email: newcomer(a)flounder.com
> Web: http://www.flounder.com
> MVP Tips: http://www.flounder.com/mvp_tips.htm


From: Joseph M. Newcomer on
In order to draw anything on the screen, you need to have the coordinates. So you must
have some way to get this information. But if you have to keep doing relative
computations, the cumulative roundoff is going to get you.

What you need to pass down is not pixel sizes, but the pair

<total size, percentage factor>

the key here is that you must not convert to pixels until the latest possible time; each
time you pass down a pixel size, you introduce the possibility that you will get a
roundoff error that will cause exactly the kinds of errors you are seeing.

Yes, an alternative to floating point is rational arithmetic, but it introduces the same
problem, which is that you have to pass the values down all the way.
joe
On Sat, 17 May 2008 21:58:50 -0700, "Somebody" <somebody(a)cox.net> wrote:

>Thanks for the feedback Joe...
>
>Unfortunately, I can not calculate from the top/left corner because there is
>no such concept in my UI. A "cell" knows nothing about its surrounding
>cells, or where its located, etc.
>
>The concept is:
>
>start with rect A
>split rect A horizontally OR vertically by x% to form rects A/B
>split rect B horizontally OR vertically by x% to form rects B/C
>etc.
>
>Ie... a rect can only be split once in a single direction, but I can split
>the split rectangles to form nested cells.
>
>So in the above example:
>
>start with rect A
>split rect A horizontally by 50% to form rects A/B
>split rect B horizontally by 50% to form rects B/C
>
>So now... A|B's ratio is 50%, but so is B|C because its a recursive thing:
>
>root is 100 pixels wide
>A gets 50% of that... A's right node which is B|C gets 50%
>B gets 50% of what A passed down (essentially 25% of the full width) and C
>gets the other 50%...
>
>The problem occurs if say, I move to pixel x = 100 and the width is 300. By
>storing the ratio, I'll *never* recover 100 exactly.
>
>So I'm thinking, instead of storing the ratio as a float, why not store the
>numerator and denomenator as ints?
>
>100 / 300 = 0.33
>.33 * 300 = 99 (OFF BY 1 PIXEL)
>
>BUT...
>
>100 * 300 / 300 = 100 (exact)...
>
>Sound like a reasonable idea?
>
>"Joseph M. Newcomer" <newcomer(a)flounder.com> wrote in message
>news:gibv2411fcurfaugkmod3gmi3sbcitj2dq(a)4ax.com...
>> See below...
>> On Sat, 17 May 2008 21:14:51 -0700, "Somebody" <somebody(a)cox.net> wrote:
>>
>>>I have a GUI something like this:
>>>
>>><column splitter> <data> < column splitter> < data > etc.
>>>
>>>Each column has a vertical splitter where the user can adjust the columns.
>>>
>>>One of the requirements was that when the user sizes the app, the columns
>>>should scale accordingly.
>>>
>>>For example... say the app was 100 pixels wide and column A was at 25,
>>>column B at 50 and column C at 75... if the app got resized to 200 pixels
>>>wide, column A would now be at 50, B at 100 and C at 150.
>>>
>>>Since a column could be divided arbitrarily by the user (horizontally and
>>>vertically), we implemented a tree structure that would hold all this
>>>information. We used an "int nRatio" design. So for example, an even
>>>split,
>>>the left node would get a nRatio=50 and the right node would get a
>>>nRatio=50.
>>>
>>>As we went along with the implementation, we found that we would get the
>>>splitters jumping around by a few pixels here and there. Debugging showed
>>>round off errors with our division. So we changed "int nRatio" to "float
>>>fRatio". That improved a bit, but we would still get cases where the
>>>splitters jumped around by a few pixels, so we tried "double dRatio".
>>>Still
>>>same problem. Finally, we ended up multiplying the numerator by 10,000 to
>>>get more decimal precision.
>> ****
>> Yes, this is typical of using integers. Double will not change the
>> phenomenon, because
>> you are essentially working at about 1 decimal place, and going from 8 to
>> 15 decimal
>> places isn't going to fix your problem.
>>
>> Key here is that you need to keep the ratios strictly as percentages. If
>> you are using
>> uniform spacing, this is easy. Otherwise, when the user resizes, you need
>> to compute the
>> ratios of the column size to client area size, for example 0.25, 0.50,
>> 0.25 (if the user
>> wants a wide column in the middle). Then you will always resize based on
>> these ratios and
>> the client area. If the user resizes the columns (changes their ratio,
>> adds columns,
>> etc.) then you compute new ratios. But at no point do you remember column
>> sizes in
>> pixels, only in percentages. Otherwise, your use of flating point has
>> created no real
>> additional precision.
>> ****
>>>
>>>That seemed to work, but then we got a bug opened where in following
>>>example:
>>>
>>>A | B | C | D
>>>
>>>moving splitter A|B or B|C would cause splitters to the right to move.
>>>Think
>>>about it: A|B was 50%, B|C was 50%, C|D was 50%... so moving A|B without
>>>adjusting the ratios for B|C and C|D would cause them to scale as if the
>>>app
>>>was being resized :(.
>> ****
>> You are confusing relative widths with absolute widths. You need to
>> compute the position
>> from the left margin, and round THAT value to a pixel position. If you
>> round to pixels
>> and add the pixels, you're back doing integer arithmetic. So if the
>> widths are
>>
>> 0.25, 0.50, 0.25
>>
>> then the first width would be 0.25*client.Width(), the second width would
>> be
>> 0.75 * client.Width() - 0.25 * client.Width(), and the third width would
>> be 1.00 *
>> client.Width() - (0.75 * client.Width()). Otherwise, the "jumping" you
>> are seeing is an
>> expected phenomenon, caused by roundoff when you compute the x position of
>> the right of
>> the column as based on the computation in floating point plus the x
>> position of the left
>> of the column (because truncation or rounding would change the left edge
>> +/- 1 pixel, and
>> this error would then accumulate across the columns). You should expect,
>> and cannot
>> prevent, +/- 1 pixel errors, the trick is to keep them from being
>> cumulative.
>> ****
>>>
>>>We came up with a solution where if I moved A|B, we would recalculate the
>>>ratios for the other splitters to keep "rectSplitter" in the same place...
>>>
>>>Oops... you guessed it, round off errors popped up, even with it
>>>multiplied
>>>by 10,000.
>>>
>>>Our whole layout algorithms work based on this "left node gets 50%, right
>>>node gets 50%" concept, so changing to something else might be a huge
>>>re-write.
>> ****
>> Well, as soon as you convert to pixels, you lose precision, so as you move
>> up the tree,
>> the 1-pixel errors accumulate. You have to propagate positions in
>> floating point upwards,
>> and don't do roundoff until final rendering. Remember that every time you
>> have an odd
>> number of pixels involved, one of the computations will always be off-by-1
>> if you truncate
>> or round (it doesn't matter which).
>> ****
>>>
>>>I understand that I can not have "fractional" pixels though. We need to
>>>somehow recalculate the ratios because we can't layout by saying "make the
>>>splitter appear at x,y" since as I said, its based around this ratio
>>>calculations.
>> ****
>> Yes, you keep fractional pixels in your tree; only when you go to render
>> them will you
>> truncate or round to integers. This way, errors do not accumulate as you
>> move up the
>> tree, and your maximum error will be +/- 1 pixel. You could provide some
>> heuristic based
>> on tree depth to decide whether to truncate or round, or something like
>> that, but I think
>> the problem is just cumulative roundoff errors.
>> ****
>>>
>>>We could try bumping up our multiplier to something bigger then 10,000
>>>like
>>>100,000 or 1,000,000, but I'm concerned we'll always get round off errors.
>> ****
>> You will always get roundoff errors. The trick is to keep them from being
>> cumulative. So
>> if two columns end up at 38 1/4 pixels wide, it doesn't matter; they will
>> both be 38, but
>> there's a 1/2-pixel width that propagates upward. Eventually, these will
>> cancel out.
>>
>> I had problems like this in PostScript layout until I understood how to
>> avoid cumulative
>> pixel errors.
>> joe
>> ****
>>>
>>>Any ideas?
>>>
>> Joseph M. Newcomer [MVP]
>> email: newcomer(a)flounder.com
>> Web: http://www.flounder.com
>> MVP Tips: http://www.flounder.com/mvp_tips.htm
>
Joseph M. Newcomer [MVP]
email: newcomer(a)flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
From: Alexander Grigoriev on

"Joseph M. Newcomer" <newcomer(a)flounder.com> wrote in message
news:gibv2411fcurfaugkmod3gmi3sbcitj2dq(a)4ax.com...
> See below...
> Key here is that you need to keep the ratios strictly as percentages. If
> you are using
> uniform spacing, this is easy. Otherwise, when the user resizes, you need
> to compute the
> ratios of the column size to client area size, for example 0.25, 0.50,
> 0.25 (if the user
> wants a wide column in the middle). Then you will always resize based on
> these ratios and
> the client area. If the user resizes the columns (changes their ratio,
> adds columns,
> etc.) then you compute new ratios. But at no point do you remember column
> sizes in
> pixels, only in percentages. Otherwise, your use of flating point has
> created no real
> additional precision.
> ****

I suggest the other way around. The ratios are always kept as "ratios". You
keep integer table that corresponds to column sizes, like: 100, 50, 100; one
per column or group of columns of equal width. These are not pixels. When an
user resizes a column, you replace the table with new column widths in
pixels. This will guarantee that any recalculation will leave other columns
unchanged.

When an user resizes the whole window, you calculate new column widths based
on ratios between these, but the table doesn't change. MulDiv function is
very handy in all these calculations. Make sure to handle zero window width
correctly, though.

As you may guess, when you resize the window width, the splitters may move
at different rate, sometimes one splitter moves by a pixel and another
stays, and the other way around. This is normal, because a splitter cannot
move half pixel. As long as you keep the ratios, it guarantees that your
layout won't creep.