I am currently programming on 64-bit Fedora 36, and I realized that GMP floating point numbers have limitations on the exponent size: https://gmplib.org/manual/Floating_002dpoint-Functions
The exponent of each float has fixed precision, one machine word on most systems. In the current implementation the exponent is a count of limbs, so for example on a 32-bit system this means a range of roughly 2^-68719476768 to 2^68719476736, or on a 64-bit system this will be much greater
For example, the following C program prints 0.1e-3215911262793760767 on my machine.
#include <assert.h>
#include <stdio.h>
#include <gmp.h>
int main(void) {
mpf_t f;
const char *s = "1e3000000000000000000000000000000";
assert(mpf_init_set_str(f, s, 10) == 0);
assert(mpf_out_str(NULL, 10, 100, f));
printf("\n");
}
This problem also happens when using the C++ interface. The following C++ program outputs 1e+-1294967296:
#include <iostream>
#include <gmpxx.h>
int main(void) {
mpf_class f("1e3000000000");
std::cout << f << std::endl;
}
Is there a way to detect the exponent overflow? For example, I am expecting mpf_init_set_str() to return a non-zero value to indicate the error. Or a C++ exception can be raised while initializing mpf_class f. However, currently the floats initialize successfully to the wrong value. Otherwise, is this a bug in GMP?
This is not a bug. This is documented in the GMP manual:
The 'mpf' functions and variables have no special notion of infinity or not-a-number, and applications must take care not to overflow the exponent or results will be unpredictable.
Basically, overflow on mpf numbers is undefined behavior. If you want well-defined behavior, you should use GNU MPFR instead.
Yes it is a GMP bug. Report it! The current implementation can result in an undefined behavior, even a segmentation fault. You can see that at this.
Related
I am getting an overflow error when calling mpz_pow_ui from gmp, with the maximum unsigned long int value.
According to the signature here, this is supposed to work, shouldn’t it?
What am I missing?
Example:
example.cpp:
#include <gmp.h>
#include <limits>
int main(){
unsigned long int exp = std::numeric_limits<unsigned long int>::max();
mpz_t result;
mpz_t three;
mpz_init(three);
mpz_set_ui(three, 3);
mpz_pow_ui(result, three, exp);
}
$ gcc -oexample example.cpp -lgmp
$ ./example
gmp: overflow in mpz type
Aborted
Common beleif is that GMP is truly an arbitrary precision software, from the front page:
What is GMP?
GMP is a free library for arbitrary precision arithmetic, operating on signed integers, rational numbers, and floating-point numbers. There is no practical limit to the precision except the ones implied by the available memory in the machine GMP runs on.
But that is (unfortunately) not true. A grep on the GMP code for the error overflow in mpz type shows it appears in mpz/init2.c, mpz/realloc.c, and mpz/realloc2.c. Basically (if you are not running on a CRAY machine), the abort you got happens here:
if (UNLIKELY (new_alloc > INT_MAX))
{
fprintf (stderr, "gmp: overflow in mpz type\n");
abort ();
}
Thus whenever you try to create or calculate a number with more than 2^31 - 1 limbs (or machine words), GMP will abort. This is because the limbs of a number are accessed by an int index:
typedef struct
{
int _mp_alloc; /* Number of *limbs* allocated and pointed to by the _mp_d field. */
int _mp_size; /* abs(_mp_size) is the number of limbs the last field points to. If _mp_size is negative this is a negative number. */
mp_limb_t *_mp_d; /* Pointer to the limbs. */
} __mpz_struct;
...
typedef __mpz_struct mpz_t[1];
So, there is a practical limit for what GMP can calculate (note that on most machines you will won't have that much RAM anyway, but there are special computers with very large RAM, and those require long indexing).
EDIT: INT_MAX is a definition from the standard (Appendix E - Implementation limits) defintion of numeric limits, limits.h, which was included in GMP in gmp-impl.h in 2014:
# define INT_MAX 2147483647
EDIT 2: The OP asks in a comment why is the parameter to mpz_pow_ui an unsigned long int and not an unsigned int, since it cannot compute with all of the values of an unsigned long int exponent. This is because GMP is, according to the front page:
What is GMP? GMP is a free library for arbitrary precision arithmetic, operating on signed integers, rational numbers, and floating-point numbers. There is no practical limit to the precision except the ones implied by the available memory in the machine GMP runs on. GMP has a rich set of functions, and the functions have a regular interface.
Well, all GMP functions named with an ui work with an unsigned long int, see mpz_get_ui, mpz_add_ui, mpq_set_ui, mpz_fac_ui, mpf_add_ui, and many others.
Its just not worth to mix arbitrary precision mpn_, mpz_, and mpq_ functions with simple ints or unsigned ints, since these are trivially converted to longs. That's one of the reasons they say the interface is regular, all functions named ui work with unsigned long ints and all functions named si work with long ints, no function that mixes native integer types with "arbitrary precision" integers work with simple non-long ints.
The pow functions are among the few of GMP's ui functions that cannot produce a result for a long argument, so it makes no sense to make only those functions to work with simple non-long ints, since this would confuse the interface.
But I agree that GMP maintainers should be more clear about the limitations of the library.
There are various points to be considered in your code;
You have to init every variable. You can also use Combined Initialization and Assignment Functions
void mpz_init_set (mpz_t rop, const mpz_t op)
void mpz_init_set_ui (mpz_t rop, unsigned long int op)
void mpz_init_set_si (mpz_t rop, signed long int op)
void mpz_init_set_d (mpz_t rop, double op)
You have to free the memory with mpz_clear or mpz_clears.
You use C++ to code, however, you are using C API of GNU/GMP. You may consider using the C++ interface.
The main function should return. If there is no return value, 0 is returned by default. Even in the void case, some programmers always prefer a return statement.
The exponent is too big; 3^18446744073709551615. That is ~10^18 digits
#include <gmp.h>
#include <limits>
int main(){
unsigned long int exp = std::numeric_limits<unsigned long int>::max();
mpz_t result;
mpz_t three;
//init the memory
mpz_init(three);
mpz_init(result);
mpz_set_ui(three, 3);
mpz_pow_ui(result, three, exp);
//free the memory
mpz_clear(three);
mpz_clear(result);
return 0;
}
I was trying to handle the integer division by zero (please don't judge, I was told I had to use <csignal> lib and I couldn't just use an if statement), but I needed to make sure the program would keep running (even though it's a very bad practice), instead of crashing or closing. The weird part is that the program should only handle division by zero but should exit for every other type of SIGFPE.
SideNote: Now, I have no idea why they use names like FPU or FE or FPE when referring to integer "exceptions" (or I should say interrupts) since the standard says clearly that dividing a floating point number should return either inf or nan for 0 / 0 (tell me if I'm wrong).
Anyway, I wrote this test code so I could better understand what I needed to do before the actual implementation. I know, it's weird to have x as a global variable but if I don't reset it, it will keep calling handle for no reason like forever....
#include <iostream>
#include <csignal>
using namespace std;
int x = 0;
void handle(int s);
int main(int argc, char * argv[]) {
signal(SIGFPE, handle);
cout << "Insert 0: ";
cin >> x; // here I would input 0, so the program can compile
x = 5 / x;
cout << "X: " << x << endl;
return 0;
}
void handle(int s) {
if (s != FPE_INTDIV) exit(1);
cout << "sig: " << s << endl;
x = 1;
}
As you can see I used FPE_INTDIV to rule out every other type of exceptions, but it doesn't work.
Eventually I discovered that FPE_INTDIV is a symbolic constant for 7 (that's what vs-code's intellisense tells me) and if I were to print the value of s, that would be 8. I discovered that, strangely enough, 8 is the value for FPE_INTOVF on which the documentation states that it's specifically designed for integer overflows.
Why on earth is the symbolic value for overflows used for integer division if there is a symbolic for integer division? What am I missing? Did someone mess up the values in the library? Am I using the wrong macros?
I should also mention, this code compiles fine with clang++ and g++ but when compiled on a Windows computer with cl, it tells me there's no macro for FPE_INTDIV.
How can I be sure of what I'm doing and write a cross platform solution that works?
I already feel like an idiot.
It's defined as:
The SIGFPE signal reports a fatal arithmetic error. Although the name is derived from “floating-point exception”, this signal actually covers all arithmetic errors, including division by zero and overflow. If a program stores integer data in a location which is then used in a floating-point operation, this often causes an “invalid operation” exception, because the processor cannot recognize the data as a floating-point number.
There's no reason for it to be labelled specifically FPE but these sorts of labels can evolve in unpredictable ways. I wouldn't read too much into it.
These signals are part of the POSIX standard and may not be fully supported or implemented in Windows. The Windows implementation of these support facilities is lacking in a number of areas, like how fork() is unsupported.
For example:
float a = 3.14159f;
If I was to inspect the bits in this number (or any other normalized floating point number), what are the chances that the bits are different in some other platform/compiler combination, or is that possible?
Not necessarily: The c++ standard doesn't define floating point representation (it doesn't even define the representation of signed integers), although most platforms probably orient themselves on the same IEEE standard (IEEE 754-2008?).
Your question can be rephrased as: Will the final assertion in the following code always be upheld, no matter what platform you run it on?
#include <cassert>
#include <cstring>
#include <cstdint>
#include <limits>
#if __cplusplus < 201103L // no static_assert prior to C++11
#define static_assert(a,b) assert(a)
#endif
int main() {
float f = 3.14159f;
std::uint32_t i = 0x40490fd0;// IEC 659/IEEE 754 representation
static_assert(std::numeric_limits<float>::is_iec559, "floating point must be IEEE 754");
static_assert(sizeof(f) == sizeof(i), "float must be 32 bits wide");
assert(std::memcmp(&f, &i, sizeof(f)) == 0);
}
Answer: There's nothing in the C++ standard that guarantees that the assertion will be upheld. Yet, on most sane platforms the assertion will hold and the code won't abort, no matter if the platform is big- or little-endian. As long as you only care that your code works on some known set of platforms, it'll be OK: you can verify that the tests pass there :)
Realistically speaking, some compilers might use a sub-par decimal-to-IEEE-754 conversion routine that doesn't properly round the result, so if you specify f to enough digits of precision, it might be a couple of LSBs of mantissa off from the value that would be nearest to the decimal representation. And then the assertion won't hold anymore. For such platforms, you might wish to test a couple mantissa LSBs around the desired one.
I am running a simulation of physical experiments, so I need really high floating point precision (more than 16 digits). I use Boost.Multiprecision, however I can't get a precision higher than 16 digits, no matter what I tried. I run the simulation with C++ and eclipse compiler, for example:
#include <boost/math/constants/constants.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>
#include <limits>
using boost::multiprecision::cpp_dec_float_50;
void main()
{
cpp_dec_float_50 my_num= cpp_dec_float_50(0.123456789123456789123456789);
std::cout.precision(std::numeric_limits<cpp_dec_float_50>::digits10);
std::cout << my_num << std::endl;
}
The output is:
0.12345678912345678379658409085095627233386039733887
^
But it should be:
0.123456789123456789123456789
As you can see, after 16 digits it is incorrect. Why?
Your issue is here:
cpp_dec_float_50 my_num = cpp_dec_float_50(0.123456789123456789123456789);
^ // This number is a double!
The compiler does not use arbitrary-precision floating point literals, and instead uses IEEE-754 doubles, which have finite precision. In this case, the closest double to the number you have written is:
0.1234567891234567837965840908509562723338603973388671875
And printing it to the 50th decimal does indeed give the output you are observing.
What you want is to construct your arbitrary-precision float from a string instead (demo):
#include <boost/math/constants/constants.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>
#include <limits>
using boost::multiprecision::cpp_dec_float_50;
int main() {
cpp_dec_float_50 my_num = cpp_dec_float_50("0.123456789123456789123456789");
std::cout.precision(std::numeric_limits<cpp_dec_float_50>::digits10);
std::cout << my_num << std::endl;
}
Output:
0.123456789123456789123456789
The problem is that the C++ compiler converts numbers to doubles when compiling (I also learned this a while ago). You have to use special functions to handle more decimal points. See the Boost documentation or other answers here on SO for examples.
That said, it is almost impossible that there would be any real need for such high precision. If you are loosing precision you should consider other floating point algorithms instead of blindly increasing the number of decimals.
I was just wondering how disastrous integer overflow really is. Take the following example program:
#include <iostream>
int main()
{
int a = 46341;
int b = a * a;
std::cout << "hello world\n";
}
Since a * a overflows on 32 bit platforms, and integer overflow triggers undefined behavior, do I have any guarantees at all that hello world will actually appear on my screen?
I removed the "signed" part from my question based on the following standard quotes:
(§5/5 C++03, §5/4 C++11) If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.
(§3.9.1/4) Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2^n where n is the number of bits in the value representation of that particular size of integer. This implies that unsigned arithmetic does not overflow because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting unsigned integer type.
As pointed out by #Xeo in the comments (I actually brought it up in the C++ chat first):
Undefined behavior really means it and it can hit you when you least expect it.
The best example of this is here: Why does integer overflow on x86 with GCC cause an infinite loop?
On x86, signed integer overflow is just a simple wrap-around. So normally, you'd expect the same thing to happen in C or C++. However, the compiler can intervene - and use undefined behavior as an opportunity to optimize.
In the example taken from that question:
#include <iostream>
using namespace std;
int main(){
int i = 0x10000000;
int c = 0;
do{
c++;
i += i;
cout << i << endl;
}while (i > 0);
cout << c << endl;
return 0;
}
When compiled with GCC, GCC optimizes out the loop test and makes this an infinite loop.
You may trigger some hardware safety feature. So no, you don't have any guarantee.
Edit:
Note that gcc has the -ftrapv option (but it doesn't seem to work for me).
There are two views about undefined behavior. There is the view it is there to gather for strange hardware and other special cases, but that usually it should behave sanely. And there is the view that anything can happen. And depending on the UB source, some hold different opinions.
While the UB about overflow has probably been introduced for taking into account hardware which trap or saturate on overflow and the difference of result between representation, and so one can argue for the first view in this case, people writing optimizers hold very dearly the view that if the standard doesn't guarantee something, really anything can happen and they try to use every piece of liberty to generate machine code which runs more rapidly, even if the result doesn't make sense anymore.
So when you see an undefined behavior, assume that anything can happen, however reasonable a given behavior may seem.