PRB: RFX Unnecessarily Updates Floating Point Fields
ID: Q163244
|
The information in this article applies to:
-
The Microsoft Foundation Classes (MFC), included with:
-
Microsoft Visual C++ for Windows, versions 1.5, 1.51, 1.52, 1.52b, 1.52c
-
Microsoft Visual C++ 32-bit Edition, versions 2.0, 2.1, 2.2, 4.0, 4.1, 4.2, 5.0, 6.0
SYMPTOMS
When you use the Microsoft Foundation Classes (MFC) ODBC database classes
to access tables containing floating point numeric data, you may observe
that some numeric fields are updated unnecessarily. Fields that contain
values that were not modified may be marked as dirty and, as a result, are
unnecessarily updated.
CAUSE
RFX_Double and RFX_Single are the RFX functions that the wizards select to
handle the SQL data types SQL_DOUBLE and SQL_REAL, respectively. Both of
these functions detect when recordset member variable values have changed
and require an update to change the corresponding fields in the actual
database table. The detection of dirty fields is based on caching the
original value of the recordset member variable and comparing it to the
current value of the variable. The comparison that Microsoft Foundation
Classes uses to detect dirty floating point values has always been some
form of exact comparison.
It is generally not recommended that you perform exact comparisons of
floating point values because when store a decimal fraction in binary it is
inherently imprecise. Rounding and truncation can occur when these values
are processed (particularly when the Floating Point Unit (FPU) is invoked)
and can lead two values that were initially exactly equal to eventually be
only nearly equal. It is preferable to either treat a floating point field
as character data that is immune to rounding errors or incorporate a small
offset value into comparisons (known as an epsilon value) to allow nearly
equal values to be considered sufficiently equal.
RFX_Double and RFX_Single do not employ an epsilon value in their dirty
field detection comparisons so they will occasionally mark fields whose
values have undergone some form of rounding or truncation as being dirty.
The process of transferring recordset member variables via DDX to and from
the edit controls of a record view or a dialog box can introduce sufficient
rounding errors to result in a field that is marked dirty. In addition, in
Visual C++ 4.2 Microsoft Foundation Classes has been modified to use the
"==" equality operator when comparing the cached and current values. The
"==" invokes the Floating Point Unit and that, in itself, causes rounding
that may result in the field being flagged dirty when it could actually be
unchanged.
RESOLUTION
There are several ways to address this problem:
MORE INFORMATION
Sample Code
The following sample code is designed to replace RFX_Double for MFC 4.2.
You can use this code as a guide for replacing RFX_Single and for making
similar modifications to other versions of Microsoft Foundation Classes.
//****************************************************************
// rfx_double2.cpp
// Most of this code is copied from the MFC source--see comments
// for NEW code.
//
// .cpp file for version of RFX_Double that uses an epsilon value
// in its dirty field comparison.
//
// THIS CODE IS WRITTEN TO WORK WITH MFC 4.2, TO WORK WITH OTHER
// VERSIONS, THIS CODE MAY REQUIRE EXTENSIVE MODIFICATION.
#include "stdafx.h"
// NEW: Constant required for clean compile--different versions of MFC
// use different constants.
static const double afxDoublePseudoNull = AFX_RFX_DOUBLE_PSEUDO_NULL;
// NEW: Comparison that uses an epsilon to define a window within
// which two values are considered equal.
BOOL AFXAPI CompareValueWithEpsilon(void* pvSrc, void* pvDest,
int nSrcType, double epsilon)
{
// Only call with type of double -- you can modify this to accept
// other types.
if (nSrcType != AFX_RFX_DOUBLE)
{
// called with unexpected type
ASSERT(FALSE);
return FALSE;
}
BOOL bDirty = FALSE;
// Do dirty detection comparison factoring in epsilon value.
if ((*(double *)pvDest) > ((*(double *)pvSrc) + epsilon) ||
(*(double *)pvDest) < ((*(double *)pvSrc) - epsilon))
bDirty = TRUE;
return !bDirty;
}
// Fourth parameter is new to this version--it allows you to specify
// the width of the comparison window.
//
// NOTE: Epsilon is expected to be positive.
void AFXAPI RFX_Double2(CFieldExchange* pFX, LPCTSTR szName,
double& value, double epsilon)
{
ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
ASSERT(AfxIsValidString(szName));
switch (pFX->m_nOperation)
{
case CFieldExchange::MarkForUpdate:
{
// NEW: You must move this from original MFC location
// to avoid problems with delegating in default case.
UINT nField;
if (!pFX->IsFieldType(&nField))
return;
if (value != afxDoublePseudoNull)
pFX->m_prs->ClearNullFieldStatus(nField - 1);
// Get the field data,
CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];
// If the user changed the field value from previous value,
// mark field dirty.
if ((pInfo->m_bStatus & AFX_SQL_FIELD_FLAG_NULL))
{
if (!pFX->m_prs->IsFieldStatusNull(nField - 1))
pFX->m_prs->SetDirtyFieldStatus(nField - 1);
}
else
{
// Saved field is not NULL. The current field is null,
// so the field is dirty.
BOOL bDirty = pFX->m_prs->IsFieldStatusNull(nField - 1);
// If values differ, then field dirty
void* pvDataCache = pInfo->m_pvDataCache;
// NEW: Now call a function that does dirty field
// detection based on an epsilon value.
if (bDirty ||
!CompareValueWithEpsilon(&value, pvDataCache,
pInfo->m_nDataType, epsilon))
pFX->m_prs->SetDirtyFieldStatus(nField - 1);
}
#ifdef _DEBUG
// Field address must not change--ODBC's SQLBindCol depends
// upon this.
void* pvBind = &value;
if (pInfo->m_pvBindAddress != pvBind)
{
TRACE1("Error: field address (column %u) has changed!\n",
nField);
ASSERT(FALSE);
}
#endif // _DEBUG
if ((pFX->m_pvField == NULL || pFX->m_pvField == &value) &&
pFX->m_prs->IsFieldStatusDirty(nField - 1))
{
pFX->m_bField = TRUE;
}
break;
}
// NEW: Only process MarkForUpdate--delegate all else.
default:
{
// NEW: Call the original RFX function for all
// cases but MarkForUpdate.
RFX_Double(pFX,szName, value);
}
return;
}
}
Additional query words:
CRecordview DDX_FieldText FPP kbVC151 kbVC152 kbVC200 kbVC210 kbVC220 kbVC 400 kbVC410 kbVC420 kbVC500 kbVC600 s_mfc kbMfc kbtshoot MfcDatabase
Keywords :
Version : winnt:
Platform : winnt
Issue type :