Кросс-платформа/компилятор совместим с символом числа с плавающей запятой
У нас есть игра, которая должна быть детерминированной, поскольку она является частью его многопользовательской модели. Мы также используем Lua, который использует sprintf
внутри (формат %.14g
).
Проблема возникает, когда печатает число, например 0,00001. В некоторых случаях он печатает 1e-05
, а в некоторых других случаях он печатает 1e-005
(дополнительный ноль).
Например, при компиляции с Visual Studio 2015 он печатает 1e-005
, а с Visual studio 2013 он печатает 1e-05
. Я пробовал разные настройки локали, но, похоже, это не имеет никакого эффекта.
Вопрос: Какое наилучшее решение для достижения детерминированных результатов?
Меня не волнует, стандартизирована или исключена научная нотация.
Решения, о которых я думал:
- Когда я использую нотацию
%f
, она не игнорирует незначительные нули, поэтому наличие %.14f
приведет к непрактично длинным номерам.
- Использование пользовательского метода
sprintf
(копия, вставленная из некоторых стандартных библиотек)
- Используя какой-то специальный формат, о котором я не думал (я использую только это как ссылку: http://www.cplusplus.com/reference/cstdio/printf/)
Ответы
Ответ 1
Через год мы так решили.
Мы загрузили пользовательскую реализацию печати (трио) и принудительное использование этой реализации вместо системной в lua (и наших источниках).
Нам также пришлось изменить
long double trio_long_double_t;
к
double trio_long_double_t;
в triodef.h, чтобы гарантировать, что Visual Studio и linux/mac дают те же результаты.
Ответ 2
Вы можете переключиться на LuaJIT. Он последовательно форматирует номера между платформами.
На странице расширений:
tostring() и т.д. canonicalize NaN и ± Inf
Все преобразования числа в строку последовательно конвертируют не конечные числа в одни и те же строки на всех платформах. NaN приводит к "nan", положительная бесконечность приводит к "inf", а отрицательная бесконечность приводит к "-inf".
tonumber() и т.д. использовать встроенную строку для преобразования чисел
Все преобразования с номерами в число последовательно конвертируют целочисленные и с плавающей запятой в десятичные и шестнадцатеричные на всех платформах. strtod() больше не используется, что позволяет избежать многочисленных проблем с плохими реализациями библиотеки C. Встроенная функция преобразования обеспечивает полную точность в соответствии со стандартом IEEE-754, она работает независимо от текущей локали и поддерживает шестнадцатеричные числа с плавающей запятой (например, 0x1.5p-3).
Ответ 3
Самый проголосовавший ответ неверен, потому что документация на самом деле неправильная.
Это то, что происходит в LuaJIT:
#define lua_number2str(s,n)sprintf((s),"%.14g",(n))
#define lua_str2number(s,p)strtod((s),(p))
Глядя на реализацию tonumber
и tostring
, это макросы, вызываемые для получения результатов.
Не стесняйтесь исправить этот ответ, если найдете реальную "встроенную" реализацию, потому что мне любопытно также знать, как это работает.
Ответ 4
Lua дает вам math.frexp в стандартной библиотеке. Вы можете использовать это, чтобы разделить ваши поплавки на форму экспоненты и мантиссы, а затем сделать обычную печать в чистом Lua, которая не будет зависеть от базовой платформы. Вот пример:
m,e = math.frexp(val)
io.write(m)
io.write('E')
io.write(e)
PS Наслаждаясь пятничными фактами, продолжайте их:)