由于从 float 自动转换为 double,std::num_put 出现 nan-boxing 问题

2023-12-25

我在用着这个帖子 https://stackoverflow.com/a/53691931/3336423用一些额外的信息来扩展 nan 值和这个帖子 https://stackoverflow.com/a/35272035/3336423修改std::cout行为并显示此额外信息。

这是定义函数和的代码NumPut class:

#include <iostream>
#include <assert.h>
#include <limits>
#include <bitset>
#include <cmath>
#include <locale>
#include <ostream>
#include <sstream>

template <typename T>
void showValue( T val, const std::string& what )
{
    union uT {
      T d;
      unsigned long long u;
    };
    uT ud;
    ud.d = val;
    std::bitset<sizeof(T) * 8> b(ud.u);
    std::cout << val << " (" << what << "): " << b.to_string() << std::endl;
}

template <typename T>
T customizeNaN( T value, char mask )
{
    T res = value;
    char* ptr = (char*) &res;
    assert( ptr[0] == 0 );
    ptr[0] |= mask;
    return res;
}

template <typename T>
bool isCustomNaN( T value, char mask )
{
    char* ptr = (char*) &value;
    return ptr[0] == mask;
}

template <typename T>
char getCustomNaNMask( T value )
{
    char* ptr = (char*) &value;
    return ptr[0];
}

template <typename Iterator = std::ostreambuf_iterator<char> >
class NumPut : public std::num_put<char, Iterator>
{
private:
    using base_type = std::num_put<char, Iterator>;

public:
    using char_type = typename base_type::char_type;
    using iter_type = typename base_type::iter_type;

    NumPut(std::size_t refs = 0)
    :   base_type(refs)
    {}

protected:
    virtual iter_type do_put(iter_type out, std::ios_base& str, char_type fill, double v) const override {
        if(std::isnan(v))
        {
            char mask = getCustomNaNMask(v);
            if ( mask == 0x00 )
            {
                out = std::copy(std::begin(NotANumber), std::end(NotANumber), out);
            }
            else
            {
                std::stringstream maskStr;
                maskStr << "(0x" << std::hex << (unsigned) mask << ")";
                std::string temp = maskStr.str();
                out = std::copy(std::begin(CustomNotANumber), std::end(CustomNotANumber), out);
                out = std::copy(std::begin(temp), std::end(temp), out);
            }
        }
        else
        {
            out = base_type::do_put(out, str, fill, v);
        }
        return out;
    }

private:
    static const std::string NotANumber;
    static const std::string CustomNotANumber;
};

template<typename Iterator> const std::string NumPut<Iterator>::NotANumber = "Not a Number";
template<typename Iterator> const std::string NumPut<Iterator>::CustomNotANumber = "Custom Not a Number";

inline void fixNaNToStream( std::ostream& str )
{
    str.imbue( std::locale(str.getloc(), new NumPut<std::ostreambuf_iterator<char>>() ) );
}

一个简单的测试函数:

template<typename T>
void doTest()
{
    T regular_nan = std::numeric_limits<T>::quiet_NaN();
    T myNaN1 = customizeNaN( regular_nan, 0x01 );
    T myNaN2 = customizeNaN( regular_nan, 0x02 );

    showValue( regular_nan, "regular" );
    showValue( myNaN1, "custom 1" );
    showValue( myNaN2, "custom 2" );
}

我的主要程序:

int main(int argc, char *argv[])
{
    fixNaNToStream( std::cout );

    doTest<double>();
    doTest<float>();

    return 0;
}

doTest<double>输出:

Not a Number (regular): 0111111111111000000000000000000000000000000000000000000000000000
Custom Not a Number(0x1) (custom 1): 0111111111111000000000000000000000000000000000000000000000000001
Custom Not a Number(0x2) (custom 2): 0111111111111000000000000000000000000000000000000000000000000010

doTest<float>输出:

Not a Number (regular): 01111111110000000000000000000000
Not a Number (custom 1): 01111111110000000000000000000001
Not a Number (custom 2): 01111111110000000000000000000010

虽然我期望float:

Not a Number (regular): 01111111110000000000000000000000
Custom Not a Number(0x1) (custom 1): 01111111110000000000000000000001
Custom Not a Number(0x2) (custom 2): 01111111110000000000000000000010

问题是num_put只有一个虚拟的do_put for double, 不是为了float. So my float被默默地投射到double,丢失我的扩展信息。

我知道有一些替代方案,比如使用FloatFormat从第二篇文章开始,或者只是写一个聪明的float2double函数并在将 NaN 值发送到输出流之前调用它,但它们要求开发人员处理这种情况......他可能会忘记。

有没有办法在内部实现NumPut类或任何其他能让事情正常工作的东西float被发送到灌输者stream就像它适用于double?

我的要求是能够简单地调用类似的函数fixNaNToStream对于任何输出流(std::cout, local std::stringstream, ...) 然后发送float and double并将它们识别为我的自定义 NaN 并相应地显示。


