Перенос двойника из C ++ в python без потери точности - PullRequest
1 голос
/ 28 мая 2020

У меня есть код C ++, который выводит массив значений типа double. Я хочу использовать эти двойные значения в python. Самым очевидным и простым способом передачи значений было бы, конечно, выгрузить их в файл, а затем перечитать файл в формате python. Однако это приведет к потере точности, поскольку не все десятичные разряды могут быть переданы. С другой стороны, если я добавлю больше десятичных знаков, файл станет больше. В массиве, который я пытаюсь передать, несколько миллионов записей. Следовательно, моя идея состоит в том, чтобы использовать двоичное представление double, выгрузить их в двоичный файл и перечитать его в python.

Первая проблема заключается в том, что я не знаю, как двойные значения форматируются в памяти , например здесь . Двоичное представление объекта из памяти легко прочитать, но я должен знать, где расположены знаковый бит, показатель степени и мантиасса. Для этого, конечно, существуют стандарты . Поэтому первый вопрос: как мне узнать, какой стандарт использует мой компилятор? Я хочу использовать g++-9. Я пробовал искать в Google этот вопрос для разных компиляторов, но без точного ответа. Следующий вопрос будет о том, как превратить байты обратно в двойные, учитывая формат.

Другая возможность может заключаться в компиляции кода C ++ как python модуля и использовании его напрямую, передавая массив без файл только из памяти. Но я не знаю, легко ли это будет быстро настроить.
Я также видел, что можно скомпилировать код C ++ непосредственно из строки в python, используя numpy, но я не могу найти для этого никакой документации.

Ответы [ 2 ]

2 голосов
/ 28 мая 2020

Вы можете записать двойные значения в двоичной форме, а затем прочитать и преобразовать их в python с struct.unpack("d", file.read(8)), тем самым предполагая, что используется IEEE 754.

Есть пара проблемы, однако:

  • C ++ не определяет битовое представление чисел двойной точности. Хотя это IEEE 754 на любой платформе, с которой я сталкивался, это не следует воспринимать как должное.
  • Python предполагает порядок байтов с прямым порядком байтов. Итак, на маленькой машине с прямым порядком байтов вы должны указать struct.unpack при чтении или изменить порядок байтов перед записью.

Если этот код нацелен на конкретную машину c, я бы посоветовал просто протестировать подход на автомате. В этом случае не следует предполагать, что этот код работает на других архитектурах, поэтому желательно, чтобы у вас были проверки в вашем Makefile / CMakefile, которые отказываются использовать неожиданные цели.

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

1 голос
/ 28 мая 2020

Я не проверял это, но, вероятно, интерфейс C ++ python сохранит double s, просто скопировав двоичное изображение, которое они представляют (64-битное изображение), поскольку, скорее всего, оба языка используют одно и то же внутреннее представление двоичного плавающего числа с запятой (двоичный 64-битный формат IEEE-754) У этого есть одна причина: оба используют сопроцессор с плавающей запятой для работы с ними, и это формат, который требуется для передачи чисел.

Возникает один вопрос по этому поводу, поскольку вы не говорите: Как вы определили, что вы теряете точность данных? Проверяли ли вы только разные десятичные цифры? Или вы экспортировали реальный двоичный формат, чтобы проверить различия в битовых шаблонах? Распространенной ошибкой является вывод обоих чисел, скажем, с 20 значащими цифрами, а затем наблюдение за различиями в последних двух или трех цифрах. Это потому, что вы не можете познакомиться с тем, что double s, представленные таким образом (в двоичном формате IEEE-752), имеют только около 17 значащих цифр (это зависит от числа, но вы можете иметь различия в di git 17-й или более поздний, это потому, что числа закодированы в двоичном формате)

Я настоятельно не рекомендую вам преобразовывать эти числа в десятичное представление и отправлять их как строки ascii. Вы потеряете некоторую точность (в виде ошибок округления, см. Ниже) в кодировании, а затем снова на этапе декодирования в python. Подумайте, что преобразование (даже с максимальной точностью) двоичного числа с плавающей запятой в десятичное, а затем обратно в двоичное - это почти всегда процесс потери информации. Проблема в том, что число, которое может быть представлено точно в десятичном виде (например, 0.1), не может быть представлено точно в двоичной форме (вы получаете бесконечную повторяющуюся последовательность с периодом c, как если бы вы делили 1.0 на 3.0 в decimal, вы получите неточный результат) Противоположное преобразование отличается, так как вы всегда можете преобразовать конечное десятичное двоичное число в конечное десятичное число с основанием десять, , но не в пределах 53 бит - что является количество бит, выделенных для мантиссы в 64-битных числах с плавающей запятой)

Итак, мой совет - перепроверить, где ваши числа показывают различия, и сравнить с тем, что я говорю здесь (если числа показывают различия в di git позиции после 16-го десятичного числа di git, эти различия в порядке --- они должны делать только с различными алгоритмами, используемыми библиотекой C ++ и python библиотекой для преобразования чисел в десятичный формат ) Если до этого возникнут различия, проверьте, как представлены числа с плавающей запятой в python, или проверьте, теряете ли вы в какой-то момент точность, сохраняя эти числа в переменной с одинарной точностью float (это чаще, чем обычно оценивается), и посмотрите, есть ли какая-то разница (я не верю, что будет) в форматы, используемые обеими средами. Кстати, отображение таких различий в вашем вопросе должно быть плюсом (что вы также не сделали), поскольку мы могли бы сказать вам, нормальны ли наблюдаемые вами различия или нет.

...