背景
- 浮動小数点数データを printf とかでファイルに ASCII 保存し, 再度読み込むと完全に同じ値になるとは限らない
- レイトレーシングやレンダリングなど精度にシビアなアプリで, しかし他とのソフトウェア互換性のためなどで ASCII で数値を扱う必要がある
ライブラリ
とりあえずは Ryu を使っておけば問題なさそうです.
Converts floating point numbers to decimal strings
https://github.com/ulfjack/ryu
理論などは, ryu のページに paper があります.
double 限定ですと,
も使えそうでしょうか.
References
Reducing {fmt} library size 4x using Bloaty McBloatface
http://www.zverovich.net/2020/05/21/reducing-library-size.html
からの情報.
Grisu3
https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf
Dragon4
http://kurtstephens.com/files/p372-steele.pdf
(Ryu(龍) はこの Dragon4 を参考にしたからですかね)
StaticJSON と組み合わせ, JSON 文字列として浮動小数点数を出力する
JSON で数値をテキスト出力したいが, 正確に print したいときがありますね.
StaticJSON と組み合わせてみます.
StaticJSON では, Convert で from_shadow
, to_shadow
を定義することでカスタムの変換をかけることができます.
from_shadow
, to_shadow
は primitive type(i.e. float
, double
)で定義しても使われないので, 明示的に変換したい場合はなにかしらクラスを定義します.
ここでは, Bounds2f(4 floats)クラスのサンプルを提示します.
namespace {
// Exactly serialize IEEE754 float value to decimal string using ryu library.
std::string float_to_string(const float &f) {
char *s = f2s(f);
std::cout << "to_shadow " << f << ", to_string = " << s << "\n";
std::string ret = std::string(s);
free(s);
return ret;
}
} // namespace
namespace staticjson
{
template <>
struct Converter<Bounds2f>
{
typedef std::array<std::string, 4> shadow_type;
static std::unique_ptr<ErrorBase> from_shadow(const shadow_type& shadow, Bounds2f& value)
{
std::array<float, 4> results;
for (size_t i = 0; i < 4; i++) {
double result;
Status status = s2d(shadow[i].c_str(), &result);
if (status == SUCCESS) {
// ok
} else if (status == INPUT_TOO_SHORT) {
return std::make_unique<error::CustomError>("Input string is too short");
} else if (status == INPUT_TOO_LONG) {
return std::make_unique<error::CustomError>("Input string is too long");
} else {
// TODO(LTE): Allow Infinity, NaN
return std::make_unique<error::CustomError>("Malformed input");
}
results[i] = static_cast<float>(result);
}
value.p1.x = results[0];
value.p1.y = results[1];
value.p2.x = results[2];
value.p2.y = results[3];
return nullptr;
}
static void to_shadow(const Bounds2f& value, shadow_type& shadow)
{
shadow[0] = float_to_string(value.p1.x);
shadow[1] = float_to_string(value.p1.y);
shadow[2] = float_to_string(value.p2.x);
shadow[3] = float_to_string(value.p2.y);
}
};
} // namespace staticjson
Bounds2f bbox;
bbox.p1.x = 0.133f;
bbox.p1.y = 0.1133f;
bbox.p2.x = 2.3f; //std::numeric_limits<float>::infinity();
bbox.p2.y = 4.0f; //std::numeric_limits<float>::quiet_NaN();
std::string a = staticjson::to_json_string(bbox);
std::cout << "json = " << a << std::endl;
Bounds2f br;
staticjson::ParseStatus err;
bool ret = staticjson::from_json_string(a.c_str(), &br, &err);
if (!ret) {
std::cout << err.description() << "\n";
}
std::cout << "bbox.p1.x = " << br.p1.x << std::endl;
std::cout << "bbox.p1.y = " << br.p1.y << std::endl;
std::cout << "bbox.p2.x = " << br.p2.x << std::endl;
std::cout << "bbox.p2.y = " << br.p2.y << std::endl;
json = ["1.33E-1","1.133E-1","2.3E0","4E0"]
bbox.p1.x = 0.133
bbox.p1.y = 0.1133
bbox.p2.x = 2.3
bbox.p2.y = 4
のようになります!(本来は, ビットが変わっていないかチェックしましょう!)
inf, nan は Infinity
, NaN
と print される. ryu_parse
(s2d)で読むと MALFORMED_INPUT と解釈されるので注意です!
その他
llvm の APFloat も正確に扱えたような?
(少なくとも, 10 年くらいまえの知識ですが, ASCII IR で浮動小数点数を扱うときは exact に表現しないとエラーになり, そのエラーチェックなどに APFloat が使われていたような記憶が)