[comp.sys.handhelds] HP48 thinks -3 in binary is zero!

neal@druhi.ATT.COM (Neal D. McBurnett) (07/01/90)

I was shocked to discover that adding #5h and -3 yields #5h!
Upon further inquiry, it turns out that << -3 R->B >>  produces #0h.
I would expect it to produce the two's complement value using the
current wordsize, e.g.  #FFFDh.

This problem arose in the following situation.  I want to keep track
of the cumulative error in the internal clock, so I wrote a program
"TADJ" as a replacement for the standard CLKADJ function which
adjusts the internal clock.  TADJ takes an adjustment in seconds
and both modifies the internal clock via CLKADJ and keeps track of
the cumulative error by incrementing a global variable "OFFSET".
OFFSET is a binary integer because I usually use it in conjunction with
the result of TICKS, which is binary.

Here is TADJ:
  << 8192 * DUP CLKADJ
	'OFFSET' STO+
  >>

This works fine for positive arguments, but when I try "-10 TADJ" I
end up adjusting the internal clock, but "OFFSET" is unchanged!

I can't think of any justification for this.  It is also a royal
pain to get around it.  I can't simply convert the binary to a real
number before doing the arithmetic, because it is a huge number
(related to the number of clock ticks since the year zero, just like the
output of TICKS....) and precision would be lost.  I guess my best
bet is to use << +/- STO- >> if the argument is negative.  Sigh.

HP, can you fix this in a future ROM?

-Neal McBurnett, neal@druhi.ATT.COM, AT&T Bell Labs, Denver.

edp@deland.enet.dec.com (Eric Postpischil (Always mount a scratch monkey.)) (07/01/90)

> I was shocked to discover that adding #5h and -3 yields #5h!

> Upon further inquiry, it turns out that << -3 R->B >>  produces #0h.

> I can't think of any justification for this.  It is also a royal
> pain to get around it.  I can't simply convert the binary to a real
> number before doing the arithmetic, because it is a huge number
> (related to the number of clock ticks since the year zero, just like the
> output of TICKS....) and precision would be lost.  I guess my best
> bet is to use << +/- STO- >> if the argument is negative.  Sigh.

Since your adjustment is small even though OFFSET is large, you can
convert the adjustment to binary prior to doing the arithmetic.  Use <<
MA + R->B MA - 'OFFSET' STO+ >> where MA is the maximum adjustment you
might supply (or the largest integer you can specify without losing
precision, 10E10).

You can also use the timekeeping routines below.


				-- edp


Here's a set of timekeeping routines that compensate for inaccuracy in the
48's clock.  (They could also be used to keep sidereal time.)

In a list called CLKDAT (clock data), there are, in order:

	o The value of TICKS at the start of some reference period.

	o The cumulative number of ticks added to the clock since then.

	o The difference between true time and clock time as a fraction
	  of the number of elapsed clock ticks (called "accuracy factor").

The following routines are provided:

KICK	Called with no arguments to update the clock by the correct
	number of ticks.

ADJT	Called with a unit object specifying an amount of time by which
	to change the time zone or system.  E.g., call ADJT with -1_h
	on the stack to change from Daylight Savings to Standard Time.

ADJC	Called to adjust the clock by a specified number of ticks.  Once
	the clock is set exactly with ADJC, EXACT should be called or the
	data will be lost when KICK is next called.

EXACT	Uses the cumulative number of ticks added to the clock, by KICK
	and by ADJC, to compute a new accuracy factor.

SCHEDULE
	Schedules an alarm at 4 a.m. to call KICK daily.

CANCEL
	Cancels the alarm.

RESET	Set a new reference time and discard cumulative ticks added.
	Keep the old accuracy factor until a new one is computed.

To use these timekeeping routines:

	o Set the time with the built-in functions.
	o Make the clock as exact as desired with CLKADJ.
	o Execute RESET (no arguments).
	o After using RESET, do not use the built-in time adjustment commands.
	o Let time pass.
	o Make the clock as exact as desired with ADJC.  (ADJC takes as an
	  argument a number of ticks, just as CLKADJ does.)
	o Execute EXACT.
	o Execute SCHEDULE.

Every night at 4 a.m., KICK will update the clock.

After the initial setting, you can update the accuracy factor by again
adjusting the clock with ADJC and executing EXACT.  If your calculator's
environment changes, you can establish a new reference time by making the clock
exact and executing RESET.  This keeps the current accuracy factor but resets
the reference time, so the next execution of EXACT will base the new accuracy
factor on the time elapsed since RESET.

