It is not $\simeq 537\textrm{ or }1073$
It's close, but it's not accurate. Each hash attempt has a $ \frac{1}{2^{32}} $ chance of matching a single key, the probability of not matching any of the $4\cdot 10^6$ keys is $ \left(1-\frac{1}{2^{32}}\right)^{4\cdot 10^6} $, and since the average of the geometric distribution is the inverse of the probability of success we have:
$$ \frac{1}{1-\left(1-\frac{1}{2^{32}}\right)^{4\cdot 10^6}}=1074.\small 241\scriptsize 901\tiny 485213438\dots $$
and if your good with a 50% chance of failure then the median of the geometric distribution gives us (just rearranged from the above formula):
$$ \frac{\log_{1-\frac{1}{2^{32}}}{1/2}}{4\cdot 10^6}=744.\small 261\scriptsize 117\tiny 8682496203\dots $$
These numbers are accurate and precise.
For those of you who demand more precision:
#include <gmp.h>
#include <mpfr.h>
#include <stdlib.h>
void handle_oom() {
/* safe clean up */
/* for those who *demand* reliability in the face of uncertainty */
/* for those who deem unconditional and immediate program termination to be unthinkably reckless */
/* for those of us who deem the corruption of personal data to be a programming SIN */
/* for the rest of us ... */
exit(EXIT_FAILURE);//there is always religious hypocrisy...
}
void*alloc_func_ptr(size_t alloc_size) {void*result=malloc(alloc_size);if(result==NULL)handle_oom();return result;}
void*realloc_func_ptr(void*ptr,size_t old_size,size_t new_size){void*result=realloc(ptr,new_size);if(result==NULL)handle_oom();return result;}
void free_func_ptr(void*ptr,size_t size){free(ptr);}
int main(void) {
mpfr_t result;
mpfr_t a;
mp_set_memory_functions(alloc_func_ptr,realloc_func_ptr,free_func_ptr);
mpfr_set_default_prec(69);
mpfr_init_set_ui(result, 2, MPFR_RNDN);//2
mpfr_pow_ui(result, result, 32, MPFR_RNDN);//2^32
mpfr_ui_div(result, 1, result, MPFR_RNDN);//1/2^32
mpfr_ui_sub(result, 1, result, MPFR_RNDN);//1-1/2^32
mpfr_pow_ui(result, result, 4000000,MPFR_RNDN);//(1-1/2^32)^4000000
mpfr_ui_sub(result,1,result,MPFR_RNDN);//1-(1-1/2^32)^4000000
mpfr_ui_div(result,1,result,MPFR_RNDN);//1/(1-(1-1/2^32)^4000000)
mpfr_printf("%.19RNg\n", result);
mpfr_init(a);
//mpfr_set_ld(a, 0.367708147258652352659885967356423464025283, MPFR_RNDN);
mpfr_set_ld(a, 0.5, MPFR_RNDN);//1/2
mpfr_log(a, a, MPFR_RNDN);//log(1/2)
mpfr_set_ui_2exp(result,1,32,MPFR_RNDN);//2^32
mpfr_ui_div(result,1,result,MPFR_RNDN);//1/2^32
mpfr_ui_sub(result,1,result,MPFR_RNDN);//1-1/2^32
mpfr_log(result,result,MPFR_RNDN);//log(1-1/2^32)
mpfr_div(result,a,result,MPFR_RNDN);//log(1/2)/log(1-1/2^32)
mpfr_div_ui(result,result,4000000,MPFR_RNDN);//log(1/2)/log(1-1/2^32)/4000000
mpfr_printf("%.19RNg\n", result);
mpfr_clear(result);//because you never know where your code will run
mpfr_clear(a);//some simple embedded hardware OSs might not reclaim the memory on their own...
//and we wouldn't want some dumb rocket to fall out of the sky now do we...
//because that's what happens when you get lazy with code
return EXIT_FAILURE;//because life sucks, and your all going to die
//plus if your reading this you really are a failure
//go do something useful!
}
and remember: if you forget to add the "$\tiny\dots$" at the end of at least one number, you'r a liar, a damned liar.