home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 7 / 07.iso / c / c065 / 2.ddi / MATH.ZIP / XCVT.CAS < prev   
Encoding:
Text File  |  1990-06-07  |  16.6 KB  |  551 lines

  1. /*------------------------------------------------------------------------
  2.  * filename - xcvt.cas
  3.  *
  4.  * function(s)
  5.  *        __xcvt - converts a double value to an ASCIIZ string
  6.  *-----------------------------------------------------------------------*/
  7.  
  8. /*[]------------------------------------------------------------[]*/
  9. /*|                                                              |*/
  10. /*|     Turbo C Run Time Library - Version 3.0                   |*/
  11. /*|                                                              |*/
  12. /*|                                                              |*/
  13. /*|     Copyright (c) 1987, 1990 by Borland International        |*/
  14. /*|     All Rights Reserved.                                     |*/
  15. /*|                                                              |*/
  16. /*[]------------------------------------------------------------[]*/
  17.  
  18.  
  19. #pragma  inline
  20. #include <asmrules.h>
  21.  
  22. #include <_printf.h>
  23. #include <_math.h>
  24. #include <math.h>    /* for pow10 */
  25.  
  26. #define    I asm
  27.  
  28. #if LPROG
  29. #define  EXTPROC1(x)  asm push cs ;  asm call near ptr (x)
  30. #else
  31. #define  EXTPROC1(x)  asm call near ptr (x)
  32. #endif
  33. /*--------------------------------------------------------------------------*
  34.  
  35. Name            __xcvt - convert double/long double value to ASCIIZ string
  36.  
  37. Usage           short pascal near __xcvt(void *valP,
  38.                                     short ndigits,
  39.                                     int *signP,
  40.                                     char *strP,
  41.                                     int ftype)
  42.  
  43. Prototype in    _printf.h
  44.  
  45. Description     The double/long double (*valP) is converted to a decimal 
  46.                 string (*strP) of up to 18 digits, a  sign (*signP, 
  47.                 false == positive) and a decimal exponent (the function
  48.                 return value).
  49.  
  50.                 "ndigits" specifies  how the number  should be rounded.  If
  51.                 positive,  then  ndigits  specifies  the  maximum number of
  52.                 digits. Otherwise, ndigits specifies  the maximum number of
  53.                 fractional decimals (to the right of the decimal point). If
  54.                 |ndigits| is > 18 then ndigits will be limited to +-18.
  55.  
  56.                 The string  is in ASCIIZ   form. The string  is padded with
  57.                 zeros  to the  right to  fill in  the requested  number of
  58.                 digits or decimal places.
  59.  
  60.                 The exponent is calculated as  if the decimal point were at
  61.                 the left (most significant) end  of the string (there is no
  62.                 "." character  in the string).  If the value  was zero then
  63.                 the exponent is set to zero.
  64.  
  65.                 If the value was zero then the exponent is 0 and the string
  66.                 is  all "0".  If the  value was  infinite or  NAN then  the
  67.                 exponent is MAXSHORT and the string is all "9".
  68.  
  69.  
  70.     The ftype parameter will be :
  71.  
  72.         2 - FLOAT
  73.         6 - DOUBLE
  74.         8 - LONG DOUBLE
  75.  
  76.     The numbers correspond to the offset of the exponent word from 
  77.     the start of the number.
  78.  
  79. Return value    __xcvt returns the decimal exponent of the number.
  80.  
  81. Note: A #define in '_printf.h' can be used to enable recognition of floats
  82.       as well as doubles and long doubles.  This feature may be disabled
  83.       though because it isn't strictly ANSI standard.  The code in this module
  84.       is set up to recognize floats but the higher modules will never pass
  85.       the float flag unless the variable is defined in _float.h
  86. *---------------------------------------------------------------------------*/
  87.  
  88. #define MaxSigDigits    18
  89.  
  90. #pragma warn -use
  91. int pascal near
  92. __xcvt(void *valP, int digits, int *signP, char *strP, int ftype)
  93. {
  94.     short    ten = 10;
  95.     short    SW;        /* iNDP status word */
  96.     char     frac [10];    /* tenbyte BCD integer */
  97.  
  98. /* caller expects ES to be preserved! */
  99. I    push    ES
  100.  
  101. #if (! LDATA)
  102. I    mov    ax, ds
  103. I    mov    es, ax
  104. #endif
  105.  
  106. /*
  107.     Convert parm to 'long double' and store locally.  We ZAP the sign 
  108.     bit out of the number on the stack before loading it, but after 
  109.     saving the exponent word in CX.  This saves having to do a FABS 
  110.     later on which saves lots of time if this code is running emulated
  111.     and really doesn't cost any more than a real FABS running on a 8087
  112.     in terms of speed.
  113. */
  114. I    LES_    di, valP        /* ES:DI <- pointer to value    */
  115. I    mov    ax, 7FFFH         /* Mask for sign zapping    */
  116. I    mov    bx, ftype        /* types are 2,6 or 8        */
  117. I    mov    cx, es:[bx+di]        /* Get original exponent word    */
  118. I    and    es:[bx+di], ax        /* Zap the sign bit        */
  119. I    shr    bx, 1            /* Make 'type' into 0,2 or 4    */
  120. I    shr    bx, 1            /*    and do an indexed jump to    */
  121. I    shl    bx, 1            /*       the right load instr.    */
  122. I    jmp    word ptr cs:type_table[bx]
  123.  
  124. #pragma warn -asm
  125. I type_table    LABEL    NEAR
  126. I    dw    F4bytes            /* 4  byte 'float'        */
  127. I    dw    F8bytes            /* 8  byte 'double'        */
  128. I    dw    F10bytes        /* 10 byte 'long double'    */
  129.  
  130. I F4bytes    LABEL NEAR
  131. I    FLD    FLOAT (es:[di])        /* Load 32 bit 'float'        */
  132. I    jmp    short its_loaded
  133. I F8bytes    LABEL NEAR
  134. I    FLD    DOUBLE (es:[di])    /* Load 64 bit 'double'        */
  135. I    jmp    short its_loaded
  136. I F10bytes    LABEL NEAR
  137. /* hack a few bits off of normals (please don't ask why) */
  138. I    and    ax, es:[di+8]
  139. I    cmp    ax, 7FFFh
  140. I    je    F10bytesHacked
  141. I    and    BY0(es:[di]), 0F0H    /* Can't print em' anyway    */
  142. F10bytesHacked:
  143. I    FLD    LONGDOUBLE (es:[di])    /* Load 80 bit 'long double'    */
  144. #pragma warn .asm
  145.  
  146. /* Take original exponent word's sign & return it to caller.         */
  147.  
  148. its_loaded:
  149. I    xor    bx, bx
  150. I    shl    cx, 1            /* CF <- sign            */
  151. I    rcl    bx, 1            /* BX <- sign            */
  152. I    LES_    di, signP        /* Store result in caller space    */
  153. I    mov    ES_ [di], bx
  154.  
  155. /* 
  156.     Weed out all the 'strange' numbers here(0, Infinity & NANs).    
  157.  
  158.     The format of C0, C1, C2 & C4 in the status word is:    
  159.  
  160.     15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
  161.     --+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--
  162.        C3          C2 C1 C0 
  163.     ----- upper byte ------+----- lower byte ------
  164.            AH               AL
  165.  
  166.     C3 C2 C1 C0
  167.     -----------
  168.      0  0  0  0    +Unnormal
  169.      0  0  0  1    +NAN
  170.      0  0  1  0    -Unnormal    (*)
  171.      0  0  1  1    -NAN        (*)
  172.      0  1  0  0    +Normal
  173.      0  1  0  1    +Infinity
  174.      0  1  1  0    -Normal        (*)
  175.      0  1  1  1    -Infinity    (*)
  176.      1  0  0  0    +Zero
  177.      1  0  0  1    Empty
  178.      1  0  1  0    -Zero        (*)
  179.      1  0  1  1    Empty
  180.      1  1  0  0    +Denormal
  181.      1  1  0  1    Empty
  182.      1  1  1  0    -Denormal    (*)
  183.      1  1  1  1    Empty
  184.  
  185.      (*) We'll never see these in operation because we've zapped the 
  186.          sign bit before loading the number (it was saved though 
  187.          before being clobbered).
  188.  
  189.     Note : 286/287 specific porters.
  190.  
  191.         The 80287/80387 know how to do a FSTSW directly into the
  192.         AX register.
  193. */
  194.  
  195. I    FXAM
  196. I    FSTSW   SW            /* Get the 87' Status        */
  197. I    FWAIT
  198.  
  199. I    mov    ax, SW             /* Load up the status word    */
  200. I    and    ah, 47H            /* Mask out uninteresting stuff */
  201.  
  202. /*
  203.     Zero is the most likely 'strange' number, so it's checked first.
  204.     Remember, signs were zapped above so we only need to look for the
  205.     positive cases!
  206. */
  207. I    cmp    ah, 40H            /* +0    */
  208. I    je    zero
  209.  
  210. I    cmp    ah, 05H            /* +INF */
  211. I    je    its_infinity
  212.  
  213. I    cmp    ah, 01H            /* +NAN    */
  214. I    je    its_NAN
  215.  
  216.     goto normal;
  217.  
  218. /************************************************************************
  219. *    Special representations for 0, Infinity and NAN values.        *
  220. ************************************************************************/
  221.  
  222. its_NAN:
  223. I    mov     dx, NAN_number        /* dx = NAN flag        */
  224. I    jmp     short   pop_and_go
  225.  
  226. its_infinity:
  227. I    mov     dx, INF_number        /* dx = Infinity flag        */
  228. I    jmp     short   pop_and_go
  229.  
  230.     /* True zero and 'rounds to zero' results wind up here        */
  231.  
  232. zero:
  233. roundToZero:
  234. I    mov     dx, 1                   /* We really want 0.0E+01    */
  235. I    mov     al, '0'
  236.  
  237. extreme:
  238. I    mov     cx, digits        /* fill caller's string with    */
  239. I    or      cx, cx                  /*      either all zeros or    */
  240. I    jg      extSized                /*      all nines.        */
  241. I    neg     cx
  242. I    inc     cx                      /* digit left of decimal point    */
  243.  
  244. extSized:
  245. I    cmp     cx, __XCVTDIG__         /* limit caller's buffer    */
  246. I    jbe     extLimited
  247. I    mov     cx, __XCVTDIG__
  248.  
  249. extLimited:
  250. I    cld                /* Fill in & NULL terminate str    */
  251. I    LES_    di, strP
  252. I    rep stosb
  253. I    xor    al, al
  254. I    stosb
  255.  
  256. pop_and_go:
  257. I    FSTP    ST(0)                   /* clear X from stack */
  258.         goto  end;
  259.  
  260. /************************************************************************
  261. *        Normal numbers are not zero, infinite or NANs.        *
  262. *                                    *
  263. * Note:    upon arrival here --                        *
  264. *        87' TOS contains the number to convert            *
  265. ************************************************************************/
  266.  
  267. normal:
  268. /*
  269.     How many decimal places are there in the number?  It is not good
  270.     to use the log10 function for two reasons:
  271.  
  272.     - it is slow and clumsy (even when not emulated)
  273.  
  274.     - the answer can be wrong: it is somewhat rare but rounding errors
  275.       in the log function can cause the wrong number of digits.
  276.  
  277.     An alternative method is to make a swift estimate of the log, then 
  278.     check it later.  So long as the error is at most one digit up or 
  279.     down, and happens in a minority of cases, performance will be
  280.     reasonable.  We can form the estimate by multiplying the binary 
  281.     exponent by a conversion factor Log10of2.  Since 16 bit accuracy
  282.     is OK at this stage, it is possible to use fixed point arithmetic
  283.     on the main CPU. 
  284. */
  285.     /*** FST won't do temp-reals so we have to use FSTP ***/
  286.  
  287. I    FLD    st(0)            /* Duplicate the number        */
  288. I    FSTP    LONGDOUBLE (frac)    /* Save long double form    */
  289. I    FWAIT
  290.  
  291. I    mov    ax, frac[8]        /* Get new 80 bit #'s exp word    */
  292. I    sub    ax, 3FFFH        /* Remove exponent bias        */
  293.  
  294. I    mov    dx, 4D10h               /* 10000h * Log10of2, rounded.    */
  295. I    imul    dx
  296. I    xchg    ax, bx
  297. I    mov    ah, 4DH
  298. I    mov    al, frac[7]
  299. I    shl    al, 1
  300. I    mul    ah
  301. I    add    ax, bx
  302. I    adc    dx, 0
  303. I    neg    ax
  304. I    adc    dx, 0            /* DX = estimated exponent    */
  305.  
  306. /*
  307.     Now we are ready to do the rounding.  DX estimates the decimal digits
  308.     left of the decimal point.  AX contains the requested precision.
  309. */
  310. I    mov     ax, digits
  311. I    or      ax, ax              /* -,0,+ = decimals, dflt, digits */
  312. I    jg      digitPlaces
  313.  
  314. /*
  315.     The caller has requested (-AX) decimals following the decimal point.
  316. */
  317. I    neg     ax
  318. I    add     ax, dx            /* AX = equivalent signif. digits */
  319. I    jl      roundToZero             /* Ignore if it rounds to zero    */
  320.  
  321. /*
  322.     The caller has requested (AX) significant digits (approximately).
  323.     This is now limited to 18, the maximum precision convertible by
  324.     the iNDP-87 (equivalent to around 59 bits of precision: double
  325.     precision in C is 53 bits, roughly 16 decimals).  Zeros will be
  326.     appended later to make up the extra digits requested.
  327. */
  328. digitPlaces:
  329. I    cmp     ax, MaxSigDigits
  330. I    jng     defaultPlaces
  331. I    mov     ax, MaxSigDigits
  332.  
  333. /*
  334.     Now the number is scaled to place the requested number of digits
  335.     left of the decimal point, and that number is rounded and converted
  336.     to a BCD integer.  Upon arrival here:
  337.  
  338.     DX      is the estimated decimal magnitude of the number
  339.     AX      is the number of leading digits required
  340. */
  341. defaultPlaces:
  342. I    mov     bx, ax          /* BX = safe copy of AX */
  343. I    sub     ax, dx
  344. I    mov     si, ax
  345. I    jz      adjusted        /* 10^0 == 1, so skip the multiply/divide */
  346.  
  347. I    jnl     power10
  348. I    neg     ax
  349.  
  350. power10:
  351. I    push    ax
  352.         EXTPROC1 (pow10)         /* leaves result in ST */
  353. I    pop     ax
  354.  
  355. /* pow10 may ret +INF, which would wreck things */
  356. I    cmp    ax, 4932
  357. I    jle    adjust
  358. I    FSTP    st(0)
  359. I    FLDZ
  360.  
  361. /*
  362.     Now the value 10^(|SI|) is on TOS.  That is multiplied or divided
  363.     with the value in TOS(1) to yield a number with an integral part
  364.     probably having just the number of wanted digits.
  365. */
  366. adjust:
  367. I    or      si, si
  368. I    jg      increase
  369.  
  370. I    FDIV
  371. I    jmp     short   adjusted
  372.  
  373. increase:
  374. I    FMUL
  375.  
  376. /*
  377.     Before unpacking the TOS, we must check that the number of actual
  378.     decimals is correct, since up till now everything has depended on
  379.     an estimate.
  380. */
  381. adjusted:
  382. I    push    bx
  383.     EXTPROC1 (pow10)         /* leaves result in ST */
  384. I    pop     ax
  385. I    FCOMP                           /* cmp ST, ST(1), then pop */
  386. I    FSTSW   SW
  387. I    FWAIT
  388.  
  389. I    test    BY1 (SW), 45h           /* test C3, C2, C0 */
  390. I    jz      notTooHigh              /*  all zero implies ST > ST(1) */
  391.  
  392. /*
  393.     If arrived here then the number is too high.  The error is never
  394.     as great as tenfold, so divide by 10 to correct it.
  395. */
  396. I    inc     dx                      /* correct the estimate of decimals */
  397. I    inc     bx                      /*  and size of result string */
  398. I    cmp     bx, MaxSigDigits
  399. I    ja      mustShorten
  400. I    cmp     W0 (digits), 0         /* is format F or E ? */
  401. I    jng     notTooLow
  402.  
  403. mustShorten:
  404. I    FIDIV   W0 (ten)                /* E formats: maintain requested */
  405. I    dec     bx                      /*  count of digits */
  406. I    jmp     short   notTooLow
  407.  
  408. /*
  409.     If arrived here the number was not too high, but may be too low.
  410. */
  411. notTooHigh:
  412. I    mov     ax, bx
  413. I    dec     ax
  414. I    push    ax
  415.     EXTPROC1 (pow10)         /* leaves result in ST */
  416. I    pop     ax
  417. I    FCOMP                           /* cmp ST, ST(1), then pop */
  418. I    FSTSW   SW
  419. I    FWAIT
  420. I    test    BY1 (SW), 41h           /* test C3, C0 */
  421. I    jnz     notTooLow               /* either non-zero implies ST <= ST(1 */
  422.  
  423. /*
  424.     Adjust upward tenfold to correct the alignment.
  425. */
  426. I    dec     dx                      /* correct the estimate of decimals */
  427. I    dec     bx                      /*      and size of result string */
  428. I    cmp     W0 (digits), 0         /* is format F or E ? */
  429. I    jng     notTooLow
  430.  
  431. I    FIMUL   W0 (ten)                /* E formats: maintain requested */
  432. I    inc     bx                      /*      count of digits */
  433.  
  434. /*
  435.     Now convert the number in TOS into a decimal integer of up to 18
  436.     digits.  The default rounding mode applies.
  437. */
  438. notTooLow:
  439. I    FRNDINT                 /* FBSTP does not round properly ! */
  440. I    FBSTP   frac
  441.  
  442. I    LES_    di, strP        /* Locate the end of string .. */
  443. I    add     di, bx
  444. I    push    di              /* .. remember it for late      $%5 .. */
  445. I    xor    al, al          /* .. and put the zero terminator there. */
  446. I    std                     /* fill the string in reverse order */
  447. I    stosb
  448.  
  449. /*
  450.         Locate the fraction.
  451. */
  452. I    lea     si, frac
  453.  
  454. I    mov     cx, 4           /* CL = nibble shift, CH = round-up flag */
  455.  
  456. /*
  457.     The CH flag is necessary because the rounding to integer can change
  458.     a 999.. value to 1000..  by rounding up.  In that case the number
  459.     of digits changes, and we will not scan as far as the '1' digit.
  460.     The CH flag accumulates the OR of all digits: if it remains zero,
  461.     then we know we have a round-up problem.
  462. */
  463. I    FWAIT                   /* wait for conversion to finish. */
  464. I    or      bx, bx
  465. I    jnz     nextPair
  466.  
  467. /*
  468.     Fractions which may round up to 1 are checked here as a special case.
  469. */
  470. I    mov     ch, SS_ [si]
  471. I    xor     ch, 1           /* enable round-up if is 1. */
  472. I    jmp     short   maybeRoundup
  473.  
  474. /*
  475.     Note that string direction is reversed, least significant digits are
  476.     converted first.
  477. */
  478. nextPair:
  479. I    mov     al, SS_ [si]            /* convert the packed BCD .. */
  480. I    inc     si
  481. I    mov     ah, al
  482. I    shr     ah, cl
  483. I    and     al, 0Fh
  484. I    add     ax, 3030h /* '00' */    /* .. to ASCII decimals */
  485. I    stosb
  486. I    or      ch, al          /* accumulate non-zero digits */
  487. I    dec     bx
  488. I    jz      maybeRoundup
  489. I    mov     al, ah
  490. I    stosb
  491. I    or      ch, al          /* accumulate non-zero digits */
  492. I    dec     bx
  493. I    jnz     nextPair
  494.  
  495. maybeRoundup:
  496. I    pop     bx              /* remember end-of-string position. */
  497. I    and     ch, 0Fh         /* were any non-zero digits seen ? */
  498. I    jnz     append
  499.  
  500. /*
  501.     If all zeros, then we can assume the leading digit will be '1'
  502.     due to a round-up.  Increment DX to correct the estimated digits.
  503. */
  504. I    inc     dx
  505. I    cmp     W0 (digits), 0
  506. I    jg    put1
  507. I    mov     BY0 (ES_ [bx]), '0'
  508. put1:
  509.  
  510. I    inc    bx              /* also increment count of digits */
  511. I    mov     BY0 (ES_ [di+1]), '1'
  512.  
  513. /*
  514.     The caller may want more than 18 digits.  We oblige, with limits,
  515.     by appending zeros up to the intended length.
  516. */
  517. append:
  518. I    mov     cx, digits
  519. I    or      cx, cx
  520. I    jg      zMax
  521. I    neg     cx              /* request was for fixed decimals       */
  522. I    add     cx, dx          /*   so add digits to get intended size */
  523.  
  524. zMax:
  525. I    cmp     cx, __XCVTDIG__ /* assumed limit to caller's buffer     */
  526. I    jna     zLimited
  527. I    mov     cx, __XCVTDIG__
  528.  
  529. zLimited:
  530.  
  531. I    mov    BY0 (ES_ [bx]), 0 /* make sure null terminated */
  532.  
  533. I    mov     ax, bx
  534. I    sub     ax, strP        /* calculate actual digits      */
  535. I    sub     cx, ax
  536. I    jna     end             /* all digits have been delivered       */
  537.  
  538. appendZloop:
  539.  
  540. I    mov     W0 (ES_ [bx]), '0'      /* extend the string    */
  541. I    inc     bx
  542. I    loop    appendZloop
  543.  
  544. end:
  545. I    cld                     /* reinstate default, forwards string */
  546. I    pop     ES
  547.  
  548.     return _DX;        /* returns decimal exponent of the number. */
  549. }
  550. #pragma warn .use
  551.