home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 7 / 07.iso / c / c082_122 / 2.ddi / MATHSRC.ZIP / XCVT.CAS < prev   
Encoding:
Text File  |  1992-06-10  |  20.4 KB  |  581 lines

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