Login

Subversion Repositories NedoOS

Rev

Blame | Last modification | View Log | Download | RSS feed

#ifndef included_f32toa
#define included_f32toa
#include "pushpop.z80"
#include "f32_pow10_LUT.z80"
#include "f32mul.z80"
#include "mov4.z80"
#include "common_str.z80"

;#define EXTERNAL_FORMAT_LEN                  ;Uses an external reference to get the format length
;#define EXTERNAL_FORMAT_LEN fmtDigits    ;Use for TI-OS

; Set a hard upper limit to the number of digits used
; This routine uses at most MAX_FORMAT_LEN+8 bytes of space
#ifndef MAX_FORMAT_LEN
#define MAX_FORMAT_LEN 8
#endif

; Set the default number of digits used. If EXTERNAL_FORMAT_LEN is defined and
; that value is 0 or greater than MAX_FORMAT_LEN, then FORMAT_LEN is used.
#ifndef FORMAT_LEN
#define FORMAT_LEN 6
#endif

; set the char to use for `e` notation
#ifndef TOK_ENG
#define TOK_ENG 'e'
#endif

; set the char to use for negatives
#ifndef TOK_NEG
#define TOK_NEG '-'
#endif

; set the char to use for decimal points
#ifndef TOK_DECIMAL
#define TOK_DECIMAL '.'
#endif


#define f32toa_x    scrap

f32toa:
;Inputs:
;   HL points to the input float
;   BC points to where the string gets written.
  call pushpop
  ld e,(hl)
  inc hl
  ld d,(hl)
  inc hl
  ld (f32toa_x),de
  ld a,(hl)
  ld e,a
  add a,a
  inc hl
  ld a,(hl)
  ld d,a
  adc a,a
  ld h,b
  ld l,c
  inc a
  jp z,f32toa_return_infnan
  res 7,d
  ld (f32toa_x+2),de
  jr nc,+_
  ld (hl),TOK_NEG    ;negative sign
  inc hl
_:
;DEBC is the float, A is a copy of the exponent, HL points to the next byte to write
  dec a
  jp z,f32toa_return_0

  push hl

  ld (hl),'0' ; pad one more '0' to make room for rounding (i.e. 9.999999=>10.000000)
  inc hl

  push hl

; approximate the base-10 exponent as floor((A-127)*log10(2))
; which is approximately floor((A-127)*77/256)
  ld h,0
  ld l,a
  ld b,h
  ld c,a
  add hl,hl ;2
  add hl,hl ;4
  add hl,hl ;8
  add hl,bc ;9
  add hl,hl ;18
  add hl,bc ;19
  add hl,hl ;38
  add hl,hl ;76
  add hl,bc ;77
  ; now subtract 127*77 from HL and keep the upper 8 bits of the result
  ld a,205
  add a,l
  ld a,217
  adc a,h
  ; A is the approximate base-10 exponent
;f32toa_x needs to be multipled by 10^-A
  push af ; save the exponent
  call nz,f32toa_adjust
;the float is now on [1, 20)
;Let's no work on the float as a 0.24 fixed-point value
;we'll first need to extract the integer component
  ld hl,(f32toa_x)
  ld de,(f32toa_x+2)
  ld a,e
  rlca
  scf
  rra
  ld e,a
  ld a,d
  adc a,a
  sub 126
; A is how many bits to shift out of HLE
  ld b,a
  xor a
f32toa_first_digit_loop:
  add hl,hl
  rl e
  rla
  djnz f32toa_first_digit_loop

; A is the first digit
  pop bc  ; B is the base-10 exponent
  ex (sp),hl
  ld d,0
  cp 10
  jr c,f32toa_first_digit_fixed
  inc d
  dec hl
  ld (hl),'1'
  inc hl
  sub 10
f32toa_first_digit_fixed:
  add a,'0'
  ld (hl),a

  ld a,b
  ex (sp),hl
  pop bc
