PIC Microcontoller Math Method

Fixed AN575 INT24 routine (round floating point to 24 bit integer)

by Nikolai Golovchenko

I've found also a few more problems with the [AN575] INT24 routine:

1) when AEXP==0, which means zero floating number, but AARGB0:1 is not zero, return will be incorrect. So AARGB0:1 have to be cleared in that case.

2) problem with overflow checking (for example -32769 (0x8e8001) will result in 0xC78e, instead of 0x8000)

There is really something wrong with rounding. They teach in school that if a number is in the middle between two integers, the highest by absolute value is taken. For example, MATLAB has four rounding functions:

ROUND - round to closest integer (like in school)
FIX - round towards zero (ignore fractional part)
CEIL - round towards plus infinity
FLOOR - round towards minus infinity

let x = [-1:0.25:1]

» x = [-1:0.25:1]
x =
    -1 -0.75  -0.5 -0.25     0  0.25   0.5  0.75     1
» round(x)
ans =
    -1    -1    -1     0     0     0     1     1     1
» fix(x)
ans =
    -1     0     0     0     0     0     0     0     1
» ceil(x)
ans =
    -1     0     0     0     0     1     1     1     1
» floor(x)
ans =
    -1    -1    -1    -1     0     0     0     0     1
» 

I assume that the INT24 routine should implement ROUND function. But why there is a check for AARGB1.0 (LSb of integer result)?...

[Here is] a fixed version (I hope) of INT24 routine (fpint.asm).

#include <p16f84.inc>
	cblock 0x20
	AEXP ; - exponent 2^(AEXP - 127)
	AARGB0
	AARGB1 ;- low byte
	BEXP
	BARGB0
	BARGB1	; - temporary for result
	TEMPB0
	TEMPB1
	LOOPCOUNT	; - counter
	SIGN
	FPFLAGS
	endc

mov	macro x, y
	movlw x
	movwf y
	endm

EXP	EQU	AEXP
#define	_C	STATUS, C
#define	_Z	STATUS, Z
#define EXPBIAS	0x7F
#define MSB 0x7
#define LSB 0x0

#define RND 0x0
#define SAT 0x1
#define IOV 0x2
	


	BSF FPFLAGS, RND
	BSF FPFLAGS, SAT
;9.4999      8217FF        0009              9
	mov 0x82, AEXP
	mov 0x17, AARGB0
	mov 0xFF, AARGB1
	call INT2416
	nop
;9.50          821800         000A             10
	mov 0x82, AEXP
	mov 0x18, AARGB0
	mov 0x00, AARGB1
	call INT2416
	nop
;9.9999       821FFF        000A             10        
	mov 0x82, AEXP
	mov 0x1F, AARGB0
	mov 0xFF, AARGB1
	call INT2416
	nop
;10.50        822800         000A              11
	mov 0x82, AEXP
	mov 0x28, AARGB0
	mov 0x00, AARGB1
	call INT2416
	nop
;10.999      822FFF        000A               11
	mov 0x82, AEXP
	mov 0x2F, AARGB0
	mov 0xFF, AARGB1
	call INT2416
	nop
;10.4999     8227FF        000A              10
	mov 0x82, AEXP
	mov 0x27, AARGB0
	mov 0xFF, AARGB1
	call INT2416
	nop
;11.000      823000         000B              11
	mov 0x82, AEXP
	mov 0x30, AARGB0
	mov 0x00, AARGB1
	call INT2416
	nop
;11.4999    8237FF        000B              11
	mov 0x82, AEXP
	mov 0x37, AARGB0
	mov 0xFF, AARGB1
	call INT2416
	nop
;11.50        823800         000C              12
	mov 0x82, AEXP
	mov 0x38, AARGB0
	mov 0x00, AARGB1
	call INT2416
	nop
;11.999      823FFF        000C              12        
	mov 0x82, AEXP
	mov 0x3F, AARGB0
	mov 0xFF, AARGB1
	call INT2416
	nop
;12.0         824000         000C              12
	mov 0x82, AEXP
	mov 0x40, AARGB0
	mov 0x00, AARGB1
	call INT2416
	nop
;12.4999    8247FF         000C              12
	mov 0x82, AEXP
	mov 0x47, AARGB0
	mov 0xFF, AARGB1
	call INT2416
	nop
;12.50       824800          000C              12
	mov 0x82, AEXP
	mov 0x48, AARGB0
	mov 0x00, AARGB1
	call INT2416
	nop
;12.9999    824FFF         000C              12
	mov 0x82, AEXP
	mov 0x4F, AARGB0
	mov 0xFF, AARGB1
	call INT2416
	nop
;13.0          825000         000D              13
	mov 0x82, AEXP
	mov 0x50, AARGB0
	mov 0x00, AARGB1
	call INT2416
	nop
;overflow 32767.5 (RND=1, SAT=1) -> 7FFF
	mov 0x8d, AEXP
	mov 0x7f, AARGB0
	mov 0xff, AARGB1
	call INT2416
	nop
