The reason you see the results you do is:
When you pass an int argument but use a printf specifier for double (remember that a float is converted to a double in this situation), then most C implementations pass the int argument according to their usual rules for passing a int argument to a variadic function (a function that accepts diverse argument types), but the printf routine interprets the machine state as if it were passed a double argument, as described below. (This is not necessarily what always happens; once you leave the behavior defined by the C standard, a C implementation may do other things. In particular, there can be complex interactions with the optimizer that cause surprising results. However, this is what happens most commonly. You cannot rely on it.)
Each computing platform has some rules for how arguments are passed. One platform might specify that all arguments are pushed onto the stack, from right to left, and that each argument is put onto the stack using only as many bytes as it needs. Another platform might specify that arguments are pushed onto the stack left to right or that arguments are padded up to the next multiple of four bytes, to keep the stack nicely aligned. Many modern platforms specify that integer arguments under a certain size are passed in general registers, floating-point arguments are passed in floating-point registers, and other arguments are passed on the stack.
When printf sees %f and looks for a double argument, but you have passed an int, what does printf find? If this platform pushes both arguments onto the stack, then printf finds the bits for your int, but it interprets those bits as if they were a double. This results in printf printing a value determined by your int, but it bears no obvious relationship to your int because the bits have entirely different meanings in the encodings for int and for double.
If this platform puts an int argument in one place but a double argument in another place, then printf finds some bits that have nothing at all to do with your int argument. They are bits that just happened to be left over in, for example, the floating-point register where the double argument should be. Those bits are just the residue of previous work. The value you get will be essentially random with respect to the int you have passed. You can also get a mix, with printf looking for eight bytes of a double by taking four bytes of the int you passed along with four bytes of whatever else was nearby.
When you run the program multiple times, you will often see the same value printed. This happens for two reasons. First, computers are mechanical. They operate in largely deterministic ways, so they do the same things over and over again, even if those things were not particularly designed to be used the way you are using them. Second, the environment the operating system passes to the program when it starts is largely the same each time you start the program. Most of its memory is either cleared or is initialized from the program file. Some of the memory or other program state is initialized from other environment in the computer. That data can be different from run to run of the program. For example, the current time obviously changes from run to run. So does your command history, plus values the command shell has placed in its environment variables. Sometimes running a program multiple times will produce different results.
When you use code whose behavior is not defined by some specification (which may be the C specification, a compiler specification, a machine and operating system specification, or other documents), then you cannot rely on the behavior of that code. (It is possible to rely on the behavior of code compiled by a particular C compiler that is specified by that C compiler even though it is not fully specified by the C standard.)