I have this, which may be wrong as I've not tested it a whole lot. The constants at start are "close enough", I did not try to be 100% precise:
47 + inputs * (39+(mixin+1)*2) + outputs*38 + 64 * inputs * (mixin+1)
If you want to know whether a fee is acceptable before it is actually sent, you can use
set always-confirm-transfers 1 in simplewallet, and you'll always be prompted with the transaction before it is sent to the daemon.
For RingCT, the calculation is more complicated, see https://github.com/moneromooo-monero/bitmonero/blob/rct/src/wallet/wallet2.cpp, estimate_rct_tx_size (which is also a good approximation only, not exact).
The main reason for "not exact" is that integers tend to be stored as variable length, depending on their value.
In both cases, a tx_extra of 40 bytes is assumed (typically, a payment id). You can remove 34 bytes if there's none.