The HP-67 emulator, more on rounding

The HP-67 calculator has some specialized behaviour when displaying numbers in fixed mode.  Numbers less than 1 always have a leading zero before the decimal, leaving 9 digits of precision for the remainder of the value.  If asked to display 0.001 to two digits of precision in fixed mode, the calculator will display the number in scientific notation instead, as that value rounds to a visual representation of zero.  If asked to display 1234567890.123 to two digits of precision, it will display only 1234567890, as the display has only ten digits.

So, now we need functions to display a rational number in either scientific or fixed mode.  The scientific mode is fairly simple, as we can use the long division code we wrote earlier to produce a sequence of digits, and we know the exponent on the total.  Fixed mode is more complicated, because of the automatic conversion to scientific notation.

A number that is too wide to fit in the 10 digits of the display must be at least 10^10-(1/2).  With the rounding rules, that number will round up to an 11 digit number.  A number that is too small to fit in the number of digits of precision requested must be smaller than 10^-d / 2.  With the rounding rules, that number will round up to 1 in the last digit displayed after the decimal.  In between these values, the magnitude of the number (the power of 10 represented by the most significant non-zero digit) and the precision after the decimal point will, together, determine how many digits total must be displayed.

The temptation is to find the base-10 logarithm of the rational number, take the floor function on that, and you have the magnitude of the rational.  However, this is not safe.  Rational numbers are promoted to single-precision floats, and cannot generally be exactly represented.  Sometimes, the error introduced in the conversion from rational to float can cause a number to cross to a different magnitude, as seen in this example:
*slime-repl sbcl*

CL-USER> (log (/ 1000001 10) 10.d0)
5.000000276521525d0
CL-USER> (log (/ 10000001 10) 10.d0)
6.000000083320534d0
CL-USER> (log (/ 100000001 10) 10.d0)
6.999999890119543d0

So, for safety, after the magnitude is determined with the logarithm, it is verified using purely rational arithmetic, and adjusted if necessary.

The next thing of interest is rounding numbers to lower precision.  It is easier to write the code to produce a certain fixed precision, then successively round the number to the desired precision later.  However, successive rounding has a major issue in the case of the number 0.44445.  If you were to round this successively, you might first get 0.4445, then 0.445, then 0.45, then 0.5.  This sequence is clearly incorrect.  When performing successive rounding, when the digit you’re eliminating is 5, you have to know how you got that 5 digit, whether it was from rounding down, or rounding up.  If it came from rounding down or was exact, then the 5 causes a rounding up of the next-higher-precision number.  If it came from rounding up, then the 5 causes no rounding up.  The long division function has been modified to return that information in a third entry in the returned list, and a new function that understands this format, round-long-division-result, has been written to make use of it.

With these changes, we now are ready to display any rational number in either fixed or scientific notation, in a way that exactly matches the HP-67’s behaviour.

The current code is checked into the git repository under the tag v2014-11-08.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

反垃圾邮件 / Anti-spam question * Time limit is exhausted. Please reload CAPTCHA.