From 34f78fb1f2e479f3dbb70ff820080a912b308a50 Mon Sep 17 00:00:00 2001 From: Stephen Heumann Date: Sun, 16 Apr 2023 20:23:53 -0500 Subject: [PATCH] printf: add support for 'a'/'A' conversion specifiers (C99). These print a floating-point number in a hexadecimal format, with several variations based on the conversion specification: Upper or lower case letters (%A or %a) Number of digits after decimal point (precision) Use + sign for positive numbers? (+ flag) Use leading space for positive numbers (space flag) Include decimal point when there are no more digits? (# flag) Pad with leading zeros after 0x? (0 flag) If no precision is given, enough digits are printed to represent the value exactly. Otherwise, the value is correctly rounded based on the rounding mode. --- stdio.asm | 303 ++++++++++++++++++++++++++++++++++++++++++++++++++- stdio.macros | 11 ++ 2 files changed, 312 insertions(+), 2 deletions(-) diff --git a/stdio.asm b/stdio.asm index 9e975f0..d59c923 100644 --- a/stdio.asm +++ b/stdio.asm @@ -3830,6 +3830,304 @@ ug1 rtl string ds 4 end +**************************************************************** +* +* ~Format_a - format a floating-point number in hex format +* (lowercase output) +* ~Format_A - format a floating-point number in hex format +* (uppercase output) +* +* Inputs: +* ~altForm - always include decimal point? +* ~fieldWidth - output field width +* ~paddChar - padd character +* ~leftJustify - left justify the output? +* ~precision - precision of output +* ~precisionSpecified - was the precision specified? +* +**************************************************************** +* +~Format_a private + using ~printfCommon +argp equ 7 argument pointer +; +; Set the "or" value; this is used to set the case of character results +; + lda #$20 + sta orVal + bra in1 + +~Format_A entry + stz orVal +; +; Check for infinities or nans +; +in1 ldy #8 load exponent/sign word + lda [argp],Y + asl a + tax + eor #32767*2 + bne sn1 if number is an infinity or NaN + lda #' ' do not use '0' padding + sta ~paddChar +; Always format like %e for now, because %E code messes up padding for INF/NAN. +; lda orVal if doing %A format +; bne in2 +; brl ~Format_E format like %E +in2 brl ~Format_e else format like %e +; +; Determine sign +; +sn1 stz ~sgn assume sign is positive + bcc ex1 if sign of number is negative + lda #'-' set sign character to '-' + sta ~sign + dec ~sgn flag that sign is negative +; +; Get exponent +; +ex1 txa get exponent field + lsr a (clears carry) + sbc #16383-1 compute unbiased exponent + sta ~exp save it +; +; Get significand +; +sg1 ldy #6 store the significand in ~sig +sg2 lda [argp],Y + sta ~sig,Y + dey + dey + bpl sg2 + ora ~sig+2 if significand is zero then + ora ~sig+4 + ora ~sig+6 + bne pc1 + lda #3 set exponent so it will print as 0 + sta ~exp +; +; Determine precision +; +pc1 lda ~precisionSpecified if the precision was not specified then + bne rd0 + lda #15 use a precision of 15 + sta ~precision +; +; Do rounding +; +rd0 lda ~precision if precision < 15 + cmp #15 + jge pd1 + + stz ~sig+8 make sure bit above significand is zero + inc a shift significand (precision+1)*4 bits left + asl a + asl a + pha + tay +rd1 ldx #0 + clc +rd1a rol ~sig,X + inx + inx + txa + eor #16 + bne rd1a + dey + bne rd1 + + lda ~sig consolidate extra bits + ora ~sig+2 + ora ~sig+4 + beq rd2 + lda #1 + tsb ~sig+6 + +rd2 lda ~sig+6 if there are extra non-zero bits then + beq rdW + FGETENV get rounding direction + txa + asl a + bcs roundDn0 + bmi roundUp if rounding to nearest then +roundNr lda ~sig+6 if first extra bit is 0 + bpl rdW do not round + asl a else if remaining extra bits are non-zero + bne do_round + lda ~sig+8 or low-order bit of result is 1 then + lsr a + bcc rdW + bra do_round apply rounding + +roundUp lda ~sgn if rounding upward then + bmi rdW if positive then + bra do_round apply rounding + +roundDn0 bmi rdW if rounding downward then +roundDn lda ~sgn if negative then + bpl rdW apply rounding + +do_round ldx #8 (perform the rounding, if needed) +rdV inc ~sig,X + bne rdW + inx + inx + cpx #14+1 + blt rdV + +rdW ply shift significand (precision+1)*4 bits right +rdX ldx #14 + clc +rdXa ror ~sig,X + dex + dex + bpl rdXa + dey + bne rdX + + lsr ~sig+8 handle carry out from rounding + bcc pd1 + ldx #6 +rdYa ror ~sig,X + dex + dex + bpl rdYa + inc ~exp +; +; Compute amount of padding +; +pd1 lda ~fieldWidth subtract off precision from field width + sec + sbc ~precision + sec subtract off minimal extra chars + sbc #6 + sta ~fieldWidth + lda ~sign if there is a sign character then + beq pd2 + dec ~fieldWidth decrement field width +pd2 lda ~precision if precision != 0 or # flag used then + ora ~altForm + beq pd2a + sta ~altForm flag this + dec ~fieldWidth decrement field width +pd2a lda ~exp get exponent + bpl pd3 compute absolute value of exponent + eor #$FFFF + inc a +pd3 cmp #10 if |exponent| >= 10 then + blt pd4 + dec ~fieldWidth decrement field width + cmp #100 if |exponent| >= 100 then + blt pd4 + dec ~fieldWidth decrement field width + cmp #1000 if |exponent| >= 1000 then + blt pd4 + dec ~fieldWidth decrement field width + cmp #10000 if |exponent| >= 10000 then + blt pd4 + dec ~fieldWidth decrement field width +pd4 lda ~paddChar if we are not padding with zeros then + cmp #'0' + beq pn1 + jsr ~RightJustify handle right justification +; +; Print the number +; +pn1 lda ~sign if there is a sign character then + beq pn2 + pha print it + jsl ~putchar +pn2 pea '0' print hex prefix + jsl ~putchar + lda #'X' + ora orVal + pha + jsl ~putchar +pn3 lda ~paddChar if the number needs 0 padding then + cmp #'0' + bne pn5 + lda ~fieldWidth + bmi pn5 + beq pn5 +pn4 ph2 ~paddChar print padd zeros + jsl ~putchar + dec ~fieldWidth + bne pn4 + +pn5 lda #0 print the digits + ldy #4 +pn6 asl ~sig + rol ~sig+2 + rol ~sig+4 + rol ~sig+6 + rol a + dey + bne pn6 +; clc (already clear) + adc #'0' + cmp #'9'+1 + blt pn7 + adc #6 + ora orVal +pn7 pha + jsl ~putchar + lda ~altForm print '.' after first digit if needed + beq pn8 + ph2 #'.' + jsl ~putchar + stz ~altForm +pn8 dec ~precision + bpl pn5 +; +; Print exponent +; + lda #'P' print 'P' or 'p' exponent prefix + ora orVal + pha + jsl ~putchar + + lda ~exp adjust exponent to reflect 4 bits + dec a in integer part (before '.') + dec a + dec a + pha push exponent + bmi pe1 print '+' if exponent is positive + ph2 #'+' + jsl ~putchar +pe1 ph4 #~str push the string addr + ph2 #6 push the string buffer length + ph2 #1 do a signed conversion + _Int2Dec convert exponent to string + ldx #0 print the exponent +pe2 lda ~str,x + and #$00FF + cmp #' '+1 + blt pe3 + phx + pha + jsl ~putchar + plx +pe3 inx + cpx #6 + blt pe2 +; +; Remove the number from the argument list +; + lda argp remove the parameter + adc #10-1 (carry is set) + sta argp +; +; Handle left justification +; + brl ~LeftJustify handle left justification + +; +; Local data +; +orVal ds 2 for setting the case of characters + end + + **************************************************************** * * ~Format_c - format a '%' character @@ -4813,6 +5111,7 @@ lb3 creturn 4:ptr * f,F Signed decimal floating point. * e,E Exponential format floating point. * g,G Use f,e or E, as appropriate. +* a,A Hexadecimal format floating point. * % Write a '%' character. * **************************************************************** @@ -5028,8 +5327,8 @@ val ds 2 value ; List of format specifiers and the equivalent subroutines ; fList dc c'%',a'~Format_Percent' % - dc c'a',a'~Format_e' a (not formatted correctly) - dc c'A',a'~Format_E' A (not formatted correctly) + dc c'a',a'~Format_a' a + dc c'A',a'~Format_A' A dc c'f',a'~Format_f' f dc c'F',a'~Format_f' F dc c'e',a'~Format_e' e diff --git a/stdio.macros b/stdio.macros index 14ab942..c95ac18 100644 --- a/stdio.macros +++ b/stdio.macros @@ -974,3 +974,14 @@ phd tcd mend + MACRO +&LAB FGETENV +&LAB PEA $03 + LDX #$090A + JSL $E10000 + MEND + macro +&l jge &bp +&l blt *+5 + brl &bp + mend