Prev: 2DD5 Up: Map Next: 2F8B
2DE3: THE 'PRINT A FLOATING-POINT NUMBER' SUBROUTINE
Used by the routines at PR_ITEM_1 and str.
This subroutine prints x, the 'last value' on the calculator stack. The print format never occupies more than 14 spaces.
The 8 most significant digits of x, correctly rounded, are stored in an ad hoc print buffer in mem-3 and mem-4. Small numbers, numerically less than 1, and large numbers, numerically greater than 2↑27, are dealt with separately. The former are multiplied by 10↑n, where n is the approximate number of leading zeros after the decimal, while the latter are divided by 10↑(n-7), where n is the approximate number of digits before the decimal. This brings all numbers into the middle range, and the number of digits required before the decimal is built up in the second byte of mem-5. Finally the printing is done, using E-format if there are more than 8 digits before the decimal or, for small numbers, more than 4 leading zeros after the decimal.
The following program shows the range of print formats:
10 FOR a=-11 TO 12: PRINT SGN a*9↑a,: NEXT a
i. First the sign of x is taken care of:
  • If x is negative, the subroutine jumps to PF_NEGTVE, takes ABS x and prints the minus sign.
  • If x is zero, x is deleted from the calculator stack, a '0' is printed and a return is made from the subroutine.
  • If x is positive, the subroutine just continues.
