I'm not too good with FPU... I'm kinda feeling my way along. Last time I tried to do a floating point compare, "fcomi" was an illegal instruction on that machine, and I had to do it the old fashioned way... "fcomi" is much nicer!
Your immediate problem seems to be that "ffree" marks the register empty, but doesn't pop the stack. I dealt with this by making a "local variable" (sub esp, 4 - if you do this, do "mov esp, ebp" at the end of your function!), and "fstp dword [ebp -4]" to get rid of st0. There's probably a better way.
You neglect to bump esi in the "loop", so you keep loading the second float. Easily fixed.
Then I found that "_exchange_min" was never being executed (I put print_char with 'i' in "min" and 'a' in "max" for debugging purposes - inspired by how much "dump_math" helps with debugging!) . You've got "jl", which should be right for signed comparisons, and floats are, by their nature, signed. But I think (and experiment seems to confirm it) that "fcomi" sets the flags as if an unsigned comparison had happened. I changed your code to "jb" and "ja", and it seems to work better. (actually, changing just jl to jb worked, but I changed jg to ja too... for consistency...).
I fiddled with a few other things - bumped "N" in the caller up to 7 so I'd have a few more values to play with... put another "dump_math" right at the exit from your function... I've got it so that, at function exit "min" is in st0, and "max" is in st1. I don't know where you want to go from there.
Anyway... try popping st0 into a "dummy" variable with "fstp dword [dummy]" instead of "ffree", and see if that helps...
Best,
Frank