[ Home Page ] [ Eiffel Archive ] [ Eiffel Classes and Clusters]
![]() |
Filtering Incomplete User Input |
Written by Peter Horan.
float_filter_v1.zip (5,143 bytes) - source code
When entering numerical data in a text window, it is best to inform the user that it is invalid as soon as an unwanted character is entered. However, incomplete numerical strings can fail the tests "is_real" or "is_integer". For example, the strings "", "+" and "." fail. But these are legitimate prefixes of numerical strings.In a similar manner, the expected input may be limited in range. For example, x >= x_min, x =< x_max. Checking these limits during data entry causes problems especially if the value x = 0 is not in range. For example, in setting an error percentage limit, x >= 0.01% and x <= 100% During entry, the set of prefixes representing x is {"0", "0.", "0.0"} all of which fall outside the permitted range. Hence, the need for routines which allow for these cases.
The class FLOAT_FILTER has been developed to address these problems for real numbers. It has the following features:is_float_prefix(s: STRING): BOOLEAN require s /= Void ensure -- Result = "s" is a valid numerical prefix end definitely_smaller(s, lower_limit: STRING): BOOLEAN require lower_limit.is_real is_float_prefix(s) ensure -- Result = "s" is the prefix of -- a numerical string < lower_limit end definitely_bigger(s, upper_limit: STRING): BOOLEAN require upper_limit.is_real is_float_prefix(s) ensure -- Result = "s" is the prefix of -- a numerical string > lower_limit endThe routines "definitely_smaller" and "definitely_bigger" perform simple comparisons in the cases where "lower_limit" is negative or "upper_limit" is positive, respectively, or when the quantities contain no decimal point. However, when the numerical string contains a decimal point, the comparison must be done by comparing the input string with the limit string truncated to the same number of fractional digits. For example, s = 0.2 is not definitely smaller than lower_limit = 0.25 because it could be extended to s = 0.25 and be in range. That is, the call definitely_smaller("0.2", "0.25") should return False. However, definitely_smaller("0.1", "0.25") should return True.
The test class FLOAT_FILTER_TEST is the root class. The feature "make" executes the tests.The feature "check_it" implements the exception handling.
This submission is made because it responds to a user problem that is not evident until faced with actual examples and it may be of interest to others. The method of development relies on design by contract factors. Apart from this and perhaps using the class by implementation inheritance, I claim no special object oriented virtues.A number of points are worth making.
- The test routine was written before any code was written. The code was developed by exercising the test and modifying the code as each test failed. The test strings were developed by enumerating all deviations from the syntax graph of a floating point string. This proved to be an interesting and productive method of development.
- The original testing routine was written with check clauses. I was caught out by the failure to test preconditions of all calls made from the check clause. The check clause is a convenient method of testing because it provides a tag for each boolean expression and triggers the exception mechanism. However, the method fails to detect the error of calling "index_of" from within the "is_float_original" routine with an index that is too small. My thanks to Roger Browne for pointing out my oversight that assertions are unchecked when checking other assertions.
To solve this problem and retain the benefits of the check clause, the feature "check_it" was introduced. Its signature is check_it(tag: STRING; expecting_true: BOOLEAN) and the test routine inherits from class EXCEPTIONS in order to introduce the tag when "expecting_true" returns False. When this happens, an exception is raised and the tag passed to the handler.
- In considering the contract of "is_float_prefix" to present this material, it struck me that there was a simple way of expressing the postcondition, which till then was unexpressed at worst, or a comment at best. Expressing the postcondition led to a simpler solution than parsing the string. In particular, if the string being checked is a valid prefix of a floating point number, extending it with a digit yields a floating point number. On the other hand, if it is not a valid prefix, it remains invalid if so extended.
It was a moment's work to write and test the new version because the tests were already written. Thus, two features "is_float_prefix" and "is_float_prefix_original" are supplied. Although superseded, the latter is presented as proof of the method of developing code to satisfy tests.
Peter Horan
14 October, 1999
[ Home Page ] [ Eiffel Archive ] [ Eiffel Classes and Clusters]