Как плавающая точка хранится? Когда это имеет значение?

Во время недавнего опыта написания JS-переводчика я много боролся с внутренней работой дат ECMA / JS. Итак, я полагаю, что я брошу свои 2 цента здесь. Надеемся, что совместное использование этого материала поможет другим с любыми вопросами о различиях между браузерами в том, как они обрабатывают даты.

Сторона входа

Все реализации хранят свои значения даты внутри себя как 64-разрядные номера которые представляют собой миллисекунды с 01.01.1970 UTC (GMT - это то же самое, что и UTC). Даты, возникающие после 1/1/1970 00:00:00, являются положительными числами, а даты предшествуют отрицательным.

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

Date.parse('1/1/1970');

В моей временной зоне ( EST), результат составляет 18000000, потому что это то, сколько мс составляет 5 часов (это всего 4 часа в летние месяцы). Значение будет отличаться в разных часовых поясах. Все основные браузеры делают это одинаково.

Вот и все. Несмотря на то, что в форматах входных строк есть некоторые отклонения, которые основные браузеры будут анализировать как даты, они по существу интерпретируют их одинаково с часовыми поясами и летней экономией. Один из них - формат ISO 8601. Это единственный формат, специально описанный в спецификации ECMA-262 v.5. Для всех других строковых форматов интерпретация зависит от реализации. По иронии судьбы, это формат, в котором браузеры могут отличаться. Ниже приведен сравнительный вывод Chrome vs Firefox для 1/1/1970 на моей машине с использованием формата строки ISO 8601.

Date.parse('1970-01-01T00:00:00Z');       // Chrome: 0         FF: 0
Date.parse('1970-01-01T00:00:00-0500');   // Chrome: 18000000  FF: 18000000
Date.parse('1970-01-01T00:00:00');        // Chrome: 0         FF: 18000000
  • Спецификатор «Z» указывает, что вход уже находится в UTC и не требует смещения перед хранением.
  • Спецификатор «-0500» указывает, что вход находится в GMT-05: 00, поэтому оба браузера интерпретируют ввод как находящийся в моем локальном часовом поясе. Это означает, что значение будет преобразовано в UTC перед сохранением. В моем случае это означает добавление 18000000 мс к внутреннему значению даты, что требует сдвига -18000000 мс (-05: 00), чтобы вернуть меня в локальное время.
  • Однако, когда спецификатор отсутствует, FF обрабатывает ввод в качестве локального времени, в то время как Chrome рассматривает его как время UTC. Для меня это создает 5-часовую разницу в сохраненной стоимости, что является проблематичным. В моей реализации я оказался на стороне FF здесь, потому что мне нравится вывод toString для соответствия моему входному значению, если я не укажу альтернативный часовой пояс, который я никогда не делаю. Отсутствие спецификатора должно предполагать местный вход времени.

Но здесь ситуация ухудшается, FF обрабатывает краткую форму формата ISO 8601 («YYYY -MM-DD ") по-разному, чем обрабатывает длинную форму (« YYYY-MM-DDTHH: mm: ss: sssZ ») без каких-либо логических причин. Вот результат из FF с длинными и короткими форматами даты ISO без спецификатора часового пояса.

Date.parse('1970-01-01T00:00:00');       // 18000000
Date.parse('1970-01-01');                // 0

Итак, чтобы ответить на вопрос первоначального вопросчика, "YYYY-MM-DD" - это короткая форма ISO 8601 "YYYY-MM-DDTHH:mm:ss:sssZ". Таким образом, это интерпретируется как время UTC, тогда как другое интерпретируется как локальное. Вот почему

Это не jive:

console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08")).toString());

Это делает:

console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());

Суть в этом заключается в синтаксическом анализе строк даты. Единственная строка ISO 8601, которую вы можете безопасно анализировать в браузерах, - это длинная форма. И ВСЕГДА используйте спецификатор «Z». Если вы это сделаете, вы можете спокойно перемещаться между локальным и UTC.

Это работает в браузерах (после IE9):

console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());

