The first part used a hardware for something that is more often calculated in software. This part does exactly opposite.
The second part of the binary is littered with constants like 0x43340000, 0x4CBEBC20, 0x40490FDB, 0x3DCCCCCD. Too big to be typical integers, too irregular to be addresses, too close to each other to be hashes... typical floating-point values. For example, `struct.unpack('<f', struct.pack('<I', 0x40490FDB))` returns `(3.1415927410125732,)` that should be quite recognizable :) Moreover, some functions begin with dissecting their arguments into 1+8+23 bitfields which is exactly how single-precision floating-point values are organized.
The code sequence "print `Parameter 0? [`, call some function with a value, print `]`, call another function with a pointer" suggests that the function at 0x41B8 prints floats, and the function at 0x4288 reads them from the user. Then goes an identification of floating-point emulation functions. One possible way is to read the code of every function and deduce what it does. Another way is to look at how they are called and think how I would write printing and reading floating-point numbers. Floating-point emulation has a lot of branches and special cases, I find the second way easier, but your mileage can wary. Anyway, functions at 0x5538 and 0x55E0 are comparisons (take two floating-point numbers and return -2/-1/0/1; presumably one handles strict comparisons `<`/`>` while another handles `<=`/`>=`, and the compiler cannot substitute `a>=b` with `!(a