мой код:
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
?
Зачем
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)
будет переполнен.
pow(2,63) - 1
все сделано в арифметике с плавающей запятой двойной точности. В частности, -1
превращается в -1.0
и это слишком мало, чтобы иметь значение
почему а == б
Потому что ваш компилятор (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
были рассчитаны во время выполнения.
Хотя обычно эти разные способы расчета должны давать одинаковые результаты, здесь мы имеем дело с неопределенным поведением (как объяснено ранее), так что все идет хорошо. И в вашем случае результаты компилятора отличаются от результатов времени выполнения.
Так как pow
возвращает двойную и двойную потерянную точность. Вот почему a==b
,
pow(2, 63)
эквивалентно pow((double) 2, (double) 63)
,
Действительно, C ++ 11 26.8 [c.math] параграф 3 говорит, что <cmath>
обеспечивает декларацию double pow(double, double)
и пункт 11 говорит, что (выделение мое)
- Если какой-либо аргумент, соответствующий параметру double, имеет тип long double, то все аргументы, соответствующие параметрам double, эффективно приводятся к long double.
- Иначе, если какой-либо аргумент, соответствующий
double
параметр имеет типdouble
или целочисленный тип, то все аргументы, соответствующиеdouble
параметры эффективно приводятся кdouble
.- В противном случае все аргументы, соответствующие двойным параметрам, эффективно приводятся к плавающей точке.
Теперь литералы 2
а также 63
являются int
с, следовательно, pow(2, 63)
эквивалентно pow((double) 2, (double) 63)
, Возвращаемый тип тогда double
который не имеет 63 бит точности, необходимой, чтобы «увидеть» разницу между 2^63
а также 2^63 - 1
,
Я рекомендую прочитать эта почта и отличный ответ Говард Хиннант.
длинный длинный ->% lld
длинный двойной ->% Lf
двойной ->% f
плавать ->% f
int ->% d
Прочитайте главу 15 в << УКАЗАТЕЛИ на C >> для более подробной информации.