Почему разные языки по-разному отображают число 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 локали).<br>Как 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'