|
Boost : |
From: Paul A. Bristow (boost_at_[hidden])
Date: 2002-07-08 01:18:58
Some time ago I started what became a long discussion about the presentation
of math constants, like pi.
Eventually these discussions proved inconclusive and there seemed no
acceptable solution,
although there was agreement that collection(s) of highly accurate constants
were needed.
I have been waiting for some time so see if new ideas would emerge,
and if new compiler versions would change Boosters views.
GCC 3.1 and MSVC 7.0 are now in wide use,
and Michael Kenniston has provided a new (second) proposal for avoiding the
main dispute.
To summarize for new readers:
(for details see much updated html documentation and the previous constants
in the files section)
http://groups.yahoo.com/group/boost/files/MathConstants/Math_constants.htm
1 We agree on the need for accurate math constants
- pi, e, sqrt(2) and i for a start.
2 (Physical constants are a separate matter for their accuracy is not
constant).
3 Some constants cannot be calculated accurately by current compilers
because they use 'built-in' hardware floating-point, and this is unlikely to
change.
Only constants calculated with a higher precision software system, like NTL,
will ensure that the constant is stored as accurately as the floating point
hardware will allow.
4 40 decimal digits are sufficient for all practical purposes,
and compilers are (or really should be) capable of reading decimal digits
into their various internal formats (float, double, long double).
5 A C++ naming scheme for math constants has been devised
which is at least acceptable (if a compromise).
6 Control of precision is essential, but there is opposition to a C-style
solution
using three names for each constant, for example float pi_F, double pi_D,
long double pi_L.
Nor, in Boost discussions, did use of namespaces to separate different
precisions seem acceptable.
7 There are dozens of constants that some users rate 'essential'.
Splitting into several #include files still risks violating the "don't pay
for what you don't use" principle.
8 Only MACROS provide a simple way of avoiding some cost for constants you
don't use,
but Boosters expressed very strong reluctance to MACROs because they pollute
the global namespace,
even if #undefs are provided.
(Only edit cut 'n' paste "const double pi = 3.14...; area = 3.14159 *
radius * radius"
avoids any runtime cost - will the embedded coders do this anyway?)
9 Users extremely strongly prefer to write "float area = pi * r * r;"
10 There is evidence that some compilers can generate better code from
functions like
"double pi() { return 3.1459;}"
but that this implies writing pi() instead of plain "long double pi".
So the ()s remain the main issue.
Michael Kenniston had a previous interesting suggestion
but which did not seem entirely satisfactory.
He has now produced another which is attached.
Briefly, this allows 'novice' users to write "pi" but
get the effect of "pi()" without too many precision surprises,
yet still allows the 'expert' to write "pi()" and control precision
explicitly.
A few example below, and details in attached files.
pi.zip contains a simple demo of how it works -
with an seriously reduced accuracy for pi - before you get excited!
real_constants.zip gives more examples, including a complex constant,
and some tests. The outputs using MSVC 7.0 are appended as a comment.
The downsides of Kenniston's Kunning Scheme 2 are, as I see it:
1 It stresses compilers - there is disagreement about what should compile.
2 It stresses the C++ language definition.
Is a explicit struct constructor required even if it is not used?
The language lawyers disagree.
This may be a significant issue if many constants are provided.
3 It stresses compilers ability to optimise away unused bits,
without which there is a significant risk of code bloat which
might negate any advantage of using functions over constants.
This could be a major nuisance in debug mode when optimisation may be
lacking,
even if the release code is ideal.
In the face of all these conflicting arguments,
my instinct was (and remains) to provide ALL the options
and see which proved popular.
I therefore propose to offer for formal review:
math_constants will contain a few essential like pi, e.
math_constants_2 will contain constants which
cannot be calculated accurately by compilers, like 1/3.
math_constants_3 will contain more obscure or convenient constants.
1 Three files of constants as MACROs BOOST_MATH_CONSTANT_PI
2 Three files of float constants float pi = 3.14F.
3 Three files of double constants. double pi = 3.14
4 Three files of long double constants long double pi = L.
5 Three files of functions long double pi(){ return 3.14159...}
6 Three files of Kenniston style constants which allow pi or pi()
However I am unwilling to put in the significant effort to
automatically generate these using NTL high precision
unless Boosters can agree in principle on the file contents.
So I would like to re-open the discussion with some sample files:
http://groups.yahoo.com/group/boost/files/MathConstants/pi.zip
http://groups.yahoo.com/group/boost/files/MathConstants/real_constants.zip
Paul
Paul A Bristow
Prizet Farmhouse
Kendal, Cumbria
LA8 8AB UK
+44 1539 561830
Mobile +44 7714 33 02 04
Mobile mailto:pabristow_at_[hidden]
mailto:pbristow_at_[hidden]
http://groups.yahoo.com/group/boost/files/MathConstants/Math_constants.htm
http://groups.yahoo.com/group/boost/files/MathConstants/pi.zip
http://groups.yahoo.com/group/boost/files/MathConstants/real_constants.zip
extract from test_pi.cpp from pi.zip
// Some very simple examples:
// Note that the precision of pi is seriously reduced to make it
// obvious whether result is float 3.1F, double 3.141, or long double
3.14159L.
// This is only for this demo of course!!!
{ // Declare factor and result are float, so implicit use of pi invokes a
float, and result is float.
float radius = 1.; // Unity so precision of result is obvious.
float area = pi * radius * radius;
cout << "float area = pi * radius * radius is " << area << endl;
// result is 3.1 so pi is a float.
}
{ // Now change to use double, so implicit use of pi invokes a double, and
result is double.
double radius = 1.;
double area = pi * radius * radius;
cout << "double area = pi * radius * radius is " << area << endl;
// result is 3.141 so pi is now a double. Magic!
}
{ // Now change to use long double, so implicit use of pi invokes a long
double, and result is long double.
long double radius = 1.;
long double area = pi * radius * radius;
cout << "long double area = pi * radius * radius is " << area << endl;
// result is 3.14159 so pi is now a double. More Magic!
}
// But there are some possible surprises!!!
{ // Change to use float radius and a double area.
float radius = 1.;
double area = pi * radius * radius;
cout << "Using float radius, double area = pi * radius * radius is " <<
area << endl;
// Result is 3.1 so pi is now float.
// Definition of operator* means that result of pi * radius is FLOAT,
// despite area being a double!
// Snare for the unwary?
}
{ // Now change to use a double radius and a float area.
double radius = 1.; // Definition of operator* means that result is
double.
// If we write:
// float area = pi * radius * radius;
// then get the expected warning:
// "C4244: 'initializing' : conversion from 'double' to 'float', possible
loss of data."
// Or if we write:
float area = static_cast<float>(pi * radius * radius); // Avoids warning!
cout << "float area = static_cast<float>(pi * radius * radius) is " <<
area << endl;
// result is 3.141 so pi is now appears to be double precision.
// But if precision of constant corresponded to true precision of float,
// it would have been reduced back to float precision? Or nearly??
}
{
// Examples of explicit namespace resolution:
cout << "boost::math::real_cast<float>(pi) is " <<
boost::math::real_cast<float>(pi) << endl;
cout << "boost::math::real_cast<double>(pi) is " <<
boost::math::real_cast<double>(pi) << endl;
cout << "boost::math::real_cast<long double>(pi) is " <<
boost::math::real_cast<long double>(pi) << endl;
}
{ // If want three C style constants, we could write:
float pi_f = boost::math::real_cast<float>(pi);
double pi_d = boost::math::real_cast<double>(pi);
long double pi_l = boost::math::real_cast<long double>(pi);
// and use them for example thus:
cout << "float pi_f is " << pi_f << endl;
cout << "double pi_d is " << pi_d << endl; // pi_d NOT pi - see below.
cout << "long double pi_l is " << pi_l << endl;
// But we cannot write:
// cout << "pi is " << pi << endl;
// "error C2593: 'operator <<' is ambiguous"
// Check that pi really is const.
// pi = 3.; // Naughty! (Only politicians are allowed to do this!)
// But it is correctly caught by the compiler:
// "error C2679: binary '=' : no operator defined which takes a right-hand
operand of type 'const double' "
// boost::math::real_cast<float>(pi) = 3.; // Also naughty! "error C2106:
'=' : left operand must be l-value"
}
Paul A Bristow
Prizet Farmhouse
Kendal, Cumbria
LA8 8AB UK
+44 1539 561830
Mobile +44 7714 33 02 04
Mobile mailto:pabristow_at_[hidden]
mailto:pbristow_at_[hidden]
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk