My PPC device runs for a fairly long time on a watch battery and a pair of AAAs. Call me a hayseed, but I think this is really incredible. I wasn’t too surprised, then, when I found part of the reason for this long battery life is that my palmtop has a limited register set and it doesn’t have chip support for much floating-point math. Try as I might, I simply couldn’t rearrange program logic in a way that would get rid of unresolved externals that had to do with floating point number comparisons. I finally wrote workarounds that transform floating point numbers to integers and then operate them.
Figure: The FloatingPoint Example
FloatingPoint.cpp
// FloatingPoint.cpp : Defines the entry point for the application. // #include "stdafx.h" #include "FloatingPoint.h" #include <commctrl.h> #include <windowsx.h> #include <math.h> #define MAX_LOADSTRING 100 #define BETWEEN 1 #define LESS_THAN 2 #define GREATER_THAN 3 // Global Variables: HINSTANCE hInst; // The current instance HWND hwndCB; // The command bar handle // Forward declarations of functions included in this code module: ATOM MyRegisterClass (HINSTANCE hInstance, LPTSTR szWindowClass); BOOL InitInstance (HINSTANCE, int); LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About (HWND, UINT, WPARAM, LPARAM); BOOL CALLBACK FloatingPtDlgProc (HWND, UINT, WPARAM, LPARAM); BOOL CheckFloatingPointRange( TCHAR* ); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { MSG msg; HACCEL hAccelTable; // Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_FLOATINGPOINT); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // // COMMENTS: // // This function and its usage is only necessary if you want // this code to be compatible with Win32 systems prior to the // 'RegisterClassEx' function that was added to Windows 95. // It is important to call this function so that the // application will get 'well formed' small icons associated // with it. // ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_FLOATINGPOINT)); wc.hCursor = 0; wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = szWindowClass; return RegisterClass(&wc); } // // FUNCTION: InitInstance(HANDLE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global // variable and create and display the main program window. // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; TCHAR szTitle[MAX_LOADSTRING]; // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; // The window class // name hInst = hInstance; // Store instance handle in our // global variable // Initialize global strings LoadString(hInstance, IDC_FLOATINGPOINT, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance, szWindowClass); LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); if (hwndCB) CommandBar_Show(hwndCB, TRUE); DialogBox( hInst, TEXT( "FLOATING_POINT" ), hWnd, FloatingPtDlgProc); return TRUE; } // // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_HELP_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_FILE_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_CREATE: hwndCB = CommandBar_Create(hInst, hWnd, 1); CommandBar_InsertMenubar(hwndCB, hInst, IDM_MENU, 0); CommandBar_AddAdornments(hwndCB, 0, 0); break; case WM_DESTROY: CommandBar_Destroy(hwndCB); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // Mesage handler for the About box. LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { RECT rt, rt1; int DlgWidth, DlgHeight; // dialog width and height in pixel // units int NewPosX, NewPosY; switch (message) { case WM_INITDIALOG: // trying to center the About dialog if (GetWindowRect(hDlg, &rt1)) { GetClientRect(GetParent(hDlg), &rt); DlgWidth = rt1.right - rt1.left; DlgHeight = rt1.bottom - rt1.top ; NewPosX = (rt.right - rt.left - DlgWidth)/2; NewPosY = (rt.bottom - rt.top - DlgHeight)/2; // if the About box is larger than the physical // screen if (NewPosX < 0) NewPosX = 0; if (NewPosY < 0) NewPosY = 0; SetWindowPos(hDlg, 0, NewPosX, NewPosY, 0, 0, SWP_NOZORDER | SWP_NOSIZE); } return TRUE; case WM_COMMAND: if ((LOWORD(wParam) == IDOK) || (LOWORD(wParam) == IDCANCEL)) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; } BOOL CALLBACK FloatingPtDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { HWND hWndEdit; TCHAR tszNumberBuff[8]; switch (message) { case WM_INITDIALOG: hWndEdit = GetDlgItem( hDlg, IDC_FLOAT_NUMBER ); //limit input to 6 characters Edit_LimitText( hWndEdit, 6 ); break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_TEST: memset( tszNumberBuff, 0x0, sizeof(tszNumberBuff)); GetDlgItemText( hDlg, IDC_FLOAT_NUMBER, &tszNumberBuff[0], 6 ); if( !CheckFloatingPointRange( &tszNumberBuff[0])) { MessageBox(hDlg, TEXT(" Input not in range "), TEXT( "Floating Point Test " ), MB_OK ); } else { MessageBox(hDlg, TEXT(" Input within range "), TEXT( "Floating Point Test " ), MB_OK ); } break; case IDCANCEL: EndDialog(hDlg, LOWORD(wParam)); return TRUE; default: break; } break; } return FALSE; } //////////////////////////////////////////////////////// // Some PPCs don't do floating pt compares, // so here is a work around //////////////////////////////////////////////////////// BOOL CheckFloatingPointRange( TCHAR* pszFloatString ) { int iValid, iInputDecimal, iInputSign, iCompareInput; int iThreshDecimal, iThreshSign ; int iThreshStrlen, iInputStrlen; int iInput, iThreshold, iExp, iDivisor; char *pszBuffer, *psz1stInputZero, *psz1stThreshZero; char szInputBuff[9]; double dInput; double dThreshold = 0; double* pThreshold; TCHAR *stopstring; //get validation type iValid = BETWEEN; //get user input dInput = wcstod(pszFloatString, &stopstring); //set the upper threshold value dThreshold = 8.95; //get input digits, magnitude & sign pszBuffer = _ecvt( dInput, 8, &iInputDecimal, &iInputSign ); //copy the string before the buffer contents are destroyed strcpy( szInputBuff, pszBuffer ); //find the location of the first 0, calculate number digits psz1stInputZero = strchr( szInputBuff, '0'); iInputStrlen = psz1stInputZero - szInputBuff; //convert to integer iInput = atoi( szInputBuff ); //apply correct sign if( iInputSign ) { iInput *= -1; } //get threshold digits, magnitude & sign pszBuffer = _ecvt( dThreshold, 8, &iThreshDecimal, &iThreshSign ); //find the location of the first 0, calculate number digits psz1stThreshZero = strchr( pszBuffer, '0'); iThreshStrlen = psz1stThreshZero - pszBuffer; //convert to integer iThreshold = atoi( pszBuffer ); //apply correct sign if( iThreshSign ) { iThreshold *= -1; } switch( iValid ) { case BETWEEN: case LESS_THAN: //if the input sign is greater, fail and bail //if the sign == 0, the number we converted is positive if( iInputSign == 0 && iThreshSign != 0 ) {return FALSE; } //if the input magnitude is greater, fail & bail //The decimal parameter points to an integer value //giving the position of the decimal point with respect //to the beginning of the string. if( iInputDecimal > iThreshDecimal ) {return FALSE; } if( iInputDecimal < iThreshDecimal ) {return TRUE; } // if we get to here, we have to compare // the digits to find the larger number //divide away the padding to correct the magnitudes -- //use all the decimal digits in the longest string iExp = ( iInputStrlen <= iThreshStrlen )? 8 - iThreshStrlen: 8 - iInputStrlen; iDivisor = (int)pow( 10, (double)iExp); iCompareInput = iInput / iDivisor; iThreshold = iThreshold / iDivisor; if( iThreshold < iCompareInput ) {return FALSE;} //return for Greater than if(iValid == LESS_THAN) {return TRUE;} //if this is a range, get 2nd bound if(iValid == BETWEEN) { //set lower range bound dThreshold= 4.32; //get threshold digits, magnitude & sign pszBuffer = _ecvt( dThreshold, 8, &iThreshDecimal, &iThreshSign ); //find the location of the first 0, calculate //number digits psz1stThreshZero = strchr( pszBuffer, '0'); iThreshStrlen = psz1stThreshZero - pszBuffer; //convert to integer iThreshold = atoi( pszBuffer ); //apply correct sign if( iThreshSign ) { iThreshold *= -1; } } //FALL THRU!! case GREATER_THAN: //test uses the first threshold val if GT, second if //BTWN //if the input sign is greater, fail and bail //if the sign 1= 0, the number we converted is negative if( iInputSign != 0 && iThreshSign == 0 ) {return FALSE; } //if the input magnitude is greater, fail & bail //The decimal parameter points to an integer value //giving the position of the decimal point with respect //to the beginning of the string. if( iInputDecimal < iThreshDecimal ) {return FALSE; } if( iInputDecimal > iThreshDecimal ) {return TRUE; } // if we get to here, we have to compare // the digits to find the larger number //divide away the padding to correct the magnitudes-- //use all the decimal digits in the longest string iExp = ( iInputStrlen <= iThreshStrlen )? 8 - iThreshStrlen: 8 - iInputStrlen; iInput = iInput / (int)pow( 10, (double)iExp); iThreshold = iThreshold / (int)pow( 10, (double)iExp ); if( iThreshold > iInput ) {return FALSE;} else {return TRUE;} default: //all other cases are self validating return TRUE; break; }//end switch }
The math transforms are done in the CheckFloatingPointRange() function. The tricky thing about converting floating point numbers to integers is getting the place values translated correctly, so if you write a floating point routine, pay careful attention to the length of the strings that you end up with as you translate between characters and integers.
//get input digits, magnitude & sign pszBuffer = _ecvt( dInput, 8, &iInputDecimal, &iInputSign ); //copy the string before the buffer contents are destroyed strcpy( szInputBuff, pszBuffer );
The parameters to _ecvt(), in the order shown, are a floating point number (of type double), the number of digits to return in the converted string, the address of and integer that gives the offset to the location at which the decimal point would be found if it were included in the string, and the address of an integer that specifies the sign. The sign is positive if this number is zero. Notice that we copy the result of the translation to stack based buffer immediately following the call to _ecvt(). The contents of the buffer returned by the function will be destroyed by subsequent calls, so you should preserve it if it by copying if it will be used later.
//find the location of the first 0, calculate number digits psz1stInputZero = strchr( szInputBuff, '0'); iInputStrlen = psz1stInputZero - szInputBuff; //convert to integer iInput = atoi( szInputBuff ); //apply correct sign if( iInputSign ) { iInput *= -1; }
Next, we find the offset of the first zero in the returned string, searching left to right. This is important because the strings are zero padded on the right hand side if there are fewer input digits than we specified in the call to ecvt_(). We translate the returned buffer to an integer with a call to atoi(), and then apply the sign returned by _ecvt(). We go through this conversion process for both the input number and the number to which we want to compare it. Next, let’s look at the process of comparing the two numbers:
case GREATER_THAN: //test uses the first threshold val if GT, second if //BTWN //if the input sign is greater, fail and bail //if the sign 1= 0, the number we converted is negative if( iInputSign != 0 && iThreshSign == 0 ) {return FALSE; } //if the input magnitude is greater, fail & bail //The decimal parameter points to an integer value //giving the position of the decimal point with respect //to the beginning of the string. if( iInputDecimal < iThreshDecimal ) {return FALSE; } if( iInputDecimal > iThreshDecimal ) {return TRUE; }
There are two cases that tell us out of hand which number is greater, without doing any more work, using information returned by _ecvt(): If the input number is positive and the threshold is negative, we know that the input number is greater. Also, if there is a difference in the position of the decimal point, we can tell which number is larger. If neither of these cases occur, we have to compare the magnitudes of the integers we manufactured.
//divide away the padding to correct the magnitudes-- //use all the decimal digits in the longest string iExp = ( iInputStrlen <= iThreshStrlen )? 8 - iThreshStrlen: 8 - iInputStrlen; iInput = iInput / (int)pow( 10, (double)iExp); iThreshold = iThreshold / (int)pow( 10, (double)iExp ); if( iThreshold > iInput ) {return FALSE;} else {return TRUE;}
We create a divisor that is a power of ten that will divide away all the zero padding of the least padded number, and then compare.
A Few More Thoughts on File Handling and Data Portability
Undoubtedly, as you port your application file handling code, you’ll be nipped a few times by the hounds of Unicode. It takes some experience to unlearn the “fast and loose” (and convenient) string handling habits you develop as a C programmer, but here is one thing that will help: Think of Unicode characters as being numbers, rather than characters. When you access them by address, try to make a habit of using a separately declared pointer of type TCHAR*, rather than by using the addresses and doing your own address arithmetic.
The C string handling runtimes are still used (and useful) under CE, though some come up as unresolved externals on devices I have tested. To get a good idea of what is supported for a given platform, check the header file tchar.h. Finally, if you initialize control text, filenames, or function parameters using text read from files, do a global search on function names such as strcpy() and strcat(). Chance are, you’ll find strings that need to be translated to Unicode in these spots.
Looking Ahead
Files offer one kind of persistent data storage under Windows CE, but more intriguing and more exciting is CE’s integrated database API. If you are moving a data-driven application from the desktop, it may actually be quite a bit easier to use the CE database API instead of a file-based implementation.
Downloads
Source Code: FloatingPoint.zip – 4 Kb
About the Author
Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, and entry time data validation.
In addition to writing for Developer.com, she has written several books, including Making Win 32 Applications Mobile.
# # #