У меня есть функция, выполняющая некоторые математические вычисления и возвращающая double
. Это приводит к разным результатам в Windows и Android из-за различий в реализации std::exp
( Почему я получаю специфичный для платформы результат для std :: exp? ). Разница округления e-17 распространяется, и, в конце концов, это не просто разница округления (результаты могут измениться с 2,36 до 2,47 в конце). Поскольку я сравниваю результат с некоторыми ожидаемыми значениями, я хочу, чтобы эта функция возвращала одинаковый результат на всех платформах.
Так что мне нужно округлить мой результат. Самым простым решением для этого, по-видимому (насколько я мог найти в Интернете), является std::ceil(d*std::pow<double>(10,precision))/std::pow<double>(10,precision)
. Тем не менее, я чувствую, что это может привести к различным результатам в зависимости от платформы (и, кроме того, трудно решить, каким должен быть precision
).
Мне было интересно, может ли жесткое кодирование младшего значащего байта double
быть хорошей стратегией округления.
Этот быстрый тест, похоже, показывает, что "да":
#include <iostream>
#include <iomanip>
double roundByCast( double d )
{
double rounded = d;
unsigned char* temp = (unsigned char*) &rounded;
// changing least significant byte to be always the same
temp[0] = 128;
return rounded;
}
void showRoundInfo( double d, double rounded )
{
double diff = std::abs(d-rounded);
std::cout << "cast: " << d << " rounded to " << rounded << " (diff=" << diff << ")" << std::endl;
}
void roundIt( double d )
{
showRoundInfo( d, roundByCast(d) );
}
int main( int argc, char* argv[] )
{
roundIt( 7.87234042553191493141184764681 );
roundIt( 0.000000000000000000000184764681 );
roundIt( 78723404.2553191493141184764681 );
}
Это выводит:
cast: 7.87234 rounded to 7.87234 (diff=2.66454e-14)
cast: 1.84765e-22 rounded to 1.84765e-22 (diff=9.87415e-37)
cast: 7.87234e+07 rounded to 7.87234e+07 (diff=4.47035e-07)
Мой вопрос:
- Безопасно ли
unsigned char* temp = (unsigned char*) &rounded
или здесь неопределенное поведение и почему?
- Если UB отсутствует (или если есть лучший способ сделать это без UB), является ли такая круглая функция безопасной и точной для всего ввода?
Примечание: я знаю, что числа с плавающей точкой неточны. Пожалуйста, не отмечайте как дубликат Математика с плавающей запятой не работает? или Почему числа с плавающей запятой неточны? . Я понимаю, почему результаты отличаются, я просто ищу способ сделать их идентичными на всех целевых платформах.
Редактировать, я могу переформулировать свой вопрос, поскольку люди спрашивают, почему у меня разные ценности и почему я хочу, чтобы они были одинаковыми.
Допустим, вы получаете double
от вычисления, которое может привести к другому значению из-за реализаций, специфичных для платформы (например, std::exp
). Если вы хотите исправить эти разные double
, чтобы в конечном итоге они имели одинаковое представление памяти (1) на всех платформах, и вы хотите потерять как можно меньшую точность, то является ли исправление младшего значащего байта хорошим подходом? (потому что я чувствую, что округление до произвольной заданной точности может привести к потере большего количества информации, чем этот трюк).
(1) Под «одним и тем же представлением» я имею в виду, что если вы преобразуете его в std::bitset
, вы захотите увидеть одинаковую последовательность битов для всех платформ.