Search code examples
linuxassemblyx86attfpu

Changing FPU rounding mode


I would like to know, what values should I use to change FPU rounding mode.

.data
nearest:
    ??
down:
    ??
up:
    ??
zero:
    ??
.text
.global round
    pushl   %ebp
    movl    %esp, %ebp
    movl 8(%ebp), %ecx

    cmpl $1, %ecx
je
    fldcw nearest

    cmpl $2, %ecx
je
    fldcw down

    cmpl $3, %ecx
je
    fldcw up

    cmpl $4, %ecx
je
    fldcw zero
leave
ret

I found something like this:

down:
    .byte 0x7f, 0x07
up:
    .byte 0x7f, 0x0b

but I don't know why somebody used it. I know I should change 8 and 9 bits, like this: 00 — round to nearest 01 — round down (toward negative infinity) 10 — round up (toward positive infinity) 11 — round toward zero


Solution

  • The type of rounding is determined by two bits in the control word of the FPU. You can get information about the FPU here: http://www.website.masmforum.com/tutorials/fptute/fpuchap1.htm. It's a little bit tricky to change only these two bits and left the others unchanged. Take a look at my example. I tried to be as close as possible to your code:

    .data
        num:        .double 6.5     # play with the number!
    
        old:        .word 0
        nearest:    .word 0x0000
        down:       .word 0x0400
        up:         .word 0x0800
        zero:       .word 0x0C00
        result:     .double 0
        fmt1:       .asciz "nearest: %f -> %f\n"
        fmt2:       .asciz "down:    %f -> %f\n"
        fmt3:       .asciz "up:      %f -> %f\n"
        fmt4:       .asciz "zero:    %f -> %f\n"
    
    .text
    .globl main
    main:
    
        fstcw old               # store old control word
    
        movw old, %ax
        andb $0b11110011, %ah   # clear RC field
        orw %ax, nearest
        orw %ax, down
        orw %ax, up
        orw %ax, zero
    
        fldcw nearest           # banker's rounding
        call round
        mov $fmt1, %esi
        call out
    
        fldcw down              # down toward -infinity
        call round
        mov $fmt2, %esi
        call out
    
        fldcw up                # up toward +infinity
        call round
        mov $fmt3, %esi
        call out
    
        fldcw zero              # truncating
        call round
        mov $fmt4, %esi
        call out
    
        fldcw old               # restore old control word
    
        xor %eax, %eax          # exit(0)
        ret                     # GCC only needs RET
    
    round:
        fldl num
        frndint
        fstpl result
        ret
    
    out:
        push result+4
        push result
        push num+4
        push num
        push %esi
        call printf             # "%f" needs a double
        add $20, %esp
        ret