путать в «двойной к длинной длинной» в переполнении стека

мой код:

int main()
{
long long a = pow(2,63) - 1;
long long b = pow(2,63);
double c  = pow(2,63) - 1;
double d = pow(2,63);
printf("%lld %lld \n%f %f \n%lld %lld\n", a, b, c, d, (long long)c, (long long)d);

return 0;
}

и точный результат (кодовый блок с gcc в win7 x64):

9223372036854775807 9223372036854775807
9223372036854775800.000000 9223372036854775800.000000
-9223372036854775808 -9223372036854775808

Вопрос:

Зачем a == b ?

я знаю это c == d из-за точности double,

Но почему (long long)c а также (long long)d не является 9223372036854775800 ?

И почему (long long)c != a а также (long long)d != b?

3

Решение

Зачем a == b? я знаю это c == d из-за точности в два раза.

По той же причине. Там нет перегрузок pow для целочисленных типов, поэтому арифметика выполняется с использованием double, поскольку double обычно имеет значение 52 бита, добавляя или вычитая 1 к значению 263 не будет иметь никакого эффекта.

Зачем (long long)c а также (long long)d не является 9223372036854775800?

Так как long long это 64-битный подписанный тип, а максимальное представимое значение равно 263-1. c а также d оба могут иметь значение 263 (или даже немного большее значение), который находится вне диапазона. На типичной платформе с 2-мя дополнениями это, вероятно, переполнится, чтобы дать значение около -263, как вы наблюдаете. Но обратите внимание, что это неопределенное поведение; Вы не можете полагаться ни на что, если преобразование с плавающей запятой переполняется.

Зачем (long long)c != a а также (long long)d != b?

Я не знаю; для меня, a а также b имеют такие же большие отрицательные значения. Это похоже на причуду вашей реализации, вызванную a а также b в конечном итоге со значением 263-1, а не ожидаемый 263. Как всегда при работе с числами с плавающей запятой, вы должны ожидать таких маленьких ошибок округления.

Вы можете получить точный результат, используя целочисленную арифметику:

long long a = (1ULL << 63) - 1;
unsigned long long b = 1ULL << 63;

Обратите внимание на использование арифметики без знака, так как, как упоминалось выше, подписанный (1LL << 63) будет переполнен.

2

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

pow(2,63) - 1 все сделано в арифметике с плавающей запятой двойной точности. В частности, -1 превращается в -1.0 и это слишком мало, чтобы иметь значение

4

почему а == б

Потому что ваш компилятор (GCC) рассчитал значения для инициализации a а также b с, и найдено (доказано?), оба совпадают или превышают максимально возможное значение для long longпоэтому он инициализировал оба с этим максимальным значением LLONG_MAX (или же 0x7FFFFFFFFFFFFFFF, или же 9223372036854775807 на вашей платформе).

Обратите внимание, что (как указал Паскаль Куок) это неопределенное поведение, вызванное переполнением при преобразовании double к long long при инициализации a а также b, В то время как gcc имеет дело с этим, как описано выше, другие компиляторы могут иметь дело с этим по-другому

Я знаю, что C == D из-за точности двойной

Причина c а также d держать то же значение действительно из-за точности double :

  • pow(2, 63) может быть точно представлен с дробью 1 и показатель 63
  • pow(2, 63) - 1 не может быть точно представлен

Причина не показывается 9223372036854775808 (точное значение хранится в c а также d), из-за printf точность, которая на вашей платформе, по-видимому, показывает только 17 цифр. Вы можете заставить его показывать больше, например, используя. %20.0f, но на Windows, которая, вероятно, не будет иметь никакого значения из-за эта ошибка.

почему (длинная длинная) c и (длинная длинная) d не 9223372036854775800?

Так как c а также d держать значение 9223372036854775808, или же 0x8000000000000000, который при печати в виде значения со знаком становится -9223372036854775808,

Обратите внимание, что это снова неопределенное поведение (из-за переполнения со знаком).

почему (длинная длинная) c! = a и (длинная длинная) d! = b?

Потому что они были рассчитаны по-разному. a а также b были рассчитаны компилятором, в то время как (long long) c а также (long long) d были рассчитаны во время выполнения.

Хотя обычно эти разные способы расчета должны давать одинаковые результаты, здесь мы имеем дело с неопределенным поведением (как объяснено ранее), так что все идет хорошо. И в вашем случае результаты компилятора отличаются от результатов времени выполнения.

2

Так как pow возвращает двойную и двойную потерянную точность. Вот почему a==b,

1

pow(2, 63) эквивалентно pow((double) 2, (double) 63),

Действительно, C ++ 11 26.8 [c.math] параграф 3 говорит, что <cmath> обеспечивает декларацию double pow(double, double) и пункт 11 говорит, что (выделение мое)

  1. Если какой-либо аргумент, соответствующий параметру double, имеет тип long double, то все аргументы, соответствующие параметрам double, эффективно приводятся к long double.
  2. Иначе, если какой-либо аргумент, соответствующий double параметр имеет тип double или целочисленный тип, то все аргументы, соответствующие double параметры эффективно приводятся к double.
  3. В противном случае все аргументы, соответствующие двойным параметрам, эффективно приводятся к плавающей точке.

Теперь литералы 2 а также 63 являются intс, следовательно, pow(2, 63) эквивалентно pow((double) 2, (double) 63), Возвращаемый тип тогда double который не имеет 63 бит точности, необходимой, чтобы «увидеть» разницу между 2^63 а также 2^63 - 1,

Я рекомендую прочитать эта почта и отличный ответ Говард Хиннант.

0

длинный длинный ->% lld

длинный двойной ->% Lf

двойной ->% f

плавать ->% f

int ->% d

Прочитайте главу 15 в << УКАЗАТЕЛИ на C >> для более подробной информации.

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