I'm trying to write good round_double function which will round double in specified precision:
1.
double round_double(double num, int prec)
{
for (int i = 0; i < abs(prec); ++i)
if(prec > 0)
num *= 10.0;
else
num /= 10.0;
double result = (long long)floor(num + 0.5);
for (int i = 0; i < abs(prec); ++i)
if(prec > 0)
result /= 10.0;
else
result *= 10.0;
return result;
}
2.
doub开发者_StackOverflowle round_double(double num, int prec)
{
double tmp = pow(10.0, prec);
double result = (long long)floor(num * tmp + 0.5);
result /= tmp;
return result;
}
This functions do what I wan't but they are, in my opinion, not good enough. Because starting from precision = 13 - 14, they returning bad results.
The cause I'm sure that there is possible to write good double_round is that just printing the number via cout
in specified precision (say 18) is prints better result than result of my function.
For example this part of code:
int prec = 18;
double num = 10.123456789987654321;
cout << setiosflags(ios::showpoint | ios::fixed)
<< setprecision(prec) << "round_double(" << num << ", "
<< prec << ") = " << round_double(num, prec) << endl;
Will print round_double(10.123456789987655000, 18) = -9.223372036854776500
for first round_double
and round_double(10.123456789987655000, 18) = -9.223372036854776500
for second one.
How write good round_double function in c++? Or there is already exists?
Don't cast to long long
that is forcing a conversion to an integer with limited range, beyond what 10^13 requires (well 19 for 64-bit with no whole number part). Just calling floor
should be enough.
double round_double(double num, int prec)
{
double tmp = pow(10.0, prec);
double result = floor(num * tmp + 0.5);
result /= tmp;
return result;
}
Note that Mike is also correct, you have a limited range you can represent just in double itself. It isn't so great if you need clean decimal responses. But the long long
is the cause of your totally wacky numbers.
The problem is the floating-point representation. A binary representation cannot represent all decimal numbers exactly, and only has a finite precision.
double
usually means a 64-bit binary representation as specified by IEEE754, with a 52-bit fractional part. This gives a precision of approximately 16 decimal digits.
If you need more precision than that, then the best option is probably to use an arbitrary-precision arithmetic library such as GMP. Your compiler may or may not offer a long double
type with a higher precision than double
.
EDIT: sorry, I didn't notice that you're getting completely incorrect results. As another answer says, this is due to the conversion to long long
overflowing.
Another approach is to round based on binary-digits of precision. Sample implementation below - not sure if it's useful to you, but since you got me playing I thought I'd throw it out there.
Notes:
- this uses the ieee754.h header common on Linux systems: it could easily be ported to Windows, but this is undeniably bit hackery and whether it's appropriate in any given production code is a case-by-case call.
- you could approximate some decimal near-equivalent, e.g. multiply the desired decimal precision by 10 and divide by 3 (based on 2^10 ~= 10^3).
The input number (10.1234...) with 1 bit of precision is 8; with 2 it's 10 etc..
Separately, IMHO decimal rounding is best done at output time, or when using a decimal-capable representation (e.g. storing an int
mantissa and power-of-10 exponent).
#include <ieee754.h>
#include <iostream>
#include <iomanip>
double round_double(double d, int precision)
{
ieee754_double* p = reinterpret_cast<ieee754_double*>(&d);
std::cout << "mantissa 0:" << std::hex << p->ieee.mantissa0
<< ", 1:" << p->ieee.mantissa1 << '\n';
unsigned mask0 = precision < 20 ? 0x000FFFFF << (20 - precision) :
0x000FFFFF;
unsigned mask1 = precision < 20 ? 0 :
precision == 53 ? 0xFFFFFFFF :
0xFFFFFFFE << (32 + 20 - precision);
std::cout << "masks 0:" << mask0 << ", 1: " << mask1 << '\n';
p->ieee.mantissa0 &= mask0;
p->ieee.mantissa1 &= mask1;
std::cout << "mantissa' 0:" << p->ieee.mantissa0
<< ", 1:" << p->ieee.mantissa1 << '\n';
return d;
}
int main()
{
double num = 10.123456789987654321;
for (int prec = 1; prec <= 53; ++prec)
std::cout << std::setiosflags(std::ios::showpoint | std::ios::fixed)
<< std::setprecision(60)
<< "round_double(" << num << ", " << prec << ") = "
<< round_double(num, prec) << std::endl;
}
Output...
mantissa 0:43f35, 1:ba76eea7
masks 0:fff80000, 1: 0
mantissa' 0:0, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 1) = 8.000000000000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:fffc0000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 2) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:fffe0000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 3) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:ffff0000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 4) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:ffff8000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 5) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:ffffc000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 6) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:ffffe000, 1: 0
mantissa' 0:42000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 7) = 10.062500000000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:fffff000, 1: 0
mantissa' 0:43000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 8) = 10.093750000000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:7ffff800, 1: 0
mantissa' 0:43800, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 9) = 10.109375000000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:3ffffc00, 1: 0
mantissa' 0:43c00, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, a) = 10.117187500000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:1ffffe00, 1: 0
mantissa' 0:43e00, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, b) = 10.121093750000000000000000000000000000000000000000000000000000
mantissa 0:43f35, 1:ba76eea7
masks 0:fffff00, 1: 0
mantissa' 0:43f00, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, c) = 10.123046875000000000000000000000000000000000000000000000000000
etc....
精彩评论