问题是 num_put 仅具有用于 double 的虚拟 do_put,而不具有用于 float 的虚拟 do_put。所以我的浮点数被默默地转换为双精度浮点数,丢失了我的扩展信息。

由于数字转换时携带该信息的位的位置不同,因此该信息丢失了float to double:

// Assuming an IEE-754 floating-point representation of float and double
0 11111111 10000000000000000000010
0 11111111111 1000000000000000000001000000000000000000000000000000

请注意,尾数位“移动”了 3 个位置,因为指数还需要 3 位。

另外,值得注意的是此页面中的说明:https://en.cppreference.com/w/cpp/numeric/math/isnan https://en.cppreference.com/w/cpp/numeric/math/isnan

IEEE-754 并不要求复制 NaN 来保留其位表示形式(符号和有效负载),但大多数实现都这样做。

我假设同样适用于转换此类值,因此,即使忽略 OP 代码中未定义行为的其他原因,NaN 装箱方法是否可以工作实际上是实现定义的。

在我以前尝试回答这个问题时,我使用了一些通过不同偏移量进行的显式位移来获得结果,但是作为jpo38 https://stackoverflow.com/users/3336423/jpo38还发现,最简单的方法是始终生成一个floatNaN 然后正确转换。

标准库函数标准::南夫 https://en.cppreference.com/w/cpp/numeric/math/nan可用于生成“定制”floatNaN,但在下面的演示片段中我不会使用它。

#include <cstdint>
#include <limits>
#include <cstring>
#include <cassert>
#include <type_traits>
#include <iostream>
#include <bitset>
#include <array>
#include <climits>

namespace my {

// Waiting for C++20 std::bit_cast
// source: https://en.cppreference.com/w/cpp/numeric/bit_cast
template <class To, class From>
typename std::enable_if<
    (sizeof(To) == sizeof(From)) &&
    std::is_trivially_copyable<From>::value &&
    std::is_trivial<To>::value,
    // this implementation requires that To is trivially default constructible
    To>::type
// constexpr support needs compiler magic
bit_cast(const From &src) noexcept
{
    To dst;
    std::memcpy(&dst, &src, sizeof(To));
    return dst;
}

template <typename T, std::size_t Size = sizeof(T)>
void print_bits(T x)
{
    std::array<unsigned char, Size> buf;
    std::memcpy(buf.data(), &x, Size);
    for (auto it = buf.crbegin(); it != buf.crend(); ++it)
    {
        std::bitset<CHAR_BIT> b{*it};
        std::cout << b.to_string();
    }
    std::cout << '\n';
}

// The following assumes that both floats and doubles store the mantissa
// in the lower bits and that while casting a NaN (float->double or double->float)
// the most significant of those aren't changed
template <typename T>
auto boxed_nan(uint8_t data = 0) -> typename std::enable_if<std::numeric_limits<T>::has_quiet_NaN, T>::type
{
    return bit_cast<float>(
        bit_cast<uint32_t>(std::numeric_limits<float>::quiet_NaN()) |
        static_cast<uint32_t>(data)
    );
}

template <typename T>
uint8_t unbox_nan(T num)
{
    return bit_cast<uint32_t>(static_cast<float>(num));
}

}; // End of namespace 'my'


