Есть ли решение проблем с арифметикой с плавающей запятой в C++?
-
27-09-2019 - |
Вопрос
Я занимаюсь арифметикой с плавающей запятой и испытываю проблемы с точностью.Результирующее значение различно на двух машинах для одного и того же ввода.Я прочитал пост @ Почему я не могу умножить число с плавающей запятой? а также прочитал другие материалы в Интернете и понял, что это связано с двоичным представлением чисел с плавающей запятой и машинным эпсилоном.Однако я хотел проверить, есть ли способ решить эту проблему/Некоторые обходные пути для арифметики с плавающей запятой в C++??Я конвертирую число с плавающей запятой в беззнаковое короткое для хранения и при необходимости конвертирую обратно.Однако, когда я конвертирую его обратно в беззнаковое короткое, точность (до 6 десятичных знаков) остается правильной на одной машине, но не работает на другой.
//convert FLOAT to short
unsigned short sConst = 0xFFFF;
unsigned short shortValue = (unsigned short)(floatValue * sConst);
//Convert SHORT to FLOAT
float floatValue = ((float)shortValue / sConst);
Решение
А short
должно быть не менее 16 бит, и во многих реализациях именно так и есть.Ан unsigned
16-битный short
будет содержать значения от 0 до 65535.Это означает, что сокращение не будет содержать полных пяти цифр точности и уж точно не шести.Если вам нужны шесть цифр, вам нужно 20 бит.
Таким образом, любая потеря точности, скорее всего, связана с тем, что вы пытаетесь упаковать шесть цифр точности в нечто меньшее, чем пять цифр.Нет другого решения этой проблемы, кроме использования целочисленного типа, который, вероятно, занимает столько же места, сколько и float
.
Я не знаю, почему это может работать в одной конкретной системе.Вы использовали одни и те же номера в обоих случаях?Использовалась ли какая-нибудь старая система с плавающей запятой, которая по совпадению дала ожидаемые результаты на опробованных вами образцах?Возможно ли было использовать более крупный short
чем другой?
Другие советы
Если вы хотите использовать собственные типы с плавающей запятой, лучшее, что вы можете сделать, — это убедиться, что значения, выводимые вашей программой, не слишком сильно отличаются от набора эталонных значений.
Точное определение слова «слишком много» полностью зависит от вашего приложения.Например, если вы вычислите a + b
на разных платформах вы должны обнаружить, что два результата находятся в пределах машинной точности друг друга.С другой стороны, если вы делаете что-то более сложное, например инверсию матрицы, результаты, скорее всего, будут отличаться не только точностью машины.Точное определение того, насколько близки результаты друг к другу, является очень тонким и сложным процессом.Если вы точно не знаете, что делаете, вероятно, безопаснее (и разумнее) определить необходимую вам степень точности в дальнейшем в вашем приложении и убедиться, что результат достаточно точен.
Чтобы получить представление о том, как надежно вычислить относительную ошибку между двумя значениями с плавающей запятой, см. этот ответ и связанное с ним руководство по плавающей запятой:
Вместо использования 0xFFFF используйте половину, т.е.32768 для конвертации.32768 (Ox8000) имеет двоичное представление 1000000000000000, тогда как OxFFFF имеет двоичное представление 1111111111111111.Двоичное представление Ox8000 ясно подразумевает, что операции умножения и деления во время преобразования (в короткое (или) при обратном преобразовании в число с плавающей запятой) не изменят значения точности после нуля.Однако для одностороннего преобразования предпочтительнее использовать OxFFFF, поскольку это приводит к более точному результату.