diff options
Diffstat (limited to 'bignum.s')
-rw-r--r-- | bignum.s | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/bignum.s b/bignum.s new file mode 100644 index 0000000..56f1299 --- /dev/null +++ b/bignum.s @@ -0,0 +1,205 @@ + + + .importzp ptr3, ptr4, sreg + .import popeax, popax + .export _ulong_to_big, _big_to_ulong + + .include "atari.inc" + +;IFP = $d9aa + + fptemp = $a0 ; for now + + .rodata +BIG_64K: + .byte $42, $06, $55, $36, $00, $00 + +;BIG_ULONG_MAX: + ;.byte $44, $42, $94, $96, $72, $95 + + .code + +; TODO: replace these *_to_* with OS calls + +fr0_to_fptemp: + ldx #5 +@l: + lda FR0,x + sta fptemp,x + dex + bpl @l + rts + +fr0_to_fr1: + ldx #5 +@l: + lda FR0,x + sta FR1,x + dex + bpl @l + rts + +fptemp_to_fr0: + ldx #5 +@l: + lda fptemp,x + sta FR0,x + dex + bpl @l + rts + +fptemp_to_fr1: + ldx #5 +@l: + lda fptemp,x + sta FR1,x + dex + bpl @l + rts + +fr0_to_ptr3: + ldy #5 +@l: + lda FR0,y + sta (ptr3),y + dey + bpl @l + rts + +ptr4_to_fr1: + ldy #5 +@l: + lda (ptr4),y + sta FR1,y + dey + bpl @l + rts + +; truncate FR0 to integer (no rounding: 2.8 -> 2) +trunc_fr0: + lda FR0 + and #$7f ; strip sign bit (we only care about exponent magnitude) + sec + sbc #$3f ; A now holds # of base-100 digits in integer part + bcs @ok ; # of int digits > 0? + jmp ZFR0 ; no, zero out FR0 and exit + +@ok: + cmp #5 ; are there <= 5 int digits? + bcs @done ; no, the number's already an integer. + + tax ; zero out digits: X is first one after decimal point + lda #0 +@zloop: + sta FR0+1,x + inx + cpx #5 + bne @zloop + +@done: + rts + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; void __fastcall__ big_trunc(bignump b); + sta FLPTR + stx FLPTR+1 + jsr FLD0P + jsr trunc_fr0 + jsr FST0P + rts + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; void __fastcall__ ulong_to_big(const unsigned long l, bignum *b); +_ulong_to_big: + sta ptr3 + stx ptr3+1 ; save b (destination) + + jsr popeax ; get low 16 bits of l in A/X (hi 16 bits in sreg) + sta FR0 + stx FR0+1 + jsr IFP ; convert A/X to fp + + jsr fr0_to_fptemp ; stash it + + lda sreg ; now get high 16 bits of l in A/X + sta FR0 + ldx sreg+1 + stx FR0+1 + jsr IFP ; convert to fp + + lda #<BIG_64K ; high value needs to be multiplied by 65536 + sta ptr4 + lda #>BIG_64K + sta ptr4+1 + jsr ptr4_to_fr1 + + jsr FMUL ; multiply... + jsr fptemp_to_fr1 ; grab low value + jsr FADD ; add to total + jmp fr0_to_ptr3 ; store it in b and we're done. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; char __fastcall__ big_to_ulong(bignump b, unsigned long *l); +; +; This works, but it's not small, fast, or elegant... +_big_to_ulong: + sta ptr3 + stx ptr3+1 ; save *l (dest) + + jsr popax ; get b + sta FLPTR + sta sreg + stx FLPTR+1 + stx sreg+1 + jsr FLD0P ; there's a typo in atari.inc, should be FLD1P + + ldx #<BIG_64K ; FR1 = 65536 + ldy #>BIG_64K + jsr FLD1R + + jsr FDIV ; FR0 = FR0 / FR1 + jsr trunc_fr0 ; FR0 = INT(FR0) + jsr fr0_to_fptemp ; stash for later... + jsr FPI ; get integer form + bcc @ok ; OS supposed to return with C set if range error + + ; failed, return 0 to caller + lda #0 + tax + rts + +@ok: + ldy #2 ; save top 16 bits of result where they belong + lda FR0 + sta (ptr3),y + iny + lda FR0+1 + sta (ptr3),y + + jsr fptemp_to_fr0 ; this is int((*b)/65536) in FR0 now + + ldx #<BIG_64K ; FR1 = 65536 + ldy #>BIG_64K + jsr FLD1R + + jsr FMUL ; FR0 now int((*b)/65536)*65536 + jsr FMOVE ; FR1 = FR0 + + ldx sreg ; reload original *b in FR0 + ldy sreg+1 + jsr FLD0R + + jsr FSUB ; FR0 = FR0 - FR1 + jsr FPI + + ldy #0 ; store low 16 bits where they belong + lda FR0 + sta (ptr3),y + iny + lda FR0+1 + sta (ptr3),y + + ; success. return 1 to caller. + tya + ldx #0 + rts |