#math #floating-point #drawing #scaling
#математика #с плавающей запятой #рисование #масштабирование
Вопрос:
Код, который я потерял из-за сбоя жесткого диска, однако более года спустя он все еще беспокоит меня об этом, и я действительно хочу получить ответ, чтобы я мог спать по ночам, не задумываясь об этом. Итак, я писал программное обеспечение для дефрагментации, и оно отображало кластеры. Проблема с кодом рисования — математика с плавающей запятой. Я недостаточно опытен, чтобы решить эту математическую задачу. Я мог рисовать кластеры без проблем, однако из-за математики с плавающей запятой последний пиксель боролся с первым пикселем нового кластера, когда я уменьшил его, чтобы он соответствовал окну. Я не могу понять, как это предотвратить. Я попытался округлить вниз и вверх, но это оставило пиксель либо пустым, либо перезаписало пиксель в последнем кластере, когда этого не должно быть, и т.д. Это должно выглядеть так. Не могу понять, как это сделал этот JkDefrag (с открытым исходным кодом), и у меня есть исходный код для этого! Это то, что не дает мне спать по ночам. Пожалуйста, помогите мне хорошо выспаться.
Рисование кода из резервной копии:
void DrawTESTClusters( HWND hWnd, const CMemoryDC amp;memoryDC, LONGLONG ClusterStart, LONGLONG ClusterCount, int color )
{
LONG Width;
LONG Height;
LONGLONG x1;
LONGLONG y1;
//LONGLONG x2;
//LONGLONG y2;
HDC hDC;
LONG MaxLength;
//DebugPrint(_T("%I64d - %I64d, %I64dn"), ClusterStart, ClusterEnd, ClusterEnd - ClusterStart);
// The usual checks
if ( TotalClusters <= 0 ) // Can this happen ??
return;
//_ASSERT( ClusterStart != ClusterEnd );
//if ( ClusterStart == ClusterEnd ) // Can this happen ??
// return;
_ASSERT( ClusterStart >= 0 amp;amp; ClusterStart <= TotalClusters );
//if ( ClusterStart < 0 || ClusterStart > TotalClusters ) // Can this happen ??
// return;
//_ASSERT( ClusterEnd >= 0 amp;amp; ClusterEnd <= TotalClusters );
//if ( ClusterEnd < 0 || ClusterEnd > TotalClusters ) // Can this happen ??
// return;
_ASSERT( ClusterCount >= 0 amp;amp; (ClusterStart ClusterCount) <= TotalClusters );
hDC = memoryDC.GetMemoryDC();
Width = memoryDC.GetWidth();
Height = memoryDC.GetHeight();
MaxLength = memoryDC.GetMaxLength();
// Calculate some stuff
float Scale = static_cast<float>(MaxLength) / TotalClusters;
// TODO: Test this drawing code with scale above 1.0.
// The current code should work in theory (to scale up) but if it doesn't the proposed fix-code below, commented:
//if ( Scale > 1.0 )
// Scale = 1.0;
LONG Length = static_cast<LONG>( ceil( static_cast<float>(ClusterCount) * Scale ) );
if ( Length <= 0 )
return;
//LONG ScaleA = (ClusterStart * Scale / TotalClusters);
//LONG ScaleB = (ClusterEnd * Scale / TotalClusters);
//x1 = (double)(ClusterStart * Scale) / (double)Width;
//x1 = (double)((double)ClusterStart / (double)Width) * Scale;
//y1 = (ClusterStart * Scale) % Width;
//y1 = fmod( (ClusterStart * Scale), Width );
//y1 = fmod( (double)ClusterStart / (double)Width), Scale );
//x2 = ScaleB % Width;
//y2 = ScaleB / Width;
x1 = static_cast<LONGLONG>( fmod( static_cast<float>(ClusterStart * Scale), static_cast<float>(Width) ) );
y1 = static_cast<LONGLONG>( static_cast<float>(ClusterStart * Scale) / static_cast<float>(Width) );
// Calculation done, now check if there is any point in drawing it
//if ( x1 <= 0 amp;amp; x2 <= 0 )
// return;
// Save original object.
HGDIOBJ oldPen = SelectObject( hDC, GetStockObject(DC_PEN) );
// Change the DC pen color
SetDCPenColor( hDC, ClusterMapColors[ color ] );
//if ( InUse == 1 )
// SetDCPenColor( hDC, RGB(160, 160, 160) ); // 160 == 0xA0
//else if ( InUse == 0 )
// SetDCPenColor( hDC, RGB(0xFF, 0xFF, 0xFF) );
// //SetDCPenColor( hDC, RGB(0x00, 0x00, 0x00) );
//else if ( InUse == 2 )
// //SetDCPenColor( hDC, RGB(0x00, 0xFF, 0x00) );
// SetDCPenColor( hDC, RGB(0x00, 0x00, 0xFF) ); // Blue
//else if ( InUse == 3 )
// //SetDCPenColor( hDC, RGB(0x00, 160, 0xFF) );
// SetDCPenColor( hDC, RGB(0x00, 0xFF, 0x00) ); // Green
//else if ( InUse == 4 )
// //SetDCPenColor( hDC, RGB(0x00, 160, 0xFF) );
// SetDCPenColor( hDC, RGB(0xFF, 0x00, 0x00) ); // Red
//else if ( InUse == 5 ) // Debug
// SetDCPenColor( hDC, RGB(0xFF, 0xFF, 0x00) ); // Yellow
//----------------------------------------------------
// Only Draw code in here
LONG step;
LONG line = static_cast<LONG>( y1 );
while ( Length > 0 )
{
step = Min( Length, static_cast<LONG>(Width - x1) );
//step = Min( Length, Width );
if ( MoveToEx( hDC, static_cast<int>(x1), static_cast<int>(line), NULL ) )
LineTo( hDC, static_cast<int>(step x1), static_cast<int>(line) );
Length -= step;
x1 = 0;
line ;
}
#if 0
Width = clientRect.right;
Height = clientRect.bottom;
int Scale = (Width * Height) / TotalClusters;
//x2 = (ClusterEnd * Scale ) % Width;
//y2 = (ClusterEnd * Scale ) / Width;
// Start
int startX = (ClusterStart * Width * Height / TotalClusters ) % Width;
int startY = (ClusterStart * Width * Height / TotalClusters ) / Width;
int endX = ;
int endY;
LONG Length = ClusterEnd - ClusterStart;
while ( Length > 0 )
{
LONG len = Min( Length, Width );
len -= startX;
MoveToEx( hDC, startX, startY, NULL );
LineTo( hDC, startX len, endY );
Length -= len;
startY ; // Next line
startX = 0;
}
#endif
//----------------------------------------------------
// Restore original object.
SelectObject( hDC, oldPen );
}
Комментарии:
1. » Я не могу понять, как это предотвратить». -> Не используйте FP math. Почтовый индекс используется для лучших ответов.
2. Как вы можете не использовать FP math при масштабировании? Я нашел резервную копию кода чертежа.
Ответ №1:
… из-за математики с плавающей запятой последний пиксель боролся с первым пикселем нового кластера, когда я уменьшил его, чтобы он соответствовал окну
Вместо математики с плавающей запятой низкой точности используйте математику целых чисел. Проще точно контролировать крайние случаи.
Сформируйте масштаб в виде пары числитель, знаменатель:
// float Scale = static_cast<float>(MaxLength) / TotalClusters;
long ScaleNumerator = MaxLength;
long ScaleDenominator = TotalClusters;
Замените FP целочисленным кодом. При необходимости используйте более широкую целочисленную математику. Пример:
// x1 = static_cast<LONGLONG>( fmod( static_cast<float>(ClusterStart * Scale),
// static_cast<float>(Width) ) );
long long m = 1LL * ClusterStart * ScaleNumerator / ScaleDenominator;
x1 = m % Width;
y1 = m / Width;
Возможно, в комментируемом коде OP возникли проблемы с переполнением. Альтернатива:
// int startX = (ClusterStart * Width * Height / TotalClusters ) % Width;
int startX = (1LL * ClusterStart * Width * Height / TotalClusters ) % Width;
Если это все еще приводит к проблемам с крайним случаем, корректировку целочисленного математического кода легче исправить, чем FP-код.