MORE INFORMATION
To better understand round-off errors, consider the following code:
SetMapMode (hDC, MM_ANISOTROPIC);
SetWindowExt (hDC, 2, 2);
SetViewportExt (hDC, 3, 3);
PatBlt (hDC, 0, 0, 5, 2, BLACKNESS);
This code tells the GDI to treat two logical units (the coordinates
used by most GDI functions), in both the vertical and horizontal
direction, as being equal to three device units (pixels). It then asks
the GDI to draw what amounts to a black rectangle five logical units
wide by three logical units tall starting at the logical point (0,0).
The GDI would translate this request into a request to draw a
rectangle 7.5 (5 * 3/2 = 7.5) pixels wide by 3 (2 * 3/2 = 3) pixels
tall. However, display cards cannot draw half a pixel, so the GDI
would either have to round the width up to 8 or truncate it to 7. If
an application relied on one behavior or the other, improper painting
could occur.
Note that using mapping modes, window origins/extents, and viewport
origins/extents does not mean that an application will have round-off
errors. The occurrence of round-off errors depends on what these
features are used for, the structure of the application, and other
factors. Many applications take advantage of mapping modes, window
origins/extents, and viewport origins/extents without ever
encountering adverse round-off errors.
If an application exhibits round-off errors, there are a number of ways
to prevent them, some which are described below.
Method 1
Only use MM_TEXT mapping mode, where one logical unit always equals
one device unit. However, the application must do all its own scaling
and moving of objects. The benefit of this approach is that the
application has strict control over how objects are scaled and moved;
you can look at your code to see the algebra that leads to round-off
errors, and counter these errors appropriately. The drawback to this
approach is that it makes the code more complicated and harder to read
than it might be if the
SetMapMode,
SetWindowOrg,
SetWindowExt,
SetViewportOrg, and
SetViewportExt functions were used.
Method 2
Mix MM_TEXT mapping mode with the mapping mode required. Sometimes
applications only have round-off problems with certain types of
objects. For example, in a graphing program, the application might
want to set a certain mapping mode to draw a bar graph; this mapping
mode might cause the fonts that the application draws to be of the
wrong size.
To work around problems like this, mix MM_TEXT mapping mode with your
mapping mode of choice. You could use MM_TEXT when dealing with
objects that need exact sizes or placement and the other mapping mode
for other drawing.
The benefits and drawbacks of this method are almost the same as those
for method 1. However, with method 2, applications can take advantage
of mapping modes for some of the scaling and moving of objects.
Method 3
If window/viewport origins/extents are set at compile time, be sure to
only do operations that would result in no round-off errors. For
example, take the fraction WindowExt over ViewportExt, and reduce this
fraction. Then only do operations that involve multiples of the
reduced WindowExt values. For example, given the following
WindowExt = ( 6, 27)
ViewportExt = (50, 39)
turn this into a fraction and reduce it. It yields:
in x direction: 6/50 = (2 * 3) / (2 * 5 * 5) = 3/25
in y direction: 27/39 = (3 * 3 * 3) / (3 * 13) = 9/13
Therefore, anything done in the x direction could be done using a
multiple of three logical units; anything done in the y direction
could be done using a multiple of nine logical units. For example, if
the application wanted to scroll the window horizontally, it could
scroll 3, 6, 9, 12, and so on logical units without having to deal
with rounding errors. By using these values, the application will
never have round-off errors.
One benefit of this method is that an application can take advantage
of window origins/extents and viewport origins/extents. A disadvantage
is that the application is limited to a certain set of origins/extents
(that is, those built into the application at compile time).
Method 4
Applications can perform method 3 on-the-fly. This allows the
application to deal with arbitrary window origins/extents and viewport
origins/extents. To determine the minimum number of logical units an
application could use given arbitrary extent values, the following
code might prove useful (the code shown is for determining the value
to use in the horizontal direction):
int GetMinWinXFactor (HDC hDC)
{
int nMinX, xWinExt, xViewExt, nGCD;
xWinExt = LOWORD (GetWindowExt (hDC));
xViewExt = LOWORD (GetViewportExt (hDC));
while ((nGCD = GreatestCommonDivisor (xWinExt, xViewExt)) != 1)
{
xWinExt /= nGCD;
xViewExt /= nGCD;
}
return xWinExt;
}
int GreatestCommonDivisor (int i, int j)
{
while (i != j)
if (i < j)
i -= j;
else
j -= i;
return i;
}
The return value from the
GetMinWinXFactor function above can then be used just like in method 3 (that is, the application can do all output
based on multiples of this value).
Final Notes
The discussion above did not take into account the window origin,
which can contribute to round-off errors. How origins and extents
affect the coordinates that GDI uses is summed up in "Programmer's
Reference.
Developers using mapping modes are encouraged to study the equations
presented in the programmer's reference. The GDI uses these equations when
converting between logical and device units. When round-off errors occur in
an application, it is always a good idea to run the numbers through these
equations to try to determine the cause of the errors.