scalbn.s   [plain text]


//  double scalbn( double x, int n );
//  double ldexp( double x, int n );
//  double scalb( double x, double n );
//  double scalbln( double x, long int n );
//
//  Returns x * 2^n computed efficiently, without undue over/underflow.
//  Adapted from Ian Ollmann's scalbn implementation of 2005.
//  
//  -- Stephen Canon, January 2010

.globl _scalbn
.globl _ldexp
.globl _scalb
.globl _scalbln

#if defined __x86_64__

.literal8
threek:  .quad 0x40a7700000000000
mthreek: .quad 0xc0a7700000000000

    #define     floatval    -24(%rsp)
    #define     n           %edi
    #define     ln          %rdi
    #define     clamphi     threek(%rip)
    #define     clamplo     mthreek(%rip)
  
.text
.align 4
_scalb:
    movsd       %xmm0,      floatval
    pcmpeqd     %xmm0,      %xmm0
    fldl        floatval                // load x on x87
    psllq       $63,        %xmm0       // conjure  long dbl mantissa of 2^n
    ucomisd     %xmm1,      %xmm1       // if isnan(n)
    jp          L_nIsNaN                //    branch to return n
    
    minsd       clamphi,    %xmm1       // clamp n to [-3000, 3000]
    maxsd       clamplo,    %xmm1
    cvtsd2si    %xmm1,      n           // convert clamped n to integer
    jmp         L_common                // and jump to the shared scalbn path
    
L_nIsNaN:
    fstp        %st(0)
    movapd      %xmm1,      %xmm0       // since n is a NaN, we just return n
    ret

.align 4
_scalbln:
    movsd       %xmm0,      floatval
    pcmpeqd     %xmm0,      %xmm0
    fldl        floatval                // load x on x87
    psllq       $63,        %xmm0       // conjure  long dbl mantissa of 2^n
    mov         $3000,      %rdx
    cmp         %rdx,       ln          // if n > 3000
    cmovg       %rdx,       ln          //    n == 3000
    neg         %rdx
    cmp         %rdx,       ln          // if n < -3000
    cmovl       %rdx,       ln          //    n == -3000
    jmp         L_common

.align 4
_scalbn:
_ldexp:
    movsd       %xmm0,      floatval
    fldl        floatval                // load x on x87
    pcmpeqd     %xmm0,      %xmm0
    psllq       $63,        %xmm0       // conjure  long dbl mantissa of 2^n
    mov         $3000,      %edx
    cmp         %edx,       n           // if n > 3000
    cmovg       %edx,       n           //    n == 3000
    neg         %edx
    cmp         %edx,       n           // if n < -3000
    cmovl       %edx,       n           //    n == -3000
    
L_common:    
    add         $0x3fff,    n           // long dbl exponent field of 2^n
    pinsrw      $4, n,      %xmm0       // insert exponent to make 2^n
    movdqa      %xmm0,      floatval    
    fldt        floatval
    fmulp                               // x * 2^n
    fstpl       floatval                // round to double
    movsd       floatval,   %xmm0
    ret
    
#elif defined __i386__

.literal8
threek:  .quad 0x40a7700000000000
mthreek: .quad 0xc0a7700000000000

    #define     floatval    4(%esp)
    #define     exponent    12(%esp)
    #define     n           %eax
    #define     nw          %ax
    #define     clamphi     (threek-0b)(%ecx)
    #define     clamplo     (mthreek-0b)(%ecx)

.text
.align 4
_scalb:
    call        0f    
0:  pop         %ecx                    // conjure pic info
    movsd       12(%esp),   %xmm1       // load n
    fldl        floatval                // load x on x87
    ucomisd     %xmm1,      %xmm1       // if isnan(n)
    jp          L_nIsNaN                //    branch to return n
    
    fld1
    fstpt       floatval                // conjure 1.0L
    minsd       clamphi,    %xmm1       // clamp n to [-3000, 3000]
    maxsd       clamplo,    %xmm1
    cvtsd2si    %xmm1,      n           // convert clamped n to integer
    jmp         L_common                // and jump to the shared scalbn path
    
L_nIsNaN:
    fstp        %st(0)
    fldl        12(%esp)                // n is nan, return n
    ret

.align 4
_scalbln:
_scalbn:
_ldexp:
    mov         12(%esp),   n           // load n
    fldl        floatval                // load x on x87
    fld1
    fstpt       floatval                // conjure 1.0L
    mov         $3000,      %edx
    cmp         %edx,       n           // if n > 3000
    cmovg       %edx,       n           //    n == 3000
    mov         $-3000,     %edx
    cmp         %edx,       n           // if n < -3000
    cmovl       %edx,       n           //    n == -3000
    
L_common:
    add         nw,         exponent    // 2^n in memory
    fldt        floatval
    fmulp                               // x * 2^n
    fstpl       floatval                // round to double
    fldl        floatval
    ret
    
#else
    #error "This implementation supports 32- and 64-bit Intel only"
#endif