Files
TeslaRel410/BORLAND/BC45/SOURCE/RTL/SOURCE/MATHLIB/XCVT.CAS
T
CydandClaude Fable 5 63312e07f9 source410: literal 4.10 source reconstruction + BC++ 4.52 fleet toolchain archived
- BORLAND/: Borland C++ 4.52 (chosen over 4.5 by byte-match: CODE/RP/CW32.LIB
  is identical to 4.52's install lib). BCC32/TLINK32/TLIB/MAKE run natively on
  Win11; CODE/BT/OPT.MAK is the shipped BTL4OPT.EXE's exact flag recipe
  (extender = Borland PowerPack DPMI32, not Phar Lap TNT).
- restoration/source410/: the literal 1995-form reconstruction of the missing
  BT game source (never mixed into CODE/). Round 1-3 state:
  * 6 of 10 surviving original TUs COMPILE CLEAN under the period toolchain
    (BTMSSN, BTCNSL, BTSCNRL, BTTEAM, BTL4MODE, BTL4ARND) - first builds
    since 1996.
  * BT_L4/BTL4APP.CPP pilot reconstruction: 12/12 functions, Fail() lands on
    its binary-recorded line 400 exactly.
  * BT/BTCNSL.HPP: console wire IDs recovered from the binary's ctors
    (Killed=9, Damaged=10, ScoreUpdate=13, DeathWithoutHonor=15 [T1];
    TeamScore=12 flagged [T4]).
  * MUNGA/: 8 engine-header backfills back-dated from the BT412 WinTesla tree
    (VDATA numbering decomp-verified; AUDREND's OpenAL-era virtual removed -
    the period compiler is the drift detector).
  * Tooling: backdate.py (WinTesla->1995 header transform), compile410.sh
    (per-TU verification sweep under authentic OPT.MAK flags).
  * README: corrected roadmap - MECH.HPP is the capstone grown with the mech
    TU reconstructions; BTREG.CPP green = the header-family milestone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 07:33:26 -05:00

583 lines
20 KiB
Plaintext

/*------------------------------------------------------------------------
* filename - xcvt.cas
*
* function(s)
* __xcvt - converts a double value to an ASCIIZ string
*-----------------------------------------------------------------------*/
/*
* C/C++ Run Time Library - Version 6.5
*
* Copyright (c) 1987, 1994 by Borland International
* All Rights Reserved.
*
*/
#pragma inline
#include <asmrules.h>
#include <_printf.h>
#include <_math.h>
#include <math.h> /* for pow10 */
#define I asm
#ifdef _WINDOWS
extern near _Cdecl void _fbstp(void);
#endif
#if LPROG
#define EXTPROC1(x) asm call far ptr (x)
#else
#define EXTPROC1(x) asm call near ptr (x)
#endif
/*--------------------------------------------------------------------------*
Name __xcvt - convert double/long double value to ASCIIZ string
Usage short pascal near __xcvt(void *valP,
short ndigits,
int *signP,
char *strP,
int ftype)
Prototype in _printf.h
Description The double/long double (*valP) is converted to a decimal
string (*strP) of up to 18 digits, a sign (*signP,
false == positive) and a decimal exponent (the function
return value).
"ndigits" specifies how the number should be rounded. If
positive, then ndigits specifies the maximum number of
digits. Otherwise, ndigits specifies the maximum number of
fractional decimals (to the right of the decimal point). If
|ndigits| is > 18 then ndigits will be limited to +-18.
The string is in ASCIIZ form. The string is padded with
zeros to the right to fill in the requested number of
digits or decimal places.
The exponent is calculated as if the decimal point were at
the left (most significant) end of the string (there is no
"." character in the string). If the value was zero then
the exponent is set to zero.
If the value was zero then the exponent is 0 and the string
is all "0". If the value was infinite or NAN then the
exponent is MAXSHORT and the string is all "9".
The ftype parameter will be :
2 - FLOAT
6 - DOUBLE
8 - LONG DOUBLE
The numbers correspond to the offset of the exponent word from
the start of the number.
Return value __xcvt returns the decimal exponent of the number.
Note: A #define in '_printf.h' can be used to enable recognition of floats
as well as doubles and long doubles. This feature may be disabled
though because it isn't strictly ANSI standard. The code in this module
is set up to recognize floats but the higher modules will never pass
the float flag unless the variable is defined in _float.h
*---------------------------------------------------------------------------*/
#define MaxSigDigits 18
#pragma warn -use
int pascal near
__xcvt(void *valP, int digits, int *signP, char *strP, int ftype)
{
unsigned Sign = 0x8000;
short ten = 10;
short SW; /* iNDP status word */
char frac [10]; /* tenbyte BCD integer */
/* caller expects ES to be preserved! */
I push ES
#if (! LDATA)
I mov ax, ds
I mov es, ax
#endif
/*
Convert parm to 'long double' and store locally. We ZAP the sign
bit out of the number on the stack before loading it, but after
saving the exponent word in CX. This saves having to do a FABS
later on which saves lots of time if this code is running emulated
and really doesn't cost any more than a real FABS running on a 8087
in terms of speed.
*/
I LES_ di, valP /* ES:DI <- pointer to value */
I mov ax, 7FFFH /* Mask for sign zapping */
I mov bx, ftype /* types are 2,6 or 8 */
I mov cx, es:[bx+di] /* Get original exponent word */
I and Sign, cx /* Save original sign */
I and es:[bx+di], ax /* Zap the sign bit */
I shr bx, 1 /* Make 'type' into 0,2 or 4 */
I shr bx, 1 /* and do an indexed jump to */
I shl bx, 1 /* the right load instr. */
I jmp word ptr cs:type_table[bx]
#pragma warn -asm
I type_table LABEL NEAR
I dw F4bytes /* 4 byte 'float' */
I dw F8bytes /* 8 byte 'double' */
I dw F10bytes /* 10 byte 'long double' */
I F4bytes LABEL NEAR
I FLD FLOAT (es:[di]) /* Load 32 bit 'float' */
I jmp short its_loaded
I F8bytes LABEL NEAR
I FLD DOUBLE (es:[di]) /* Load 64 bit 'double' */
I jmp short its_loaded
I F10bytes LABEL NEAR
/* hack a few bits off of normals (please don't ask why) */
I and ax, es:[di+8]
I cmp ax, 7FFFh
I je F10bytesHacked
I and BY0(es:[di]), 0F0H /* Can't print em' anyway */
F10bytesHacked:
I FLD LONGDOUBLE (es:[di]) /* Load 80 bit 'long double' */
#pragma warn .asm
/* Take original exponent word's sign & return it to caller. */
its_loaded:
I xor bx, bx
I shl cx, 1 /* CF <- sign */
I rcl bx, 1 /* BX <- sign */
I LES_ di, signP /* Store result in caller space */
I mov ES_ [di], bx
/*
Weed out all the 'strange' numbers here(0, Infinity & NANs).
The format of C0, C1, C2 & C4 in the status word is:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--
C3 C2 C1 C0
----- upper byte ------+----- lower byte ------
AH AL
C3 C2 C1 C0
-----------
0 0 0 0 +Unnormal
0 0 0 1 +NAN
0 0 1 0 -Unnormal (*)
0 0 1 1 -NAN (*)
0 1 0 0 +Normal
0 1 0 1 +Infinity
0 1 1 0 -Normal (*)
0 1 1 1 -Infinity (*)
1 0 0 0 +Zero
1 0 0 1 Empty
1 0 1 0 -Zero (*)
1 0 1 1 Empty
1 1 0 0 +Denormal
1 1 0 1 Empty
1 1 1 0 -Denormal (*)
1 1 1 1 Empty
(*) We'll never see these in operation because we've zapped the
sign bit before loading the number (it was saved though
before being clobbered).
Note : 286/287 specific porters.
The 80287/80387 know how to do a FSTSW directly into the
AX register.
*/
I FXAM
I FSTSW SW /* Get the 87' Status */
I FWAIT
I mov ax, SW /* Load up the status word */
I and ah, 47H /* Mask out uninteresting stuff */
/*
Zero is the most likely 'strange' number, so it's checked first.
Remember, signs were zapped above so we only need to look for the
positive cases!
*/
I cmp ah, 40H /* +0 */
I je zero
I cmp ah, 05H /* +INF */
I je its_infinity
I cmp ah, 01H /* +NAN */
I je its_NAN
goto normal;
/************************************************************************
* Special representations for 0, Infinity and NAN values. *
************************************************************************/
its_NAN:
I mov dx, NAN_number /* dx = NAN flag */
I jmp short pop_and_go
its_infinity:
I mov dx, INF_number /* dx = Infinity flag */
I jmp short pop_and_go
/* True zero and 'rounds to zero' results wind up here */
zero:
roundToZero:
I mov dx, 1 /* We really want 0.0E+01 */
I mov al, '0'
I LES_ di, signP /* Eliminate negative zero */
I mov W0 (ES_ [di]), 0
extreme:
I mov cx, digits /* fill caller's string with */
I or cx, cx /* either all zeros or */
I jg extSized /* all nines. */
I neg cx
I inc cx /* digit left of decimal point */
extSized:
I cmp cx, __XCVTDIG__ /* limit caller's buffer */
I jbe extLimited
I mov cx, __XCVTDIG__
extLimited:
I cld /* Fill in & NULL terminate str */
I LES_ di, strP
I rep stosb
I xor al, al
I stosb
pop_and_go:
I FSTP ST(0) /* clear X from stack */
goto end;
/************************************************************************
* Normal numbers are not zero, infinite or NANs. *
* *
* Note: upon arrival here -- *
* 87' TOS contains the number to convert *
************************************************************************/
normal:
/*
How many decimal places are there in the number? It is not good
to use the log10 function for two reasons:
- it is slow and clumsy (even when not emulated)
- the answer can be wrong: it is somewhat rare but rounding errors
in the log function can cause the wrong number of digits.
An alternative method is to make a swift estimate of the log, then
check it later. So long as the error is at most one digit up or
down, and happens in a minority of cases, performance will be
reasonable. We can form the estimate by multiplying the binary
exponent by a conversion factor Log10of2. Since 16 bit accuracy
is OK at this stage, it is possible to use fixed point arithmetic
on the main CPU.
*/
/*** FST won't do temp-reals so we have to use FSTP ***/
I FLD st(0) /* Duplicate the number */
I FSTP LONGDOUBLE (frac) /* Save long double form */
I FWAIT
I mov ax, frac[8] /* Get new 80 bit #'s exp word */
I sub ax, 3FFFH /* Remove exponent bias */
I mov dx, 4D10h /* 10000h * Log10of2, rounded. */
I imul dx
I xchg ax, bx
I mov ah, 4DH
I mov al, frac[7]
I shl al, 1
I mul ah
I add ax, bx
I adc dx, 0
I neg ax
I adc dx, 0 /* DX = estimated exponent */
/*
Now we are ready to do the rounding. DX estimates the decimal digits
left of the decimal point. AX contains the requested precision.
*/
I mov ax, digits
I or ax, ax /* -,0,+ = decimals, dflt, digits */
I jg digitPlaces
/*
The caller has requested (-AX) decimals following the decimal point.
*/
I neg ax
I add ax, dx /* AX = equivalent signif. digits */
I jl roundToZero /* Ignore if it rounds to zero */
/*
The caller has requested (AX) significant digits (approximately).
This is now limited to 18, the maximum precision convertible by
the iNDP-87 (equivalent to around 59 bits of precision: double
precision in C is 53 bits, roughly 16 decimals). Zeros will be
appended later to make up the extra digits requested.
*/
digitPlaces:
I cmp ax, MaxSigDigits
I jng defaultPlaces
I mov ax, MaxSigDigits
/*
Now the number is scaled to place the requested number of digits
left of the decimal point, and that number is rounded and converted
to a BCD integer. Upon arrival here:
DX is the estimated decimal magnitude of the number
AX is the number of leading digits required
*/
defaultPlaces:
I mov bx, ax /* BX = safe copy of AX */
I sub ax, dx
powloop:
I jz adjusted /* 10^0 == 1, so skip the multiply/divide */
I mov si, ax
I jnl power10
I neg ax
power10:
/* pow10 may ret +INF, which would wreck things */
I cmp ax, 4932
I jle getpow
I mov ax, 4932
getpow:
I push ax
EXTPROC1 (pow10) /* leaves result in ST */
I pop ax
/*
Now the value 10^(|SI|) is on TOS. That is multiplied or divided
with the value in TOS(1) to yield a number with an integral part
probably having just the number of wanted digits.
*/
I or si, si
I jg increase
I FDIV
I add ax, si
I jmp short powloop
increase:
I FMUL
I xchg ax, si
I sub ax, si
I jmp short powloop
/*
Before unpacking the TOS, we must check that the number of actual
decimals is correct, since up till now everything has depended on
an estimate.
*/
adjusted:
I push bx
EXTPROC1 (pow10) /* leaves result in ST */
I pop ax
I FCOMP /* cmp ST, ST(1), then pop */
I FSTSW SW
I FWAIT
I test BY1 (SW), 45h /* test C3, C2, C0 */
I jz notTooHigh /* all zero implies ST > ST(1) */
/*
If arrived here then the number is too high. The error is never
as great as tenfold, so divide by 10 to correct it.
*/
I inc dx /* correct the estimate of decimals */
I inc bx /* and size of result string */
I cmp bx, MaxSigDigits
I ja mustShorten
I cmp W0 (digits), 0 /* is format F or E ? */
I jng notTooLow
mustShorten:
I FIDIV W0 (ten) /* E formats: maintain requested */
I dec bx /* count of digits */
I jmp short notTooLow
/*
If arrived here the number was not too high, but may be too low.
*/
notTooHigh:
I mov ax, bx
I dec ax
I push ax
EXTPROC1 (pow10) /* leaves result in ST */
I pop ax
I FCOMP /* cmp ST, ST(1), then pop */
I FSTSW SW
I FWAIT
I test BY1 (SW), 41h /* test C3, C0 */
I jnz notTooLow /* either non-zero implies ST <= ST(1 */
/*
Adjust upward tenfold to correct the alignment.
*/
I dec dx /* correct the estimate of decimals */
I dec bx /* and size of result string */
I cmp W0 (digits), 0 /* is format F or E ? */
I jng notTooLow
I FIMUL W0 (ten) /* E formats: maintain requested */
I inc bx /* count of digits */
/*
Now convert the number in TOS into a decimal integer of up to 18
digits. The default rounding mode applies.
*/
notTooLow:
I or bx,bx /* If precision < 0, number rounds to zero */
I jl jmp_roundToZero
I FRNDINT /* FBSTP does not round properly ! */
#ifdef _WINDOWS
I mov di, ss /* ES:DI -> frac */
I mov es, di
I lea di, frac
I push bx /* save misc. registers */
I push dx
_fbstp(); /* convert TOS to packed BCD in frac */
I pop dx
I pop bx
#else
I FBSTP frac
#endif
I LES_ di, strP /* Locate the end of string .. */
I add di, bx
I push di /* .. remember it for later .. */
I xor al, al /* .. and put the zero terminator there. */
I std /* fill the string in reverse order */
I stosb
/*
Locate the fraction.
*/
I lea si, frac
I mov cx, 4 /* CL = nibble shift, CH = round-up flag */
/*
The CH flag is necessary because the rounding to integer can change
a 999.. value to 1000.. by rounding up. In that case the number
of digits changes, and we will not scan as far as the '1' digit.
The CH flag accumulates the OR of all digits: if it remains zero,
then we know we have a round-up problem.
*/
I FWAIT /* wait for conversion to finish. */
I or bx, bx
I jnz nextPair
/*
Fractions which may round up to 1 are checked here as a special case.
*/
I mov ch, SS_ [si]
I xor ch, 1 /* enable round-up if is 1. */
I jz short maybeRoundup
jmp_roundToZero:
I FLDZ /* Load dummy to discard */
I pop di /* Remove saved DI */
I jmp roundToZero /* print as zero if it is 0.*/
/*
Note that string direction is reversed, least significant digits are
converted first.
*/
nextPair:
I mov al, SS_ [si] /* convert the packed BCD .. */
I inc si
I mov ah, al
I shr ah, cl
I and al, 0Fh
I add ax, 3030h /* '00' */ /* .. to ASCII decimals */
I stosb
I or ch, al /* accumulate non-zero digits */
I dec bx
I jz maybeRoundup
I mov al, ah
I stosb
I or ch, al /* accumulate non-zero digits */
I dec bx
I jnz nextPair
maybeRoundup:
I pop bx /* remember end-of-string position. */
I and ch, 0Fh /* were any non-zero digits seen ? */
I jnz append
/*
If all zeros, then we can assume the leading digit will be '1'
due to a round-up. Increment DX to correct the estimated digits.
*/
I inc dx
I cmp W0 (digits), 0
I jg put1
I mov BY0 (ES_ [bx]), '0'
put1:
I inc bx /* also increment count of digits */
I mov BY0 (ES_ [di+1]), '1'
/*
The caller may want more than 18 digits. We oblige, with limits,
by appending zeros up to the intended length.
*/
append:
I mov cx, digits
I or cx, cx
I jg zMax
I neg cx /* request was for fixed decimals */
I add cx, dx /* so add digits to get intended size */
zMax:
I cmp cx, __XCVTDIG__ /* assumed limit to caller's buffer */
I jna zLimited
I mov cx, __XCVTDIG__
zLimited:
I mov BY0 (ES_ [bx]), 0 /* make sure null terminated */
I mov ax, bx
I sub ax, strP /* calculate actual digits */
I sub cx, ax
I jna end /* all digits have been delivered */
appendZloop:
I mov W0 (ES_ [bx]), '0' /* extend the string */
I inc bx
I loop appendZloop
end:
I cld /* reinstate default, forwards string */
I LES_ di, valP /* ES:DI <- pointer to value */
I mov bx, ftype /* types are 2,6 or 8 */
I mov cx, Sign /* Get original sign and restore it */
I or es:[bx+di], cx
I pop ES
return _DX; /* returns decimal exponent of the number. */
}
#pragma warn .use