;A is the base-10 exponent
;BC is where to write the digits
;EHL is the 0.24 fixed-point number
;D is 1 if we have 2 digits already, else D is 0
  push af
  call f32toa_digits

;BC points to the last digit. We'll want to round!
  ld h,b
  ld l,c
  ld a,(hl)
  ld (hl),0
  add a,5
  jr f32toa_round_start
f32toa_round_loop:
  dec hl
  inc (hl)
  ld a,(hl)
f32toa_round_start:
  cp '9'+1
  jr nc,f32toa_round_loop
; the string is rounded
  inc hl
  ld (hl),0

  ; pop the exponent off the stack and sign-extend
  pop af
  ld e,a
  add a,a
  sbc a,a
  ld d,a
  pop hl
  ; check if rounding caused overflow; increment exponent if so
  ld a,(hl)
  cp '0'
  jp z,formatstr
  inc de
  jp formatstr


f32toa_digits:
; How many digits do we need?
#ifdef EXTERNAL_FORMAT_LEN
  ; we define the number of digits externally
  ld a,(EXTERNAL_FORMAT_LEN)

  ; if A is 0, use FORMAT_LEN
  or a
  jr nz,$+4
  ld a,FORMAT_LEN

  ; if A > MAX_FORMAT_LEN, set A to MAX_FORMAT_LEN
  cp MAX_FORMAT_LEN
  jr c,$+4
  ld a,MAX_FORMAT_LEN

  ; the first digit is written.
  ; if we only wanted 1 digit, then A is 0 and we should stop
  or a
  ret z
#else
  ; the number of digits is not declared externally
  ; the first digit is written.
  ; if we only wanted 1 digit, then A is 0 and we should stop
#if FORMAT_LEN < 2
  ret
#else
  ld a,FORMAT_LEN
#endif
#endif

; we want D more digits (an extra one for rounding)
f32toa_digits_loop:
  push af
  call f32toa_next_digit
  pop af
f32toa_digits_start:
  dec a
  jr nz,f32toa_digits_loop
  ret

f32toa_next_digit:
; multiply 0.EBC by 10
  push bc
  ld b,h
  ld c,l
  ld a,e
  ld d,6  ;overflow digit. We shift d 3 times, that 6 turns into a 0x30 == '0'

  add hl,hl
  adc a,a
  rl d

  add hl,hl
  adc a,a
  rl d

  add hl,bc
  adc a,e
  jr nc,$+3
  inc d

  add hl,hl
  adc a,a
  ld e,a
  ld a,d
  adc a,a

  pop bc
  inc bc
  ld (bc),a
  ret


f32toa_adjust:
  ld hl,f32_pown10_LUT-4
  jr c,f32toa_pow10LUT_mul
  neg
  ld hl,f32_pow10_LUT-4
f32toa_pow10LUT_mul:
;HL points to the first entry of the LUT
;(f32toa_x) is the accumulator
;bottom 6 bits of A control which terms to multiply by
  ld de,f32toa_x
  ld b,d
  ld c,e
; process f32toa_pow10LUT_mul_sub 6 times
  call f32toa_pow10LUT_mul_sub3
f32toa_pow10LUT_mul_sub3:
  call f32toa_pow10LUT_mul_sub
  call f32toa_pow10LUT_mul_sub
f32toa_pow10LUT_mul_sub:
  inc hl
  inc hl
  inc hl
  inc hl
  rra
  jp c,f32mul
  ret

f32toa_return_0:
  ld (hl),'0'
  inc hl
  ld (hl),0
  ret

f32toa_return_infnan:
  rl b  ; save the sign
  ld a,e
  add a,a
  ld de,(f32toa_x)
  or d
  or e
  ex de,hl
  ld hl,str_NaN
  jr nz,f32toa_return
  ld hl,str_inf
  rr b
  jr nc,f32toa_return
  ld a,'-'
  ld (de),a
  inc de
f32toa_return:
  jp mov4

#include "formatstr.z80"
#endif