2

I wrote this code in NASM:

section .data
    fvar: dd 123.456
    fsig: dq 0.0
    fexp: dq 0.0

section .text
    fld dword[fVar]
    fxtract          ; put significand in ST(0), and exponent in ST(1)
    fstp qword[fsig] ; fsig = 1.929
    fstp qword[fexp] ; fexp = 6

I was waiting to find: fsig = 123456 and fexp = -3.
Or something like: fsig = 1.23456 and fexp = 3.
So, what Am I missing?

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Bite Bytes
  • 1,455
  • 8
  • 24

1 Answers1

3

fxtract gives you the exponent base 2, as @Jester said in the comments.

  • 123.456 = 1.929 × 26

To get the exponent base 10, you must know about logarithms. You calculate the logarithm base 10 of the input, then truncate it to an integer. In x87, fldlg2 gives log10(x), then fyl2x can calculate the logarithm as

  • log10(x) = log10(2) × log2(x)

To truncate it to an integer, you set the x87 to round toward zero (by oring 0x0c000 into the control word) and use frndint.

To calculate the significand, you divide the input by the power of 10. The customary way to get a power of 10 is to use powers of 2 and 5, using integer arithmetic for powers of 5 (as in David M. Gay's pow5mult), and scaling the floating-point exponent for powers of 2. A simpler, but perhaps slower or less accurate way, is to use the x87 and the formula

  • 10p = 2m = 2r × 2m - r where m = log210 × p and r = round(m)

In x87, fldl2t provides log2(10). f2xm1 calculates 2x - 1 if x is a fraction between -1 and 1. fscale multiplies by 2r if r is an integer.

The code is

section .data
    fvar: dd 123.456
    fsig: dq 0.0
    fexp: dq 0.0

section .bss
    newcw: resw 1
    oldcw: resw 1

section .text
global main
main:
    fld dword[fvar]
    ;; fexp = truncate(log_10(fvar))
    fld st0
    fldlg2
    fxch st1            ; st2 = fvar, st1 = log_10(2), st0 = fvar
    fyl2x               ; log_10(fvar) = log_10(2) * log_2(fvar)
    fstcw [oldcw]
    mov dx, [oldcw]
    or  dx, 0x0c000     ; rounding mode = 3, toward zero
    mov [newcw], dx
    fldcw [newcw]
    frndint             ; truncate log_10(fvar)
    fldcw [oldcw]       ; restore old rounding mode
    fst qword[fexp]
    ;; fsig = fvar / 10^(fexp)
    fldl2t              ; st2 = fvar, st1 = fexp, st0 = log_2(10)
    fmulp               ; m = log_2(10) * fexp
    fld st0
    frndint             ; integral part of m
    fxch st1            ; st2 = fvar, st1 = integer, st0 = m
    fsub st0, st1       ; fractional part of m
    f2xm1
    fld1
    faddp               ; 2^(fraction)
    fscale              ; 10^fexp = 2^(integer) * 2^(fraction)
    fstp st1            ; st1 = fvar, st0 = 10^fexp
    fdivp               ; fvar / 10^fexp
    fstp qword[fsig]
    int 3

I have added the label main and the int 3 so I can run this in gdb on OpenBSD/amd64.

$ nasm -felf64 float10.s && gcc -nopie -o float10 float10.o 
$ gdb float10
...
(gdb) run
...
Program received signal SIGTRAP, Trace/breakpoint trap.
...
(gdb) x/1wf &fvar
0x601000 <fvar>:        123.456001
(gdb) x/1wg &fsig
0x601004 <fsig>:        1.2345600128173828
(gdb) x/1wg &fexp
0x60100c <fexp>:        2
George Koehler
  • 1,560
  • 17
  • 23
  • If I try this code (and I did) fvar can't be negative. It gives NaN on fyl2x. To extend this nice example you should check the sign before taking logarithms. Anyway nice example. – Agguro Feb 14 '18 at 06:51