int main()
{
    auto my_nan = my::boxed_nan<float>(42);
    my::print_bits(my_nan);
    my::print_bits(static_cast<double>(my_nan));
    assert(my::unbox_nan(my_nan) == 42);
    assert(my::unbox_nan(static_cast<double>(my_nan)) == 42);

    auto my_d_nan = my::boxed_nan<double>(17);
    my::print_bits(my_d_nan);
    my::print_bits(static_cast<float>(my_d_nan));
    assert(my::unbox_nan(my_d_nan) == 17);
    assert(my::unbox_nan(static_cast<float>(my_d_nan)) == 17);

    auto my_ld_nan = my::boxed_nan<long double>(9);
    assert(my::unbox_nan(my_ld_nan) == 9);
    assert(my::unbox_nan(static_cast<double>(my_ld_nan)) == 9);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

由于从 float 自动转换为 double,std::num_put 出现 nan-boxing 问题 的相关文章

随机推荐

  • SQL Server T-SQL 中的整数最大值常量?

    T SQL 中是否有像其他语言中的常量一样提供数据类型 例如 int 的最大值和最小值范围 我有一个代码表 其中每一行都有一个上限和下限列 我需要一个条目来表示一个范围 其中上限是 int 可以容纳的最大值 有点像黑客无穷大 我不想对其进行
  • 通过 Javascript 缓存与在服务器中设置 HTTPResponse 标头有什么区别

    在前端 我使用 AngularJS resource 进行 GET 请求 在后端 我使用 SpringMVC 以 Restful 方式公开我的方法 现在我想缓存只有一些 of my GET要求 我注意到有一些方法可以做到这一点 例如使用 c
  • 如何以编程方式填充使用 React 构建的输入元素?

    我的任务是抓取用 React 构建的网站 我正在尝试填写输入字段并使用 javascript 注入到页面 移动设备中的 selenium 或 webview 提交表单 这对其他所有网站 技术来说都是一种魅力 但 React 似乎是一个真正的
  • 从 Web 应用程序中删除经过 Firebase 身份验证的用户

    我想添加选项以从我的 Web 应用程序中从经过 Firebase 身份验证的用户列表中删除用户 我使用的身份验证方法是电子邮件和密码身份验证 该应用程序是移动单页应用程序 基于js html css 文件 浏览器应用程序 我可以使用 fir
  • LINQ TO Nhibernate 计数

    我正在尝试使用 LINQ to Nhibernate 来获取数据库中表的计数 但是 我正在运行的代码是拉回表中的所有记录 而不是从表中运行 select count 这是我的代码 public int GetTotalCount Func
  • 我需要将 strtol 的结果转换为 int 吗?

    以下代码不会对 g 4 1 1 发出警告 并且 Wall int octalStrToInt const std string s return strtol s c str 0 8 我期待一个警告 因为 strtol 返回一个long i
  • LINQ 表达式树是真正的树吗?

    LINQ 表达式树是否是正确的树 如图中所示 有向或无向 维基百科似乎不太同意 而没有循环 以下 C 表达式的表达式树的根是什么 string s gt s Length 表达式树如下所示 其中 gt 表示可访问其他节点的节点的属性名称 g
  • ngOninit 变量未在 html Angular 4 中绑定

    在谷歌地图上工作 能够显示地图 我想显示当前位置但不显示 export class AppComponent title ngOnInit if navigator geolocation navigator geolocation get
  • Xml 中的非法字符

    我有一个 PHP 文件 它根据从多个来源导入的数据生成 Xml 站点地图 由于导入数据的一行中存在非法字符 我的站点地图目前格式不正确 但我正在努力将其删除 该字符看起来代表 平方 或上标 2 并且被表示为正方形 我尝试将其粘贴到十六进制编
  • Glide - 如何并行加载多个图像?

    我正在尝试一个简单的测试 MainActivity java public class MainActivity extends AppCompatActivity private static final int N 20 private
  • numeric_limits::digits10 的含义是什么

    numeric limits digits10 的确切含义是什么 stackoverflow中的一些其他相关问题让我认为这是双精度的最大精度 但是 当精度大于 17 2 numeric limits digits10 时 以下原型开始工作
  • 向某些用户显示管理消息的最佳方式?

    我正在用 PHP MySQL jQuery 构建一个社交网站 用户登录我的网站后 我想查询数据库并获取管理员公告 如果存在 这将是一个消息框 在页面上向所有用户显示 但它会有一个X单击它并且不再显示它 直到管理员发布新的公告消息 因此 如果
  • 等到线程执行结束后再执行下一个方法

    我有一个 JFrame 子类 它管理我的 UI 并调用 Item 对象的方法来导入数据库中的项目 public TestGUI throws IOException initComponents Item importItems progr
  • 带有自定义cacheResolver的spring缓存

    我想要动态缓存名称和 spring4 1 允许 http docs spring io spring docs current spring framework reference html cache html 从 Spring 4 1
  • 加载 MySQLdb 模块时出错:libmysqlclient.so.20:无法打开共享对象文件:没有这样的文件或目录

    我有一个正在运行的 django 项目 由于某些原因 我必须删除当前的 mysql 版本并在我的计算机中安装不同的 MySQL 版本 但现在当我尝试运行该程序时出现如下错误 raise ImproperlyConfigured Error
  • Golang通道,执行顺序

    我正在学习 Go 并且遇到了以下代码片段 package main import fmt func sum a int c chan int sum 0 for v range a sum v c lt sum send sum to c
  • CodeIgniter 验证码验证

    我创建了一些表单 用于将数据插入数据库并检查数据是否是从人类发送的 我使用了已经集成到 CI 中的验证码 这是我的控制器 checkrules array img path gt realpath APPPATH upload checki
  • 如何在 Tkinter 中获取屏幕尺寸?

    我想知道是否可以使用 Tkinter 计算屏幕尺寸 我想要这个 这样可以使程序在屏幕中央打开 import tkinter as tk root tk Tk screen width root winfo screenwidth scree
  • [[15.0.0,

    图书馆com google android gms play services measurement base 15 0 0 15 0 0 16 0 0 16 0 0 处的各个其他库正在请求 但解析为 16 0 0 禁用插件并使用 gra
  • 由于从 float 自动转换为 double,std::num_put 出现 nan-boxing 问题

    我在用着这个帖子 https stackoverflow com a 53691931 3336423用一些额外的信息来扩展 nan 值和这个帖子 https stackoverflow com a 35272035 3336423修改st