To change time zones or systems, use ADJT.

o If you call ADJC by accident, call KICK to correct it.
o If you call ADJT by accident, call it again with the negation of the
argument.
o If you call EXACT by accident, you are out of luck unless you have another
  copy of your accuracy factor.  If so, replace it in CLKDAT.

KICK makes the clock correct by computing the number of ticks to add to the
clock to make it correct now:

    (accuracy factor)*(TICKS - reference time - added ticks) - added ticks

Because this calculation is used rather than simply adding a fixed number
of ticks at regular intervals, KICK can be called at any time, at frequent
or infrequent intervals.  Updating the clock daily keeps it close to the
correct time.  You can change the time of the alarm and the repeat interval
in the SCHEDULE routine.  (Be sure not to set a time that is skipped over.
E.g., if you set the KICK alarm for 3 a.m. and also have an alarm that adds
1 hour to the clock at 2 a.m. for the change to Daylight Savings Time, then
the clock will go from slightly after 2 a.m. to slightly after 3 a.m., and
the KICK alarm will not be executed or scheduled for the next day.)

SCHEDULE uses AO (alarm object) as the object for the alarm to execute.  This
object discards its argument, gets the current path, executes an object called
JOB, restores the current path, and turns the calculator off.  If an error
occurs, the error message is left in the stack before shutting the calculator
off.  If a variable called JOB does not exist when SCHEDULE is executed, it is
created as a list specifying the current directory and the KICK program.  If
you would like to add additional routines to be executed, add them to the list
in JOB.  JOB is created in the home directory but can be moved to any port.  If
you need to make more complicated changes, like removing the call to OFF,
execute CANCEL, change the AO variable, and call SCHEDULE.

KICK can be modified to adjust the clock so that it will be correct at some
time in the future.  E.g., you could arrange it so that KICK runs at 4 a.m.
and adds the number of ticks needed to make the clock correct at 4 p.m.  By
doing this, the clock is early part of the day and late part of the day,
instead of only early or only late.  This cuts in half the frequency with
which KICK must be executed to keep the clock within a specified distance
of the correct time.

To make this modification to KICK, add the number of ticks to the number
in stack level one just after the call to PA.  For example, to adjust KICK
to prepare the clock to be correct in 12 hours, put

	353894400 +

after the call to PA in KICK.

Here are the variables needed.  I keep them in a directory called TIMEKEEP,
but you can place them anywhere without needing any changes.  I have typed
these in by hand but used the indicated type-2 translation codes.  Since I
did them by hand, I did them individually so errors can be tracked down.

CDP	@ Clock Data Put	CRC #AF13h 52.5 @
%%HP: T(2);
\<< \-> V L \<< 'CLKDAT' L V PUT \>> \>>

CDG	@ Clock Data Get	CRC #4B28h 29.5 @
%%HP: T(2);
\<< 'CLKDAT' SWAP GET \>>

PA	@ PreAmble		CRC #D892h 56 @
%%HP: T(2);
\<< RCLF 64 STWS 2 CDG TICKS 1 CDG - OVER - B\->R \>>

AO	@ Alarm Object		CRC #3405h 51 @
%%HP: T(2);
\<< IFERR DROP PATH :&: JOB RCL EVAL EVAL THEN ERRM END OFF \>>

RESET	@ Reset Reference Time	CRC #4B82h 33 @
%%HP: T(2);
\<< TICKS 1 CDP 0 2 CDP \>>