К счастью, большинство современных браузеров другие форматы ввода одинаково, включая наиболее часто используемые форматы «1/1/1970» и «1/1/1970 00:00:00 AM». Все следующие форматы (и другие) рассматриваются как локальный ввод времени во всех браузерах и конвертируются в UTC перед хранением. Таким образом, они совместимы с кросс-браузером. Выходной код этого кода во всех браузерах в моем часовом поясе одинаковый.

console.log(Date.parse("1/1/1970"));
console.log(Date.parse("1/1/1970 12:00:00 AM"));
console.log(Date.parse("Thu Jan 01 1970"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00 GMT-0500"));

Сторона выхода

. На стороне вывода все браузеры переводили часовые пояса одинаково, но они обрабатывать строковые форматы по-разному. Вот функции toString и то, что они выводят. Обратите внимание на то, что функции toUTCString и toISOString выводят на моей машине 5:00 AM.

Конвертирует с UTC в локальное время перед печатью

 - toString
 - toDateString
 - toTimeString
 - toLocaleString
 - toLocaleDateString
 - toLocaleTimeString

Прямая печать сохраненного времени UTC

 - toUTCString
 - toISOString 

In Chrome
toString            Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString        Thu Jan 01 1970
toTimeString        00:00:00 GMT-05:00 (Eastern Standard Time)
toLocaleString      1/1/1970 12:00:00 AM
toLocaleDateString  1/1/1970
toLocaleTimeString  00:00:00 AM

toUTCString         Thu, 01 Jan 1970 05:00:00 GMT
toISOString         1970-01-01T05:00:00.000Z

In Firefox
toString            Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString        Thu Jan 01 1970
toTimeString        00:00:00 GMT-0500 (Eastern Standard Time)
toLocaleString      Thursday, January 01, 1970 12:00:00 AM
toLocaleDateString  Thursday, January 01, 1970
toLocaleTimeString  12:00:00 AM

toUTCString         Thu, 01 Jan 1970 05:00:00 GMT
toISOString         1970-01-01T05:00:00.000Z

Я обычно не используйте формат ISO для ввода строки. Единственный раз, когда использование этого формата полезно для меня, - это когда даты нужно сортировать как строки. Формат ISO можно сортировать как есть, а другие - нет. Если у вас есть совместимость с несколькими браузерами, укажите либо часовой пояс, либо используйте совместимый строковый формат.

Код new Date('12/4/2013').toString() проходит через следующее внутреннее псевдопреобразование:

  "12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"

Надеюсь, этот ответ был полезен.

36
задан RBT 17 July 2019 в 02:23
поделиться

10 ответов

Как упомянуто, статья Wikipedia о IEEE 754 делает хорошее задание показа, как числа с плавающей точкой хранятся в большинстве систем.

Теперь, вот некоторые общие глюки:

  • самое большое - то, что Вы почти никогда не хотите сравнить два числа с плавающей точкой для равенства (или неравенство). Вы захотите использовать больше, чем/меньшее количество, чем сравнения вместо этого.
  • , Чем больше операций Вы делаете на числе с плавающей точкой, тем более значительные погрешности округления могут стать.
  • Точность ограничена размером части, таким образом, Вы не можете быть в состоянии правильно добавить числа, которые разделяются несколькими порядками величины. (Например, Вы не будете в состоянии добавить 1E-30 к 1E30.)
24
ответ дан Rob Pilkington 27 November 2019 в 05:59
поделиться

Полное объяснение проблем, окружающих числа с плавающей точкой, дано в статье , Что Каждый Программист Должен Знать Об Арифметике С плавающей точкой .

12
ответ дан ChrisN 27 November 2019 в 05:59
поделиться

Стандарт IEEE 754 .

, Конечно, существуют другие средства сохранить числа, когда IEE754 не достаточно хорош. Библиотеки как Java BigDecimal доступны для большинства платформ и отображаются хорошо на тип числа SQL. Символы могут использоваться для иррациональных чисел, и отношения, которые не могут быть точно представлены в двоичной или десятичной плавающей точке, могут быть сохранены как отношение.

5
ответ дан erickson 27 November 2019 в 05:59
поделиться

Относительно второй части Вашего вопроса, если производительность и эффективность не важны для Вашего проекта, тогда я предлагаю, чтобы Вы передали данные с плавающей запятой как строку по TCP/IP. Это позволяет Вам избежать проблем, таких как выравнивание байта и упростит отладку.

5
ответ дан Knox 27 November 2019 в 05:59
поделиться

В основном то, о чем необходимо волноваться в числах с плавающей точкой, - то, что существует ограниченное количество цифр точности. Это может вызвать проблемы при тестировании на равенство, или если для программы на самом деле нужно больше цифр точности, чем, что тот тип данных дают Вам.

В C++, хорошее эмпирическое правило должно думать, что плавание дает Вам 7 цифр точности, в то время как двойное дает Вам 15. Кроме того, если Вы интересуетесь знанием, как протестировать на равенство, можно посмотреть этот поток вопроса.

3
ответ дан Community 27 November 2019 в 05:59
поделиться

Да существует Стандарт IEEE для Двоичной Арифметики С плавающей точкой (IEEE 754)

, число разделяется на три части, знак, экспоненту и часть, когда сохранено в двоичном файле.

2
ответ дан stukelly 27 November 2019 в 05:59
поделиться

Этот статья , наделенная правом "Стандарт IEEE, 754 Числа с плавающей точкой" могут быть полезными. Чтобы быть честным, я не абсолютно уверен, что понимаю Ваш вопрос, таким образом, я не уверен, что это будет полезным, но я надеюсь, что это будет.

2
ответ дан Onorio Catenacci 27 November 2019 в 05:59
поделиться

Если Вы действительно волнуетесь по поводу погрешностей округления с плавающей точкой, большинство языков предлагает типы данных, которые не имеют ошибок с плавающей точкой. SQL Server имеет Типы данных decimal и Типы данных money..Net имеет Тип данных decimal. Они не бесконечная точность как BigDecimal в Java, но они точны вниз к количеству десятичных точек, для которых они определяются. Таким образом, Вы не должны волноваться о долларовой стоимости, Вы вводите как 4,58$, будучи сохраненным как значение с плавающей точкой 4,579999999999997

1
ответ дан Kibbee 27 November 2019 в 05:59
поделиться

То, что я помню, является плавающей точкой на 32 бита, хранится с помощью 24 битов для фактического числа, и оставаться 8 битов используются в качестве питания 10, определяя, где десятичная точка.

я немного ржав на предмете tho...

0
ответ дан Rik 27 November 2019 в 05:59
поделиться

In follow up to this question, it appears that some numbers cannot be represented by floating point at all, and instead are approximated.

Correct.

How are floating point numbers stored? Is there a common standard for the different sizes?

As the other posters already mentioned, almost exclusively IEEE754 and its successor IEEE754R. Googling it gives you thousand explanations together with bit patterns and their explanation. If you still have problems to get it, there are two still common FP formats: IBM and DEC-VAX. For some esoteric machines and compilers (BlitzBasic, TurboPascal) there are some odd formats.

What kind of gotchas do I need to watch out for if I use floating point? Are they cross-language compatible (ie, what conversions do I need to deal with to send a floating point number from a python program to a C program over TCP/IP)?

Practically none, they are cross-language compatible.

Very rare occuring quirks:

  • IEEE754 defines sNaNs (signalling NaNs) and qNaNs (quiet NaNs). The former ones cause a trap which forces the processor to call a handler routine if loaded. The latter ones don't do this. Because language designers hated the possibility that sNaNs interrupt their workflow and supporting them enforce support for handler routines, sNaNs are almost always silently converted into qNaNs. So don't rely on a 1:1 raw conversion. But again: This is very rare and occurs only if NaNs are present.

  • You can have problems with endianness (the bytes are in the wrong order) if files between different computers are shared. It is easily detectable because you are getting NaNs for numbers.

3
ответ дан 27 November 2019 в 05:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: