Причудливое поведение с плавающей точкой с против без дополнительных переменных, почему?

Когда я запускаю следующий код в VC ++ 2013 (32-разрядная, без оптимизации):

#include <cmath>
#include <iostream>
#include <limits>

double mulpow10(double const value, int const pow10)
{
static double const table[] =
{
1E+000, 1E+001, 1E+002, 1E+003, 1E+004, 1E+005, 1E+006, 1E+007,
1E+008, 1E+009, 1E+010, 1E+011, 1E+012, 1E+013, 1E+014, 1E+015,
1E+016, 1E+017, 1E+018, 1E+019,
};
return pow10 < 0 ? value / table[-pow10] : value * table[+pow10];
}

int main(void)
{
double d = 9710908999.008999;
int j_max = std::numeric_limits<double>::max_digits10;
while (j_max > 0 && (
static_cast<double>(
static_cast<unsigned long long>(
mulpow10(d, j_max))) != mulpow10(d, j_max)))
{
--j_max;
}
double x = std::floor(d * 1.0E9);
unsigned long long y1 = x;
unsigned long long y2 = std::floor(d * 1.0E9);
std::cout
<< "x == " << x << std::endl
<< "y1 == " << y1 << std::endl
<< "y2 == " << y2 << std::endl;
}

я получил

x  == 9.7109089990089994e+018
y1 == 9710908999008999424
y2 == 9223372036854775808

в отладчике.

Я ошеломлен Может кто-нибудь, пожалуйста, объясните мне, как, черт возьми y1 а также y2 имеют разные значения?


Обновить:

Это только кажется, что происходит под /Arch:SSE2 или же /Arch:AVXне /Arch:IA32 или же /Arch:SSE,

8

Решение

Вы конвертируете вне диапазона double значения для unsigned long long, Это не разрешено в стандарте C ++, и Visual C ++, кажется, обрабатывает это действительно плохо в режиме SSE2: он оставляет число в стеке FPU, в конечном итоге переполняет его и делает более поздний код, использующий FPU, очень интересным.

Уменьшенный образец

double d = 1E20;
unsigned long long ull[] = { d, d, d, d, d, d, d, d };
if (floor(d) != floor(d)) abort();

Это прерывает, если ull имеет восемь или более элементов, но проходит, если имеет до семи.

Решение не состоит в том, чтобы преобразовать значения с плавающей запятой в целочисленный тип, если вы не знаете, что значение находится в диапазоне.

4.9. Плавающие-интегральные преобразования [conv.fpint]

Значение типа с плавающей запятой может быть преобразовано в значение типа целого числа. Усечение преобразования; то есть дробная часть отбрасывается. Поведение не определено, если усеченное значение не может быть представлено в типе назначения. [ Замечания: Если тип назначения boolсм. 4.12. — конечная нота ]

Правило, что значения вне диапазона переносятся при преобразовании в тип без знака, применяется только в том случае, если значение уже имеет некоторый целочисленный тип.

Однако, что бы это ни стоило, это не кажется преднамеренным, поэтому, несмотря на то, что стандарт допускает такое поведение, все же стоит сообщить об этом как об ошибке.

4

Другие решения

9223372036854775808 является 0x8000000000000000; то есть оно равно INT64_MIN приведение к uint64_t,

Похоже, ваш компилятор преобразует возвращаемое значение floor в long long а затем приведение этого результата к unsigned long long,

Обратите внимание, что переполнение при преобразовании с плавающей точкой в ​​интеграл вполне обычно приводит к наименьшему представляемому значению (например, cvttsd2siq на x86-64):

Если преобразование неточно, возвращается усеченный результат. Если преобразованный результат превышает максимальное целое число двойного слова со знаком, то возникает недопустимое исключение с плавающей точкой, а если это исключение маскируется, возвращается неопределенное целочисленное значение (80000000H).

(это из документации по двойному слову, но поведение четырех слов такое же.)

4

Гипотеза: это ошибка. Компилятор конвертирует double в unsigned long long правильно, но преобразует расширенную точность с плавающей точкой (возможно long double) чтобы unsigned long long неправильно. Подробности:

double              x = std::floor(9710908999.0089989 * 1.0E9);

Это вычисляет значение на правой стороне и сохраняет его в x, Значение в правой части может быть вычислено с повышенной точностью, но, как того требуют правила C ++, оно преобразуется в double когда хранится в x, Точное математическое значение будет 9710908999008998870, но с округлением до double формат выдает 9710908999008999424.

unsigned long long y1 = x;

Это преобразует double значение в x в unsigned long long, производство ожидаемых 9710908999008999424.

unsigned long long y2 = std::floor(9710908999.0089989 * 1.0E9);

Это вычисляет значение с правой стороны, используя расширенную точность, производя 9710908999008998870. Когда значение расширенной точности преобразуется в unsigned long longесть ошибка, выдающая 263 (9223372036854775808). Это значение, вероятно, является значением ошибки «вне диапазона», созданным инструкцией, которая преобразует формат расширенной точности в 64-разрядное целое число. Компилятор использовал неправильную последовательность команд для преобразования своего формата расширенной точности в unsigned long long,

3

Вы бросили y1 как удвоение, прежде чем снова бросить его на длинную позицию. значение x — это не значение floor, а округленное значение floor.

Та же логика применима и для приведения целых чисел и чисел с плавающей точкой. float x = (float) ((int) 1.5) даст другое значение для float x = 1.5

0
По вопросам рекламы [email protected]