As for how, start by looking at how you should round the (positive) float to the nearest integer. Casting a float to an int truncates it. Adding 0.5 to the (positive) float will increment the integer part exactly when we want to round (when the decimal part is> = 0.5). This gives the following:
double round(double x) { return (long long)(x + 0.5); }
To add support for the precision parameter, note that (e.g. round(123456.789, -3) ) adding 500 and truncating in thousands of places is essentially the same as adding 0.5 and rounding to the nearest integer, it's just that decimal point in a different position. To move the radius point around, we need left and right shift operations that are equivalent to multiplying by the base raised to the shift amount. That is, 0x1234 >> 3 coincides with 0x1234 / 2**3 and 0x1234 * 2**-3 in base 2. In base 10:
123456.789 >> 3 == 123456.789 / 10**3 == 123456.789 * 10**-3 == 123.456789
For round(123456.789, -3) this means that we can do the above multiplication to move the decimal point, add 0.5, truncate, and then do the opposite multiplication to move the decimal point back.
double round(double x, double p) { return ((long long)((x * pow10(p))) + 0.5) * pow10(-p); }
Rounding by adding 0.5 and truncating works fine for non-negative numbers, but it rounds the wrong path for negative numbers. There are several solutions. If you have an effective sign() function (which returns -1, 0, or 1, depending on whether the number is <0, == 0, or> 0, respectively), you can:
double round(double x, double p) { return ((long long)((x * pow10(p))) + sign(x) * 0.5) * pow10(-p); }
If not, there are:
double round(double x, double p) { if (x<0) return - round(-x, p); return ((long long)((x * pow10(p))) + 0.5) * pow10(-p); }