PRINT_FP 2DE3 RST $28 Use the calculator.
2DE4 DEFB $31 duplicate: x, x
2DE5 DEFB $36 less_0: x, (1/0) Logical value of x.
2DE6 DEFB $00,$0B jump_true to PF_NEGTVE: x
2DE8 DEFB $31 duplicate: x, x
2DE9 DEFB $37 greater_0: x, (1/0) Logical value of x.
2DEA DEFB $00,$0D jump_true to PF_POSTVE: x Hereafter x'=ABS x.
2DEC DEFB $02 delete: -
2DED DEFB $38 end_calc: -
2DEE LD A,"0" Enter the character code for '0'.
2DF0 RST $10 Print the '0'.
2DF1 RET Finished as the 'last value' is zero.
PF_NEGTVE 2DF2 DEFB $2A abs: x' x'=ABS x.
2DF3 DEFB $38 end_calc: x'
2DF4 LD A,"-" Enter the character code for '-'.
2DF6 RST $10 Print the '-'.
2DF7 RST $28 Use the calculator again.
PF_POSTVE 2DF8 DEFB $A0 stk_zero: The 15 bytes of mem-3, mem-4 and mem-5 are now initialised to zero to be used for a print buffer and two counters.
2DF9 DEFB $C3,$C4,$C5
2DFC DEFB $02 delete: The stack is cleared, except for x'.
2DFD DEFB $38 end_calc: x'
2DFE EXX HL', which is used to hold calculator offsets (e.g. for 'STR$'), is saved on the machine stack.
2DFF PUSH HL
2E00 EXX
ii. This is the start of a loop which deals with large numbers. Every number x is first split into its integer part i and the fractional part f. If i is a small integer, i.e. if -65535<=i<=65535, it is stored in DE' for insertion into the print buffer.
PF_LOOP 2E01 RST $28 Use the calculator again.
2E02 DEFB $31 duplicate: x', x'
2E03 DEFB $27 int: x', INT (x')=i
2E04 DEFB $C2 st_mem_2: (i is stored in mem-2).
2E05 DEFB $03 subtract: x'-i=f
2E06 DEFB $E2 get_mem_2: f, i
2E07 DEFB $01 exchange: i, f
2E08 DEFB $C2 st_mem_2: (f is stored in mem-2).
2E09 DEFB $02 delete: i
2E0A DEFB $38 end_calc: i
2E0B LD A,(HL) Is i a small integer (first byte zero) i.e. is ABS i<=65535?
2E0C AND A
2E0D JR NZ,PF_LARGE Jump if it is not.
2E0F CALL INT_FETCH i is copied to DE (i, like x', >=0).
2E12 LD B,$10 B is set to count 16 bits.
2E14 LD A,D D is copied to A for testing: is it zero?
2E15 AND A
2E16 JR NZ,PF_SAVE Jump if it is not zero.
2E18 OR E Now test E.
2E19 JR Z,PF_SMALL Jump if DE is zero: x is a pure fraction.
2E1B LD D,E Move E to D and set B for 8 bits: D was zero and E was not.
2E1C LD B,$08
PF_SAVE 2E1E PUSH DE Transfer DE to DE', via the machine stack, to be moved into the print buffer at PF_BITS.
2E1F EXX
2E20 POP DE
2E21 EXX
2E22 JR PF_BITS Jump forward.
iii. Pure fractions are multiplied by 10↑n, where n is the approximate number of leading zeros after the decimal; and -n is added to the second byte of mem-5, which holds the number of digits needed before the decimal; a negative number here indicates leading zeros after the decimal.
PF_SMALL 2E24 RST $28 i (i=zero here)
2E25 DEFB $E2 get_mem_2: i, f
2E26 DEFB $38 end_calc: i, f
Note that the stack is now unbalanced. An extra byte 'DEFB +02, delete' is needed immediately after the RST $28. Now an expression like "2"+STR$ 0.5 is evaluated incorrectly as 0.5; the zero left on the stack displaces the "2" and is treated as a null string. Similarly all the string comparisons can yield incorrect values if the second string takes the form STR$ x where x is numerically less than 1; e.g. the expression "50"<STR$ 0.1 yields the logical value "true"; once again "" is used instead of "50".
2E27 LD A,(HL) The exponent byte e of f is copied to A.
2E28 SUB $7E A becomes e minus +7E, i.e. e'+2, where e' is the true exponent of f.
2E2A CALL LOG_2_A The construction A=ABS INT (LOG (2↑A)) is performed (LOG is to base 10); i.e. A=n, say: n is copied from A to D.
2E2D LD D,A
2E2E LD A,($5CAC) The current count is collected from the second byte of mem-5 and n is subtracted from it.
2E31 SUB D
2E32 LD ($5CAC),A
2E35 LD A,D n is copied from D to A.
2E36 CALL e_to_fp y=f*10↑n is formed and stacked.
2E39 RST $28 i, y
2E3A DEFB $31 duplicate: i, y, y
2E3B DEFB $27 int: i, y, INT (y)=i2
2E3C DEFB $C1 st_mem_1: (i2 is copied to mem-1).
2E3D DEFB $03 subtract: i, y-i2
2E3E DEFB $E1 get_mem_1: i, y-i2, i2
2E3F DEFB $38 end_calc: i, f2, i2 (f2=y-i2)
2E40 CALL FP_TO_A i2 is transferred from the stack to A.
2E43 PUSH HL The pointer to f2 is saved.
2E44 LD ($5CA1),A i2 is stored in the first byte of mem-3: a digit for printing.
2E47 DEC A i2 will not count as a digit for printing if it is zero; A is manipulated so that zero will produce zero but a non-zero digit will produce 1.
2E48 RLA
2E49 SBC A,A
2E4A INC A
2E4B LD HL,$5CAB The zero or one is inserted into the first byte of mem-5 (the number of digits for printing) and added to the second byte of mem-5 (the number of digits before the decimal).
2E4E LD (HL),A
2E4F INC HL
2E50 ADD A,(HL)
2E51 LD (HL),A
2E52 POP HL The pointer to f2 is restored.
2E53 JP PF_FRACTN Jump to store f2 in buffer (HL now points to f2, DE to i2).
iv. Numbers greater than 2↑27 are similarly multiplied by 2↑(-n+7), reducing the number of digits before the decimal to 8, and the loop is re-entered at PF_LOOP.
PF_LARGE 2E56 SUB $80 e minus +80 is e', the true exponent of i.
2E58 CP $1C Is e' less than 28?
2E5A JR C,PF_MEDIUM Jump if it is less.
2E5C CALL LOG_2_A n is formed in A.
2E5F SUB $07 And reduced to n-7.
2E61 LD B,A Then copied to B.
2E62 LD HL,$5CAC n-7 is added in to the second byte of mem-5, the number of digits required before the decimal in x.
2E65 ADD A,(HL)
2E66 LD (HL),A
2E67 LD A,B Then i is multiplied by 10↑(-n+7). This will bring it into medium range for printing.
2E68 NEG
2E6A CALL e_to_fp
2E6D JR PF_LOOP Round the loop again to deal with the now medium-sized number.
v. The integer part of x is now stored in the print buffer in mem-3 and mem-4.
PF_MEDIUM 2E6F EX DE,HL DE now points to i, HL to f.
2E70 CALL FETCH_TWO The mantissa of i is now in D', E', D, E.
2E73 EXX Get the exchange registers.
2E74 SET 7,D True numerical bit 7 to D'.
2E76 LD A,L Exponent byte e of i to A.
2E77 EXX Back to the main registers.
2E78 SUB $80 True exponent e'=e minus +80 to A.
2E7A LD B,A This gives the required bit count.
Note that the case where i is a small integer (less than 65536) re-enters here.
PF_BITS 2E7B SLA E The mantissa of i is now rotated left and all the bits of i are thus shifted into mem-4 and each byte of mem-4 is decimal adjusted at each shift.
2E7D RL D
2E7F EXX
2E80 RL E
2E82 RL D
2E84 EXX Back to the main registers.
2E85 LD HL,$5CAA Address of fifth byte of mem-4 to HL; count of 5 bytes to C.
2E88 LD C,$05
PF_BYTES 2E8A LD A,(HL) Get the byte of mem-4.
2E8B ADC A,A Shift it left, taking in the new bit.
2E8C DAA Decimal adjust the byte.
2E8D LD (HL),A Restore it to mem-4.
2E8E DEC HL Point to next byte of mem-4.
2E8F DEC C Decrease the byte count by one.
2E90 JR NZ,PF_BYTES Jump for each byte of mem-4.
2E92 DJNZ PF_BITS Jump for each bit of INT (x).
Decimal adjusting each byte of mem-4 gave 2 decimal digits per byte, there being at most 9 digits. The digits will now be re-packed, one to a byte, in mem-3 and mem-4, using the instruction 'RLD'.
2E94 XOR A A is cleared to receive the digits.
2E95 LD HL,$5CA6 Source address: first byte of mem-4.
2E98 LD DE,$5CA1 Destination: first byte of mem-3.
2E9B LD B,$09 There are at most 9 digits.
2E9D RLD The left nibble of mem-4 is discarded.
2E9F LD C,$FF +FF in C will signal a leading zero, +00 will signal a non-leading zero.
PF_DIGITS 2EA1 RLD Left nibble of (HL) to A, right nibble of (HL) to left.
2EA3 JR NZ,PF_INSERT Jump if digit in A is not zero.
2EA5 DEC C Test for a leading zero: it will now give zero reset.
2EA6 INC C
2EA7 JR NZ,PF_TEST_2 Jump if it was a leading zero.
PF_INSERT 2EA9 LD (DE),A Insert the digit now.
2EAA INC DE Point to next destination.
2EAB INC (IY+$71) One more digit for printing, and one more before the decimal.
2EAE INC (IY+$72)
2EB1 LD C,$00 Change the flag from leading zero to other zero.
PF_TEST_2 2EB3 BIT 0,B The source pointer needs to be incremented on every second passage through the loop, when B is odd.
2EB5 JR Z,PF_ALL_9
2EB7 INC HL
PF_ALL_9 2EB8 DJNZ PF_DIGITS Jump back for all 9 digits.
2EBA LD A,($5CAB) Get counter from the first byte of mem-5: were there 9 digits excluding leading zeros?
2EBD SUB $09
2EBF JR C,PF_MORE If not, jump to get more digits.
2EC1 DEC (IY+$71) Prepare to round: reduce count to 8.
2EC4 LD A,$04 Compare 9th digit, byte 4 of mem-4, with 4 to set carry for rounding up.
2EC6 CP (IY+$6F)
2EC9 JR PF_ROUND Jump forward to round up.
PF_MORE 2ECB RST $28 Use the calculator again.
2ECC DEFB $02 delete: - (i is now deleted).
2ECD DEFB $E2 get_mem_2: f
2ECE DEFB $38 end_calc: f
vi. The fractional part of x is now stored in the print buffer.
PF_FRACTN 2ECF EX DE,HL DE now points to f.
2ED0 CALL FETCH_TWO The mantissa of f is now in D', E', D, E.
2ED3 EXX Get the exchange registers.
2ED4 LD A,$80 The exponent of f is reduced to zero, by shifting the bits of f +80 minus e places right, where L' contained e.
2ED6 SUB L
2ED7 LD L,$00
2ED9 SET 7,D True numerical bit to bit 7 of D'.
2EDB EXX Restore the main registers.
2EDC CALL SHIFT_FP Now make the shift.
PF_FRN_LP 2EDF LD A,(IY+$71) Get the digit count.
2EE2 CP $08 Are there already 8 digits?
2EE4 JR C,PF_FR_DGT If not, jump forward.
2EE6 EXX If 8 digits, just use f to round i up, rotating D' left to set the carry.
2EE7 RL D
2EE9 EXX Restore main registers and jump forward to round up.
2EEA JR PF_ROUND
PF_FR_DGT 2EEC LD BC,$0200 Initial zero to C, count of 2 to B.
PF_FR_EXX 2EEF LD A,E D'E'DE is multiplied by 10 in 2 stages, first DE then DE', each byte by byte in 2 steps, and the integer part of the result is obtained in C to be passed into the print buffer.
2EF0 CALL CA_10A_C
2EF3 LD E,A
2EF4 LD A,D
2EF5 CALL CA_10A_C
2EF8 LD D,A
2EF9 PUSH BC The count and the result alternate between BC and BC'.
2EFA EXX
2EFB POP BC
2EFC DJNZ PF_FR_EXX Loop back once through the exchange registers.
2EFE LD HL,$5CA1 The start - 1st byte of mem-3.
2F01 LD A,C Result to A for storing.
2F02 LD C,(IY+$71) Count of digits so far in number to C.
2F05 ADD HL,BC Address the first empty byte.
2F06 LD (HL),A Store the next digit.
2F07 INC (IY+$71) Step up the count of digits.
2F0A JR PF_FRN_LP Loop back until there are 8 digits.
vii. The digits stored in the print buffer are rounded to a maximum of 8 digits for printing.
PF_ROUND 2F0C PUSH AF Save the carry flag for the rounding.
2F0D LD HL,$5CA1 Base address of number: mem-3, byte 1.
2F10 LD C,(IY+$71) Offset (number of digits in number) to BC.
2F13 LD B,$00
2F15 ADD HL,BC Address the last byte of the number.
2F16 LD B,C Copy C to B as the counter.
2F17 POP AF Restore the carry flag.
PF_RND_LP 2F18 DEC HL This is the last byte of the number.
2F19 LD A,(HL) Get the byte into A.
2F1A ADC A,$00 Add in the carry i.e. round up.
2F1C LD (HL),A Store the rounded byte in the buffer.
2F1D AND A If the byte is 0 or 10, B will be decremented and the final zero (or the 10) will not be counted for printing.
2F1E JR Z,PF_R_BACK
2F20 CP $0A
2F22 CCF Reset the carry for a valid digit.
2F23 JR NC,PF_COUNT Jump if carry reset.
PF_R_BACK 2F25 DJNZ PF_RND_LP Jump back for more rounding or more final zeros.
2F27 LD (HL),$01 There is overflow to the left; an extra 1 is needed here.
2F29 INC B
2F2A INC (IY+$72) It is also an extra digit before the decimal.
PF_COUNT 2F2D LD (IY+$71),B B now sets the count of the digits to be printed (final zeros will not be printed).
2F30 RST $28 f is to be deleted.
2F31 DEFB $02 delete: -
2F32 DEFB $38 end_calc: -
2F33 EXX The calculator offset saved on the stack is restored to HL'.
2F34 POP HL
2F35 EXX
viii. The number can now be printed. First C will be set to hold the number of digits to be printed, not counting final zeros, while B will hold the number of digits required before the decimal.
2F36 LD BC,($5CAB) The counters are set (first two bytes of mem-5).
2F3A LD HL,$5CA1 The start of the digits (first byte of mem-3).
2F3D LD A,B If more than 9, or fewer than minus 4, digits are required before the decimal, then E-format will be needed.
2F3E CP $09
2F40 JR C,PF_NOT_E
2F42 CP $FC Fewer than 4 means more than 4 leading zeros after the decimal.
2F44 JR C,PF_E_FRMT
PF_NOT_E 2F46 AND A Are there no digits before the decimal? If so, print an initial zero.
2F47 CALL Z,OUT_CODE
The next entry point is also used to print the digits needed for E-format printing.
PF_E_SBRN 2F4A XOR A Start by setting A to zero.
2F4B SUB B Subtract B: minus will mean there are digits before the decimal; jump forward to print them.
2F4C JP M,PF_OUT_LP
2F4F LD B,A A is now required as a counter.
2F50 JR PF_DC_OUT Jump forward to print the decimal part.
PF_OUT_LP 2F52 LD A,C Copy the number of digits to be printed to A. If A is 0, there are still final zeros to print (B is non-zero), so jump.
2F53 AND A
2F54 JR Z,PF_OUT_DT
2F56 LD A,(HL) Get a digit from the print buffer.
2F57 INC HL Point to the next digit.
2F58 DEC C Decrease the count by one.
PF_OUT_DT 2F59 CALL OUT_CODE Print the appropriate digit.
2F5C DJNZ PF_OUT_LP Loop back until B is zero.
PF_DC_OUT 2F5E LD A,C It is time to print the decimal, unless C is now zero; in that case, return - finished.
2F5F AND A
2F60 RET Z
2F61 INC B Add 1 to B - include the decimal.
2F62 LD A,"." Put the code for '.' into A.
PF_DEC_0S 2F64 RST $10 Print the '.'.
2F65 LD A,"0" Enter the character code for '0'.
2F67 DJNZ PF_DEC_0S Loop back to print all needed zeros.
2F69 LD B,C Set the count for all remaining digits.
2F6A JR PF_OUT_LP Jump back to print them.
PF_E_FRMT 2F6C LD D,B The count of digits is copied to D.
2F6D DEC D It is decremented to give the exponent.
2F6E LD B,$01 One digit is required before the decimal in E-format.
2F70 CALL PF_E_SBRN All the part of the number before the 'E' is now printed.
2F73 LD A,"E" Enter the character code for 'E'.
2F75 RST $10 Print the 'E'.
2F76 LD C,D Exponent to C now for printing.
2F77 LD A,C And to A for testing.
2F78 AND A Its sign is tested.
2F79 JP P,PF_E_POS Jump if it is positive.
2F7C NEG Otherwise, negate it in A.
2F7E LD C,A Then copy it back to C for printing.
2F7F LD A,"-" Enter the character code for '-'.
2F81 JR PF_E_SIGN Jump to print the sign.
PF_E_POS 2F83 LD A,"+" Enter the character code for '+'.
PF_E_SIGN 2F85 RST $10 Now print the sign: '+' or '-'.
2F86 LD B,$00 BC holds the exponent for printing.
2F88 JP OUT_NUM_1 Jump back to print it and finish.
Prev: 2DD5 Up: Map Next: 2F8B