Avoid double-to-int conversion overflows in modp_dtoa functions

Those methods already had a fallback to use sprintf() for large values
except:

* The check-for-large-value was unnecessarily done after many
  operations that aren't relevant to the check and those operations can
  result in a conversion overflow (undefined behavior).

* The check-for-large-value was using the literal value for a
  32-bit INT_MAX instead of just using INT_MAX.  For a platform where
  `int` is less than 32-bits, the same conversion overflow from the
  previous point could still occur (undefined behavior).

* The check-for-large-value was not inclusive of INT_MAX.
  In a case where the conversion of INT_MAX itself to a double
  can't be represented exactly, it's implementation-defined whether
  the closest higher or closest lower representable-value is selected.
  If the higher value is selected, then a `double` value comparing equal
  to INT_MAX-as-converted-to-double would cause an overflow of an `int`
  upon conversion (undefined behavior).
This commit is contained in:
Jon Siwek 2020-09-17 16:43:04 -07:00
parent 264e6858f2
commit d25ead8f8e

View file

@ -6,6 +6,7 @@
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include <limits.h>
// other interesting references on num to string convesion
// http://www.jb.man.ac.uk/~slowe/cpp/itoa.html
@ -88,8 +89,28 @@ void modp_dtoa(double value, char* str, int prec)
str[0] = 'n'; str[1] = 'a'; str[2] = 'n'; str[3] = '\0';
return;
}
/* we'll work in positive values and deal with the
negative sign issue later */
int neg = 0;
if (value < 0) {
neg = 1;
value = -value;
}
/* if input is larger than thres_max, revert to exponential */
const double thres_max = (double)(0x7FFFFFFF);
const double thres_max = (double)(INT_MAX);
/* for very large numbers switch back to native sprintf for exponentials.
anyone want to write code to replace this? */
/*
normal printf behavior is to print EVERY whole number digit
which can be 100s of characters overflowing your buffers == bad
*/
if (value >= thres_max) {
sprintf(str, "%e", neg ? -value : value);
return;
}
double diff = 0.0;
char* wstr = str;
@ -101,16 +122,6 @@ void modp_dtoa(double value, char* str, int prec)
prec = 9;
}
/* we'll work in positive values and deal with the
negative sign issue later */
int neg = 0;
if (value < 0) {
neg = 1;
value = -value;
}
int whole = (int) value;
double tmp = (value - whole) * _pow10[prec];
uint32_t frac = (uint32_t)(tmp);
@ -129,17 +140,6 @@ void modp_dtoa(double value, char* str, int prec)
++frac;
}
/* for very large numbers switch back to native sprintf for exponentials.
anyone want to write code to replace this? */
/*
normal printf behavior is to print EVERY whole number digit
which can be 100s of characters overflowing your buffers == bad
*/
if (value > thres_max) {
sprintf(str, "%e", neg ? -value : value);
return;
}
if (prec == 0) {
diff = value - whole;
if (diff > 0.5) {
@ -189,8 +189,27 @@ void modp_dtoa2(double value, char* str, int prec)
return;
}
/* we'll work in positive values and deal with the
negative sign issue later */
int neg = 0;
if (value < 0) {
neg = 1;
value = -value;
}
/* if input is larger than thres_max, revert to exponential */
const double thres_max = (double)(0x7FFFFFFF);
const double thres_max = (double)(INT_MAX);
/* for very large numbers switch back to native sprintf for exponentials.
anyone want to write code to replace this? */
/*
normal printf behavior is to print EVERY whole number digit
which can be 100s of characters overflowing your buffers == bad
*/
if (value >= thres_max) {
sprintf(str, "%e", neg ? -value : value);
return;
}
int count;
double diff = 0.0;
@ -203,16 +222,6 @@ void modp_dtoa2(double value, char* str, int prec)
prec = 9;
}
/* we'll work in positive values and deal with the
negative sign issue later */
int neg = 0;
if (value < 0) {
neg = 1;
value = -value;
}
int whole = (int) value;
double tmp = (value - whole) * _pow10[prec];
uint32_t frac = (uint32_t)(tmp);
@ -231,17 +240,6 @@ void modp_dtoa2(double value, char* str, int prec)
++frac;
}
/* for very large numbers switch back to native sprintf for exponentials.
anyone want to write code to replace this? */
/*
normal printf behavior is to print EVERY whole number digit
which can be 100s of characters overflowing your buffers == bad
*/
if (value > thres_max) {
sprintf(str, "%e", neg ? -value : value);
return;
}
if (prec == 0) {
diff = value - whole;
if (diff > 0.5) {
@ -301,21 +299,6 @@ void modp_dtoa3(double value, char* str, int n, int prec)
return;
}
/* if input is larger than thres_max, revert to exponential */
const double thres_max = (double)(0x7FFFFFFF);
int count;
double diff = 0.0;
char* wstr = str;
if (prec < 0) {
prec = 0;
} else if (prec > 9) {
/* precision of >= 10 can lead to overflow errors */
prec = 9;
}
/* we'll work in positive values and deal with the
negative sign issue later */
int neg = 0;
@ -324,32 +307,23 @@ void modp_dtoa3(double value, char* str, int n, int prec)
value = -value;
}
int whole = (int) value;
double tmp = (value - whole) * _pow10[prec];
uint32_t frac = (uint32_t)(tmp);
diff = tmp - frac;
if (diff > 0.5) {
++frac;
/* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */
if (frac >= _pow10[prec]) {
frac = 0;
++whole;
}
} else if (diff == 0.5 && ((frac == 0) || (frac & 1))) {
/* if halfway, round up if odd, OR
if last digit is 0. That last part is strange */
++frac;
if (prec < 0) {
prec = 0;
} else if (prec > 9) {
/* precision of >= 10 can lead to overflow errors */
prec = 9;
}
/* if input is larger than thres_max, revert to exponential */
const double thres_max = (double)(INT_MAX);
/* for very large numbers switch back to native sprintf for exponentials.
anyone want to write code to replace this? */
/*
normal printf behavior is to print EVERY whole number digit
which can be 100s of characters overflowing your buffers == bad
*/
if (value > thres_max) {
if (value >= thres_max) {
/* ---- Modified part, compared to modp_dtoa3. */
int i = snprintf(str, n, "%.*f", prec, neg ? -value : value);
@ -373,6 +347,28 @@ void modp_dtoa3(double value, char* str, int n, int prec)
/* ---- End of modified part.. */
}
int count;
double diff = 0.0;
char* wstr = str;
int whole = (int) value;
double tmp = (value - whole) * _pow10[prec];
uint32_t frac = (uint32_t)(tmp);
diff = tmp - frac;
if (diff > 0.5) {
++frac;
/* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */
if (frac >= _pow10[prec]) {
frac = 0;
++whole;
}
} else if (diff == 0.5 && ((frac == 0) || (frac & 1))) {
/* if halfway, round up if odd, OR
if last digit is 0. That last part is strange */
++frac;
}
if (prec == 0) {
diff = value - whole;
if (diff > 0.5) {