CLKDAT	@ Clock Data		CRC #57E6h 23 @
%%HP: T(2);
{ #0 0 0 }

CANCEL	@ Cancel Alarm		CRC #CE1h 141.5 @
%%HP: T(2);
\<< RCLF -55 SF IERR 1 \-> I \<< WHILE I RCLALARM IF 3 GET 'AO'
RCL SAME THEN I DELALARM 1 ELSE 'I' INCR END REPEAT END \>> THEN
END STOF \>>

SCHEDULE	@ Schedule Alarm	CRC #D5E2h 132.5 @
%%HP: T(2);
\<< CANCEL PATH HOME IF :&: JOB VTYPE 0 < THEN DUP 'KICK' +
:&: JOB STO END EVAL DATE 1 DATE+ 4 'AO' RCL 707788800 4 \->LIST
STOALARM DROP \>>

EXACT	@ Compute Accuracy Factor	CRC #F256h 29.5 @
%%HP: T(2);
\<< PA / 3 CDP STOF \>>

ADJC	@ Adjust Clock			CRC #2BEFh 35.5 @
%%HP: T(2);
\<< DUP 2 CDG + 2 CDP CLKADJ \>>

ADJT	@ Adjust Time			CRC #BF52h 69.5 @
%%HP: T(2);
\<< 1_s CONVERT UVAL 8192 * DUP 1 CDG + 1 CDP CLKADJ \>>

KICK	@ Kick Clock			CRC #D931h 47 @
%%HP: T(2);
\<< PA 3 CDG * 0 RND SWAP - ADJC STOF >>


				-- edp                 

r91400@memqa.uucp (Michael C. Grant) (07/02/90)

In article <576@cbnewsb.ATT.COM>, neal@druhi.ATT.COM (Neal D. McBurnett) writes:
> I was shocked to discover that adding #5h and -3 yields #5h!
> Upon further inquiry, it turns out that << -3 R->B >>  produces #0h.
> I would expect it to produce the two's complement value using the
> current wordsize, e.g.  #FFFDh.

It was my understanding that binary/hex/octal/decimal numbers (the ones
with '#' preceding them) were unsigned integers.  Thus, -3 would indeed
convert to #0h, or result in an error (which I would personally prefer).

Allowing signed numbers of this type would produce inconsistencies if
you changed the width of the number in mid-calculation.  For example,
if you had set the width to 8, typed the number
#11110000b
into the calculator, and then changed the width to 16, what would happen?
Well, I personally prefer 
#0000000011110000b
instead of the other alternative--sign extension.

Perhaps the easiest way to get around this problem would be to write
your own version of a two's complement routine, and convert ALL of your
numbers that way, so that any negative numbers would be converted properly.
I'd give you the program but I don't have my calculator handy (and I'm
not very good at remembering the keywords).

Michael C. Grant

madler@piglet.caltech.edu (Mark Adler) (07/03/90)

>> Upon further inquiry, it turns out that << -3 R->B >>  produces #0h.

I think this is documented.  Unlike the HP-16C, the HP-48SX (and it's
ancestors, the 28C and 28S) don't believe in negative binary numbers.
If you're below the range of non-negative integers allowed by the
current wordsize, you get zero from R->B, and if you're above, you get
the word filled with binary one's (i.e. FFFFh if the wordsize is 16).

It wouldn't be a big deal, nor largely inconsistent if it did convert
negative numbers properly, but it doesn't.  The fix is simply to convert
the positive number and negate it.  For example, this program converts
positives or negatives to binary like one would want it to:

<< DUP ABS R->B SWAP IF 0 > THEN NEG END >>

Mark Adler
madler@tybalt.caltech.edu

herman@corpane.UUCP (Harry Herman) (08/01/90)

In <4060@memqa.uucp> r91400@memqa.uucp (Michael C. Grant) writes:

>In article <576@cbnewsb.ATT.COM>, neal@druhi.ATT.COM (Neal D. McBurnett) writes:
|> I was shocked to discover that adding #5h and -3 yields #5h!
|> Upon further inquiry, it turns out that << -3 R->B >>  produces #0h.
|> I would expect it to produce the two's complement value using the
|> current wordsize, e.g.  #FFFDh.

|It was my understanding that binary/hex/octal/decimal numbers (the ones
|with '#' preceding them) were unsigned integers.  Thus, -3 would indeed
|convert to #0h, or result in an error (which I would personally prefer).

|Allowing signed numbers of this type would produce inconsistencies if
|you changed the width of the number in mid-calculation.  For example,
|if you had set the width to 8, typed the number
|#11110000b
|into the calculator, and then changed the width to 16, what would happen?
|Well, I personally prefer 
|#0000000011110000b
|instead of the other alternative--sign extension.

|Perhaps the easiest way to get around this problem would be to write
|your own version of a two's complement routine, and convert ALL of your
|numbers that way, so that any negative numbers would be converted properly.
|I'd give you the program but I don't have my calculator handy (and I'm
|not very good at remembering the keywords).

|Michael C. Grant

I really like the HP-28 and the HP-48 (especially the HP-48), but I
would like HP to consider supporting signed binary math in a future
release.  It is very annoying to have to get my old HP-16 back out
whenever I need to to math on negative binary numbers.

Since some people (like Michael, above) like the current behaviour,
then maybe have yet another flag that specifies whether to do signed math
or unsigned math.  I REALLY do need to do signed math on occasion, and
would rather have a method built into the calculator to do it automatically
(and correctly) than having to role my own.


				Harry Herman
				herman@corpane
				..uunet!corpane!herman