;overflow -32769 (RND=1, SAT=1) -> 8000
	mov 0x8e, AEXP
	mov 0x80, AARGB0
	mov 0x01, AARGB1
	call INT2416
	nop
;zero -> 0000
	clrf AEXP
	call INT2416
	nop
;**********************************************************************************************
;       Float to integer conversion
;
;       Input:  24 bit floating point number in AEXP, AARGB0, AARGB1
;
;       Use:    CALL    INT2416         or      CALL    INT24
;
;       Output: 16 bit 2's complement integer right justified in AARGB0, AARGB1
;
;       Result: AARG  <--  INT( AARG ) , RND = 0
;               AARG  <--  ROUND( AARG ), RND = 1
;
;       Max Timing:     2+7+8+9+7+7*6-1+11+2   = 87 clks           RND = 0
;                       2+7+8+9+7+7*6-1+4+11+2 = 91 clks           RND = 1, SAT = 0
;                       2+7+8+9+7+7*6-1+19+2   = 95 clks           RND = 1, SAT = 1
;
;       Min Timing:     2+8+2 = 14 clks
;
;       PM: 61                                  DM: 6
;
;----------------------------------------------------------------------------------------------

INT2416
INT24
		RLF             AARGB0, W		; save sign in SIGN.7
                RRF             SIGN, F
                BSF             AARGB0,MSB		; make MSB explicit

		MOVF		EXP,W			; test for zero argument
		BTFSS		_Z
		GOTO		INT24NOTZERO
INT24CLEAR	CLRF		AARGB0			; if zero, clear AARGB0:1
		CLRF		AARGB1			; 
		RETLW		0x00			; and return
INT24NOTZERO

;check if exponent too high (exp >= 15), i.e. overflow
;Special case when exp == -1 and RND==1 (%1.xxx * 2^-1 = %0.1xxx)

;exp<=-2  AARGB0:1=0
;exp=-1	  AARGB0:1=RND
;exp=0    AARGB0:1>>15
;exp=1    AARGB0:1>>14
;...
;exp=14   AARGB0:1>>1
;exp>=15  overflow (note that +-32768 is treated as overflow even when
;-32768 can fit in 16 bit twos complement form. But -32768 will result
;in correct answer for SAT=1 or 0, although with a set IOV flag)

		ADDLW		-EXPBIAS-D'15'		; check if overflow
		BTFSC		_C
                GOTO            SETIOV16

		XORLW		0xFF			; get number of shifts 
		MOVWF		EXP			; w=-1-(exp-127-15)=14-(exp-127)
		ANDLW		0xF0			; clear Z if w > 15
		BTFSS		_Z
                GOTO            INT24CLEAR		; jump to clear AARG

                ;do shifts
		BCF		_C                      ; first shift
		RRF		AARGB0, F
		RRF		AARGB1, F
                					; rounding
		BTFSS		EXP, 3			; shift by 8 bits if exp >= 8
		GOTO		INT24SHIFTS
		RLF		AARGB1, w		; copy bit for rounding
		MOVF		AARGB0, w		; shift AARGB0:1 by 8 bits
		MOVWF		AARGB1
		CLRF		AARGB0

INT24SHIFTS	BCF		EXP, 3			; limit EXP to 0..7 range
		INCF		EXP, F			; preincrement EXP
		GOTO		INT24SHIFTTEST		; jump to decrement and zero check
INT24SHIFTSL	BCF		_C			; shift AARGB0:1 right once
		RRF		AARGB0, F		; and
		RRF		AARGB1, F		; copy bit for rounding
INT24SHIFTTEST	DECFSZ		EXP, F			; repeat if necessary
		GOTO		INT24SHIFTSL

SHIFT2416OK     BTFSC           FPFLAGS,RND
		BTFSS		_C			; round if next bit is set
                GOTO            INT2416OK
		INCF		AARGB1,F
                BTFSC           _Z
                INCF            AARGB0,F

		BTFSC           AARGB0,MSB		; test for overflow
                GOTO            SETIOV16

INT2416OK       BTFSS           SIGN,MSB                ; if sign bit set, negate               
                RETLW           0
                COMF            AARGB1,F
                COMF            AARGB0,F
                INCF            AARGB1,F
                BTFSC           _Z
                INCF            AARGB0,F
                RETLW           0

SETIOV16        BSF             FPFLAGS,IOV             ; set integer overflow flag
                BTFSS           FPFLAGS,SAT             ; test for saturation
                RETLW           0xFF                    ; return error code in WREG

		MOVLW		0x80			; saturate to largest two's
                MOVWF           AARGB0			; complement 16 bit integer
                CLRF		AARGB1			; SIGN = 0, 0x 7F FF
                BTFSS           SIGN,MSB		; SIGN = 1, 0x 80 00
                COMF		AARGB0, F
                BTFSS           SIGN,MSB
                COMF		AARGB1, F
                RETLW           0xFF                    ; return error code in WREG
;**********************************************************************************************

Questions: