Страницы

Поиск по вопросам

воскресенье, 30 сентября 2018 г.

Отображение числа 9223372036854775807

Почему разные языки по-разному отображают число 9223372036854775807, хотя все используют один и тот же формат 8-байтного double для представления чисел?
9223372036854775807 - в коде 9223372036854775808 - C++ http://ideone.com/PV5iPg и http://codepad.org/vhQzDMqT 9223372036854776000 - Javascript https://jsfiddle.net/5ugL4rqh/ 9223372036854776000 - Java http://ideone.com/QtXRWi 9223372036854780000 - C# http://ideone.com/36Lzzi


Ответ

Здесь в каждой среде/языке два преобразования:
Из константы в исходном коде в объект в памяти Печать этого объекта памяти выбранным способом.
C++
Эффект от кода из вопроса для С++: volatile double x = 9223372036854775807.; схож с gcc's -ffloat-store опцией и позволяет забыть о возможных дополнительных битах и думать только о 64-битных IEEE 754 числах двойной точности, используемые в рассматриваемой реализации (IEEE 754 не обязателен, но конкретная реализация для float чисел должна быть задокументирована).
Константа 9223372036854775807. из исходного кода превращается в 9223372036854775808. double (ожидаемо для этого типа, см. демонстрацию битового представления внизу). В CPython тоже самое происходит:

>>> 9223372036854775807. .as_integer_ratio() (9223372036854775808, 1) >>> 9223372036854775807. .hex() '0x1.0000000000000p+63'
то есть 9223372036854775807. не может быть точно представлено в IEEE 754 double и поэтому используется приближение 9223372036854775808. (263), которое уже выводится точно в этом случае с помощью: cout << fixed << x; как ascii-строка: "9223372036854775808.000000" (в C локали).
Как double в памяти и в виде бит в IEEE 754 представлен, и как печать может происходить в С, подробно описано в ответе на вопрос printf как средство печати переменных в С
В данном случае, так как число является степенью двойки, то легко найти его IEEE 754 представление:
d = ±знак · (1 + мантисса / 252) · 2порядок − 1023
знаковый бит равен нулю, так как число положительное порядок = (63 + 1023)10 = 100001111102, чтобы получить 263 у мантисса все явные 52 бита нулевые (старший неявный 53ий бит всегда равен единице)
Все биты числа вместе:
0 10000111110 0000000000000000000000000000000000000000000000000000
Что подтверждается вычислениями на Питоне:
>>> import struct >>> struct.pack('>d', 9223372036854775808.0).hex() 43e0000000000000 >>> bin(struct.unpack('>Q', struct.pack('>d', 9223372036854775808.0))[0])[2:].zfill(64) '0100001111100000000000000000000000000000000000000000000000000000'
И в обратную сторону:
>>> b64 = 0b0_10000111110_0000000000000000000000000000000000000000000000000000 .to_bytes(8, 'big') >>> b64.hex() '43e0000000000000' >>> "%f" % struct.unpack('>d', b64)[0] '9223372036854775808.000000'
Порядок байт в памяти у числа в примере показан от старшего к младшему (big-endian), но фактически может быть и от младшего к старшему (little-endian):
>>> struct.pack('d', 9223372036854775808.0).hex() '000000000000e043'
Можно посмотреть, что не подряд идут представимые числа, вычитая/прибавляя по одному биту к мантиссе:
>>> x = 0b0_10000111110_0000000000000000000000000000000000000000000000000000
>>> def to_float_string(bits): ... return "%f" % struct.unpack('>d', bits.to_bytes(8, 'big'))[0]
>>> for n in range(x-1, x+2): ... print(to_float_string(n)) 9223372036854774784.000000 9223372036854775808.000000 9223372036854777856.000000
Разница в один бит для чисел этой величины ведёт к разнице больше тысячи в десятичном представлении: ..4784, ..5808, ..7856
Можно воспользоваться C99 функцией nextafter()
#include #include #include
int main(void) { volatile double x = 9223372036854775808.0; printf("%f
", nextafter(x, DBL_MIN)); printf("%f
", x); printf("%f
", nextafter(x, DBL_MAX)); }
Результаты совпадают с предыдущими:
9223372036854774784.000000 9223372036854775808.000000 9223372036854777856.000000
Javascript
Числа в JavaScript представлены интересным способом — целые как IEEE 754 double представлены. К примеру, максимальное число (Number.MAX_SAFE_INTEGER) равно 253
9223372036854775807 на три порядка больше MAX_SAFE_INTEGER поэтому нет гарантии, что n и n+1 представимы.
> 9223372036854776000 === 9223372036854775807 true > 9223372036854775808 === 9223372036854775807 true
9223372036854776000 (результат document.write(9223372036854775807) в одной из javascript реализаций) допустим cтандартом в качестве строкового представления для 9223372036854775807 (это по-прежнему одно binary64 число: 0x1.0000000000000p+63).
Результаты побитовых операций вообще ограничены 32-битными числами со знаком. Можно посмотреть на какие ухищрения пришлость пойти, чтобы воспроизвести результат хэш-функции, реализованной в javascript: Как перевести из Javascript в Питон функцию хэширования строки
Java
В Java, double это тип со значениями, которые включают 64-bit IEEE 754 числа с плавающей точкой.
double x = 9223372036854775807.; System.out.format("%f", x);
Возможная логика, почему 9223372036854776000, а не 9223372036854775808. десятичное представление выбрано для binary64 числа 0x1.0000000000000p+63 в том, что в общем случае это позволяет меньше цифр печатать для дробных чисел — не отображаются завершающие нули (это спекуляция — я не углублялся в этот вопрос).
C#
msdn утверждает что double в C# соотвествует IEEE 754.
9223372036854780000.0 намекает, что Console.WriteLine("{0:0.0}", x); округляет до 15 цифр при печати. Напечатанное число отличается от x
>>> 9223372036854780000. .hex() '0x1.0000000000002p+63'
Вероятно это происходит по cхожей причине, что и 0.1 показывается как 0.1 при печати, а не 0.10000000000000001 или вообще 0.1000000000000000055511151231257827021181583404541015625 ( 0.1 .hex() == '0x1.999999999999ap-4'). Более того binary64 представление другое (263 vs. 263+212) (единственный из представленных примеров в вопросе, который по умолчанию не выводит эквивалентное представление).
Возможно, приоритет в округлении до 15 цифр, не обращая внимание достаточно ли это, чтобы эквивалентное binary64 представление получить. Не только C# себя так ведёт, к примеру, numpy.array в Питоне выводит по умолчанию 8 цифр:
>>> import numpy >>> a = numpy.array([2**63-1, 2**63, 2**63+2**12], dtype=numpy.float64) >>> a array([ 9.22337204e+18, 9.22337204e+18, 9.22337204e+18]) >>> 9.22337204e+18 .hex() '0x1.0000000176f0ap+63' >>> numpy.set_printoptions(precision=16) >>> a array([ 9.2233720368547758e+18, 9.2233720368547758e+18, 9.2233720368547799e+18])
Показ 17 цифр в C# возможен с помощью стандартного "{0:R}" формата, что выводит 9.2233720368547758E+18, то есть снова ту же рассматриваемую изначальную 263 степень получили:
>>> 9.2233720368547758E+18 .hex() '0x1.0000000000000p+63'

Комментариев нет:

Отправить комментарий