32 位到 16 位浮点转换

Posted

技术标签:

【中文标题】32 位到 16 位浮点转换【英文标题】:32-bit to 16-bit Floating Point Conversion 【发布时间】:2010-12-12 04:19:12 【问题描述】:

我需要一个可以在 32 位和 16 位浮点数之间转换的跨平台库/算法。我不需要对 16 位数字进行数学运算;我只需要减小 32 位浮点数的大小,以便它们可以通过网络发送。我正在使用 C++。

我知道我会损失多少精度,但这对我的应用程序来说没问题。

IEEE 16 位格式会很棒。

【问题讨论】:

您确定能够衡量此转换带来的性能收益吗?您将需要通过网络发送大量这些数字以节省大量资金。你只能得到大约 3 位小数的精度,而且范围也不是那么大。 OTOH,如果你可以线程化你的程序,现在 CPU 基本上是免费的,而且 I/O 流的转换很容易线程化。如果发送的浮点数接近网络容量,那么 I/O 的节省将是实实在在的。 IE。这是一个很好的带宽/延迟权衡,因此仅在您实际遇到带宽问题且没有延迟问题时才相关。 C++ 对 16 位浮点数有任何原生支持吗? @Lazer:不,标准支持的最小尺寸是 32 位浮点数。 @Lazer,我认为 C++ 甚至不会谈论浮点数中的位数。规范很笼统。 【参考方案1】:

从单精度到半精度的完全转换。这是我的 SSE 版本的直接副本,因此它是无分支的。它利用 -true == ~0 来执行无分支选择这一事实(GCC 将 if 语句转换为一团糟的条件跳转,而 Clang 只是将它们转换为条件移动。)

更新(2019 年 11 月 4 日): 重新设计以支持具有完全正确舍入的单 双精度值。为了清楚起见,我还在每个无分支选择上方放置了相应的 if 语句作为注释。为了速度和合理性,所有传入的 NaN 都将转换为基本的安静 NaN,因为无法在格式之间可靠地转换嵌入的 NaN 消息。

#include <cstdint> // uint32_t, uint64_t, etc.
#include <cstring> // memcpy
#include <climits> // CHAR_BIT
#include <limits>  // numeric_limits
#include <utility> // is_integral_v, is_floating_point_v, forward

namespace std

  template< typename T , typename U >
  T bit_cast( U&& u ) 
    static_assert( sizeof( T ) == sizeof( U ) );
    union  T t; ; // prevent construction
    std::memcpy( &t, &u, sizeof( t ) );
    return t;
  
 // namespace std

template< typename T > struct native_float_bits;
template<> struct native_float_bits< float > using type = std::uint32_t; ;
template<> struct native_float_bits< double > using type = std::uint64_t; ;
template< typename T > using native_float_bits_t = typename native_float_bits< T >::type;

static_assert( sizeof( float ) == sizeof( native_float_bits_t< float > ) );
static_assert( sizeof( double ) == sizeof( native_float_bits_t< double > ) );

template< typename T, int SIG_BITS, int EXP_BITS >
struct raw_float_type_info 
  using raw_type = T;

  static constexpr int sig_bits = SIG_BITS;
  static constexpr int exp_bits = EXP_BITS;
  static constexpr int bits = sig_bits + exp_bits + 1;

  static_assert( std::is_integral_v< raw_type > );
  static_assert( sig_bits >= 0 );
  static_assert( exp_bits >= 0 );
  static_assert( bits <= sizeof( raw_type ) * CHAR_BIT );

  static constexpr int exp_max = ( 1 << exp_bits ) - 1;
  static constexpr int exp_bias = exp_max >> 1;

  static constexpr raw_type sign = raw_type( 1 ) << ( bits - 1 );
  static constexpr raw_type inf = raw_type( exp_max ) << sig_bits;
  static constexpr raw_type qnan = inf | ( inf >> 1 );

  static constexpr auto abs( raw_type v )  return raw_type( v & ( sign - 1 ) ); 
  static constexpr bool is_nan( raw_type v )  return abs( v ) > inf; 
  static constexpr bool is_inf( raw_type v )  return abs( v ) == inf; 
  static constexpr bool is_zero( raw_type v )  return abs( v ) == 0; 
;
using raw_flt16_type_info = raw_float_type_info< std::uint16_t, 10, 5 >;
using raw_flt32_type_info = raw_float_type_info< std::uint32_t, 23, 8 >;
using raw_flt64_type_info = raw_float_type_info< std::uint64_t, 52, 11 >;
//using raw_flt128_type_info = raw_float_type_info< uint128_t, 112, 15 >;

template< typename T, int SIG_BITS = std::numeric_limits< T >::digits - 1,
  int EXP_BITS = sizeof( T ) * CHAR_BIT - SIG_BITS - 1 >
struct float_type_info 
: raw_float_type_info< native_float_bits_t< T >, SIG_BITS, EXP_BITS > 
  using flt_type = T;
  static_assert( std::is_floating_point_v< flt_type > );
;

template< typename E >
struct raw_float_encoder

  using enc = E;
  using enc_type = typename enc::raw_type;

  template< bool DO_ROUNDING, typename F >
  static auto encode( F value )
  
    using flt = float_type_info< F >;
    using raw_type = typename flt::raw_type;
    static constexpr auto sig_diff = flt::sig_bits - enc::sig_bits;
    static constexpr auto bit_diff = flt::bits - enc::bits;
    static constexpr auto do_rounding = DO_ROUNDING && sig_diff > 0;
    static constexpr auto bias_mul = raw_type( enc::exp_bias ) << flt::sig_bits;
    if constexpr( !do_rounding )  // fix exp bias
      // when not rounding, fix exp first to avoid mixing float and binary ops
      value *= std::bit_cast< F >( bias_mul );
    
    auto bits = std::bit_cast< raw_type >( value );
    auto sign = bits & flt::sign; // save sign
    bits ^= sign; // clear sign
    auto is_nan = flt::inf < bits; // compare before rounding!!
    if constexpr( do_rounding ) 
      static constexpr auto min_norm = raw_type( flt::exp_bias - enc::exp_bias + 1 ) << flt::sig_bits;
      static constexpr auto sub_rnd = enc::exp_bias < sig_diff
        ? raw_type( 1 ) << ( flt::sig_bits - 1 + enc::exp_bias - sig_diff )
        : raw_type( enc::exp_bias - sig_diff ) << flt::sig_bits;
      static constexpr auto sub_mul = raw_type( flt::exp_bias + sig_diff ) << flt::sig_bits;
      bool is_sub = bits < min_norm;
      auto norm = std::bit_cast< F >( bits );
      auto subn = norm;
      subn *= std::bit_cast< F >( sub_rnd ); // round subnormals
      subn *= std::bit_cast< F >( sub_mul ); // correct subnormal exp
      norm *= std::bit_cast< F >( bias_mul ); // fix exp bias
      bits = std::bit_cast< raw_type >( norm );
      bits += ( bits >> sig_diff ) & 1; // add tie breaking bias
      bits += ( raw_type( 1 ) << ( sig_diff - 1 ) ) - 1; // round up to half
      //if( is_sub ) bits = std::bit_cast< raw_type >( subn );
      bits ^= -is_sub & ( std::bit_cast< raw_type >( subn ) ^ bits );
    
    bits >>= sig_diff; // truncate
    //if( enc::inf < bits ) bits = enc::inf; // fix overflow
    bits ^= -( enc::inf < bits ) & ( enc::inf ^ bits );
    //if( is_nan ) bits = enc::qnan;
    bits ^= -is_nan & ( enc::qnan ^ bits );
    bits |= sign >> bit_diff; // restore sign
    return enc_type( bits );
  

  template< typename F >
  static F decode( enc_type value )
  
    using flt = float_type_info< F >;
    using raw_type = typename flt::raw_type;
    static constexpr auto sig_diff = flt::sig_bits - enc::sig_bits;
    static constexpr auto bit_diff = flt::bits - enc::bits;
    static constexpr auto bias_mul = raw_type( 2 * flt::exp_bias - enc::exp_bias ) << flt::sig_bits;
    raw_type bits = value;
    auto sign = bits & enc::sign; // save sign
    bits ^= sign; // clear sign
    auto is_norm = bits < enc::inf;
    bits = ( sign << bit_diff ) | ( bits << sig_diff );
    auto val = std::bit_cast< F >( bits ) * std::bit_cast< F >( bias_mul );
    bits = std::bit_cast< raw_type >( val );
    //if( !is_norm ) bits |= flt::inf;
    bits |= -!is_norm & flt::inf;
    return std::bit_cast< F >( bits );
  
;

using flt16_encoder = raw_float_encoder< raw_flt16_type_info >;

template< typename F >
auto quick_encode_flt16( F && value )
 return flt16_encoder::encode< false >( std::forward< F >( value ) ); 

template< typename F >
auto encode_flt16( F && value )
 return flt16_encoder::encode< true >( std::forward< F >( value ) ); 

template< typename F = float, typename X >
auto decode_flt16( X && value )
 return flt16_encoder::decode< F >( std::forward< X >( value ) ); 

当然,并不总是需要完整的 IEEE 支持。如果您的值不需要接近零的对数分辨率,那么如前所述,将它们线性化为定点格式会快得多。

【讨论】:

一开始你写道它依赖于 GCC 的(-true == ~0)。我想在 Visual Studio 2012 中使用您的代码 sn-p,您是否有一个输入+预期输出对可以告诉我我的编译器是否做正确的事情?它似乎可以毫无问题地来回转换,并且上述表达式成立。 你的 Float16Compressor 类的许可证是什么? 公共领域的 Unlicense (choosealicense.com/licenses/unlicense)。 @Cygon -true == ~0 始终由标准保证,只要您在 - 之前将 bool 转换为 unsigned 整数类型,因为无符号整数是保证取负值模 2^n (即实际上负值的二进制补码表示)。所以-static_cast&lt;uint32_t&gt;(true)0xFFFFFFFF~static_cast&lt;uint32_t&gt;(0) 按标准 相同。它应该 也适用于几乎所有用于有符号类型的实际系统(因为它们通常是二进制补码),但这在理论上是由实现定义的。但是“未签名的否定”总是有效的。 已修复。舍入是可选的,因为它只影响最后一位精度,代价是操作的三倍。【参考方案2】:

半浮动:float f = ((h&amp;0x8000)&lt;&lt;16) | (((h&amp;0x7c00)+0x1C000)&lt;&lt;13) | ((h&amp;0x03FF)&lt;&lt;13); 浮动到一半:uint32_t x = *((uint32_t*)&amp;f);uint16_t h = ((x&gt;&gt;16)&amp;0x8000)|((((x&amp;0x7f800000)-0x38000000)&gt;&gt;13)&amp;0x7c00)|((x&gt;&gt;13)&amp;0x03ff);

【讨论】:

但当然要记住,这目前忽略了任何类型的上溢、下溢、非规范化值或无限值。【参考方案3】:

std::frexp 从普通浮点数或双精度数中提取有效数字和指数——然后您需要决定如何处理太大而无法放入半精度浮点数(饱和...?)的指数,并进行相应调整, 并将半精度数放在一起。 This article 有 C 源代码向您展示如何执行转换。

【讨论】:

实际上,我发送的值的范围非常有限:(-1000, 1000) 所以指数不是什么大问题。 @Matt,如果你知道指数永远不会低于/溢出,那么你的工作就会轻松很多!-) @Alex,确实,它确实让事情变得更容易了!谢谢。【参考方案4】:

考虑到您的需求(-1000、1000),也许使用定点表示会更好。

//change to 20000 to SHORT_MAX if you don't mind whole numbers
//being turned into fractional ones
const int compact_range = 20000;

short compactFloat(double input) 
    return round(input * compact_range / 1000);

double expandToFloat(short input) 
    return ((double)input) * 1000 / compact_range;

这将使您精确到 0.05。如果您将 20000 更改为 SHORT_MAX,您将获得更高的准确度,但某些整数在另一端会以小数结尾。

【讨论】:

+1 在几乎所有情况下,这都会比 16 位浮点数获得更多的准确度,而且数学运算更少,没有特殊情况。一个 16 位 IEEE 浮点数只有 10 位精度,并且在 (-1, 1) 范围内填充了一半的可能值 取决于 [-1000, 1000] 范围内的分布。如果大多数数字实际上都在 [-1,1] 范围内,那么平均而言,16 位浮点数的精度会更好。 使用 SHORT_MAX 和 1024 作为比例因子会更好,给出 10.6 位定点表示,并且所有整数都可以精确表示。精度为 1/2^6 = 0.015625,远好于 0.05,并且二次方比例因子很容易优化为位移(编译器可能会为您做)。 抱歉,应该是 11.5(忘记了符号位!)。那么精度是1/2^5 = 0.0325;对于性能更好的东西来说仍然不错。 @Matt,是否可以使用不同的格式将标准化值发送到位置向量?考虑为它们中的每一个使用适当的定点方案。【参考方案5】:

为什么这么复杂?我的实现不需要任何额外的库,符合 IEEE-754 FP16 格式,管理规范化和非规范化数字,无分支,来回转换大约需要 40 个时钟周期,并抛弃 NaN 或 @ 987654322@ 用于扩展范围。这就是位运算的神奇力量。

typedef unsigned short ushort;
typedef unsigned int uint;

uint as_uint(const float x) 
    return *(uint*)&x;

float as_float(const uint x) 
    return *(float*)&x;


float half_to_float(const ushort x)  // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits
    const uint e = (x&0x7C00)>>10; // exponent
    const uint m = (x&0x03FF)<<13; // mantissa
    const uint v = as_uint((float)m)>>23; // evil log2 bit hack to count leading zeros in denormalized format
    return as_float((x&0x8000)<<16 | (e!=0)*((e+112)<<23|m) | ((e==0)&(m!=0))*((v-37)<<23|((m<<(150-v))&0x007FE000))); // sign : normalized : denormalized

ushort float_to_half(const float x)  // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits
    const uint b = as_uint(x)+0x00001000; // round-to-nearest-even: add last bit after truncated mantissa
    const uint e = (b&0x7F800000)>>23; // exponent
    const uint m = b&0x007FFFFF; // mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding
    return (b&0x80000000)>>16 | (e>112)*((((e-112)<<10)&0x7C00)|m>>13) | ((e<113)&(e>101))*((((0x007FF000+m)>>(125-e))+1)>>1) | (e>143)*0x7FFF; // sign : normalized : denormalized : saturate

如何使用它并检查转换是否正确的示例:

#include <iostream>

void print_bits(const ushort x) 
    for(int i=15; i>=0; i--) 
        cout << ((x>>i)&1);
        if(i==15||i==10) cout << " ";
        if(i==10) cout << "      ";
    
    cout << endl;

void print_bits(const float x) 
    uint b = *(uint*)&x;
    for(int i=31; i>=0; i--) 
        cout << ((b>>i)&1);
        if(i==31||i==23) cout << " ";
        if(i==23) cout << "   ";
    
    cout << endl;


int main() 
    const float x = 1.0f;
    const ushort x_compressed = float_to_half(x);
    const float x_decompressed = half_to_float(x_compressed);
    print_bits(x);
    print_bits(x_compressed);
    print_bits(x_decompressed);
    return 0;

输出:

0 01111111    00000000000000000000000
0 01111       0000000000
0 01111111    00000000000000000000000

【讨论】:

这个答案是最好的。谢谢。 太棒了!谢谢! 不过有一个问题:as_uint((float)m) 是做什么的?不是NO-OP吗?我的意思是,我想知道你为什么不写这样的“bit hack”行:const uint v = m&gt;&gt;23; @cesss 这会将整数 m 转换为浮点数,然后从该浮点数中提取指数位。强制转换隐式地执行 log2 来计算指数,这就是我用来计算前导零的方法。请注意,浮点转换 ( (float)m ) 和将位重新解释为整数 ( as_uint ) 是非常不同的事情:转换会更改位(但除了舍入之外不会更改表示的数字)并且重新解释不会更改位(但表示的数字完全不同)。 谢谢@ProjectPhysX,我没有意识到你没有转换为整数。顺便说一句,我倾向于相信这是 UB,因为它是没有联合的类型双关语。【参考方案6】:

如果您要发送信息流,您可能会做得比这更好,特别是如果一切都在一致的范围内,就像您的应用程序似乎有的那样。

发送一个小标题,它只包含一个 float32 最小值和最大值,然后您可以将您的信息作为两者之间的 16 位插值值发送。正如您所说,精度不是什么大问题,您甚至可以一次发送 8 位。

在重建时,您的价值类似于:

float t = _t / numeric_limits<unsigned short>::max();  // With casting, naturally ;)
float val = h.min + t * (h.max - h.min);

希望对您有所帮助。

-汤姆

【讨论】:

这是一个很好的解决方案,特别是对于您知道将始终在 (-1, 1) 范围内的标准化向量/四元数值。 使用插值而不只是缩放的问题在于,零不能准确表示,并且某些系统对 4x4 矩阵数学等敏感。例如,假设 (min,max-min) 是 (-11.356439590454102, 23.32344913482666),那么你可以得到的最接近零的是 0.00010671140473306195。 谢谢,刚刚使用这种方法优化了我保存的游戏的大小。使用值“0”来存储精确的 0.0000。【参考方案7】:

这个问题已经有点老了,但为了完整起见,你也可以看看this paper for half-to-float and float-to-half conversion。

他们使用具有相对较小查找表的无分支表驱动方法。它完全符合 IEEE 标准,甚至在性能上击败了 Phernost 的符合 IEEE 标准的无分支转换例程(至少在我的机器上)。但当然,他的代码更适合 SSE,而且不太容易受到内存延迟影响。

【讨论】:

+1 这篇论文非常好。请注意,它在处理 NaN 的方式上并不完全符合 IEEE。 IEEE 表示,仅当设置了至少一个尾数位时,数字才是 NaN。由于提供的代码忽略了低位,一些 32 位 NaN 被错误地转换为 Inf。不过不太可能发生。【参考方案8】:

这种 16 位到 32 位浮点的转换对于您不必考虑无穷大或 NaN 并且可以接受非正规为零 (DAZ) 的情况非常快。 IE。它适用于对性能敏感的计算,但如果您预计会遇到非规范化,则应注意被零除。

请注意,这最适合 x86 或其他具有条件移动或“设置 if”等价物的平台。

    从输入中去除符号位 将尾数的最高有效位与第 22 位对齐 调整指数偏差 如果输入指数为零,则将位设置为全零 重新插入符号位

相反的情况适用于单精度到半精度,但有一些补充。

void float32(float* __restrict out, const uint16_t in) 
    uint32_t t1;
    uint32_t t2;
    uint32_t t3;

    t1 = in & 0x7fff;                       // Non-sign bits
    t2 = in & 0x8000;                       // Sign bit
    t3 = in & 0x7c00;                       // Exponent

    t1 <<= 13;                              // Align mantissa on MSB
    t2 <<= 16;                              // Shift sign bit into position

    t1 += 0x38000000;                       // Adjust bias

    t1 = (t3 == 0 ? 0 : t1);                // Denormals-as-zero

    t1 |= t2;                               // Re-insert sign bit

    *((uint32_t*)out) = t1;
;

void float16(uint16_t* __restrict out, const float in) 
    uint32_t inu = *((uint32_t*)&in);
    uint32_t t1;
    uint32_t t2;
    uint32_t t3;

    t1 = inu & 0x7fffffff;                 // Non-sign bits
    t2 = inu & 0x80000000;                 // Sign bit
    t3 = inu & 0x7f800000;                 // Exponent

    t1 >>= 13;                             // Align mantissa on MSB
    t2 >>= 16;                             // Shift sign bit into position

    t1 -= 0x1c000;                         // Adjust bias

    t1 = (t3 > 0x38800000) ? 0 : t1;       // Flush-to-zero
    t1 = (t3 < 0x8e000000) ? 0x7bff : t1;  // Clamp-to-max
    t1 = (t3 == 0 ? 0 : t1);               // Denormals-as-zero

    t1 |= t2;                              // Re-insert sign bit

    *((uint16_t*)out) = t1;
;

请注意,您可以将常量 0x7bff 更改为 0x7c00 以使其溢出到无穷大。

源代码见GitHub。

【讨论】:

您可能指的是0x80000000 而不是0x7FFFFFFF,否则您将执行abs 而不是归零。最后一个操作也可以写成:t1 &amp;= 0x80000000 | (static_cast&lt;uint32_t&gt;(t3==0)-1)。虽然它可能取决于平台(它对分支预测失败的敏感性、条件赋值指令的存在......)和编译器(它为平台本身生成适当代码的能力)哪个更好。对于不太熟悉二进制操作和 C++ 的类型规则的人来说,您的版本可能看起来更好更清晰。 感谢您发现这一点,我已将您的 cmets 纳入答案。 在 float16 中,Clamp-to-max 测试显然是错误的,它总是被触发。归零测试的比较符号方式错误。我认为这两个测试应该是:t1 = (t3 &lt; 0x38800000) ? 0 : t1;t1 = (t3 &gt; 0x47000000) ? 0x7bff : t1; 那么非正规化为零测试是多余的,因为清零也会捕获这种情况。【参考方案9】:

此处其他答案中描述的大多数方法要么在从浮点数转换为半数时无法正确舍入,要么丢弃次正规数,这是一个问题,因为 2**-14 成为您最小的非零数,或者做不幸的事情与 Inf / NaN。 Inf 也是一个问题,因为一半的最大有限数略小于 2^16。 OpenEXR 是不必要的缓慢和复杂,最后我看了一下。一种快速正确的方法将使用 FPU 进行转换,或者作为直接指令,或者使用 FPU 舍入硬件来使正确的事情发生。任何半浮点转换都不应比 2^16 元素查找表慢。

以下是难以击败的:

在 OS X / iOS 上,您可以使用 vImageConvert_PlanarFtoPlanar16F 和 vImageConvert_Planar16FtoPlanarF。请参阅 Accelerate.framework。

英特尔 ivybridge 为此添加了 SSE 指令。参见 f16cintrin.h。 Neon 的 ARM ISA 中添加了类似的指令。请参见 arm_neon.h 中的 vcvt_f32_f16 和 vcvt_f16_f32。在 iOS 上,您需要使用 arm64 或 armv7s 架构来访问它们。

【讨论】:

【参考方案10】:

此代码将 32 位浮点数转换为 16 位并返回。

#include <x86intrin.h>
#include <iostream>

int main()

    float f32;
    unsigned short f16;
    f32 = 3.14159265358979323846;
    f16 = _cvtss_sh(f32, 0);
    std::cout << f32 << std::endl;
    f32 = _cvtsh_ss(f16);
    std::cout << f32 << std::endl;
    return 0;

我使用 Intel icpc 16.0.2 进行了测试:

$ icpc a.cpp

g++ 7.3.0:

$ g++ -march=native a.cpp

和clang++ 6.0.0:

$ clang++ -march=native a.cpp

打印出来:

$ ./a.out
3.14159
3.14062

有关这些内在函数的文档可在以下位置获得:

https://software.intel.com/en-us/node/524287

https://clang.llvm.org/doxygen/f16cintrin_8h.html

【讨论】:

对于那些因无法编译而感到沮丧的人:试试编译器标志-march=native 感谢 @user14717,我添加了使用 Intel、GCC 和 Clang 编译它的确切说明。【参考方案11】:

这个问题已经过时并且已经得到解答,但我认为值得一提的是一个开源 C++ 库,它可以创建 16 位 IEEE 兼容的半精度浮点数,并且有一个与内置浮点类型几乎相同的类,但使用 16 位而不是 32 位。它是 "half" class of the OpenEXR library。该代码在一个宽松的 BSD 风格许可证下。我不相信它在标准库之外有任何依赖。

【讨论】:

当我们谈论开源 C++ 库时,它提供了符合 IEEE 的半精度类型,这些类型尽可能地像内置浮点类型,请查看 half library(免责声明:是我写的)。【参考方案12】:

我遇到了同样的问题,发现this link 非常有帮助。只需将文件“ieeehalfprecision.c”导入您的项目并像这样使用它:

float myFloat = 1.24;
uint16_t resultInHalf;
singles2halfp(&resultInHalf, &myFloat, 1); // it accepts a series of floats, so use 1 to input 1 float

// an example to revert the half float back
float resultInSingle;
halfp2singles(&resultInSingle, &resultInHalf, 1);

我还更改了一些代码(请参阅链接中作者(James Tursa)的评论):

#define INT16_TYPE int16_t 
#define UINT16_TYPE uint16_t 
#define INT32_TYPE int32_t 
#define UINT32_TYPE uint32_t

【讨论】:

【参考方案13】:

我发现implementation 使用 AVX2 从半浮点格式转换为单浮点格式并返回。这些算法比软件实现要快得多。希望对你有用。

32 位浮点数到 16 位浮点数的转换:

#include <immintrin.h"

inline void Float32ToFloat16(const float * src, uint16_t * dst)

    _mm_storeu_si128((__m128i*)dst, _mm256_cvtps_ph(_mm256_loadu_ps(src), 0));


void Float32ToFloat16(const float * src, size_t size, uint16_t * dst)

    assert(size >= 8);

    size_t fullAlignedSize = size&~(32-1);
    size_t partialAlignedSize = size&~(8-1);

    size_t i = 0;
    for (; i < fullAlignedSize; i += 32)
    
        Float32ToFloat16(src + i + 0, dst + i + 0);
        Float32ToFloat16(src + i + 8, dst + i + 8);
        Float32ToFloat16(src + i + 16, dst + i + 16);
        Float32ToFloat16(src + i + 24, dst + i + 24);
    
    for (; i < partialAlignedSize; i += 8)
        Float32ToFloat16(src + i, dst + i);
    if(partialAlignedSize != size)
        Float32ToFloat16(src + size - 8, dst + size - 8);

16 位浮点数到 32 位浮点数的转换:

#include <immintrin.h"

inline void Float16ToFloat32(const uint16_t * src, float * dst)

    _mm256_storeu_ps(dst, _mm256_cvtph_ps(_mm_loadu_si128((__m128i*)src)));


void Float16ToFloat32(const uint16_t * src, size_t size, float * dst)

    assert(size >= 8);

    size_t fullAlignedSize = size&~(32-1);
    size_t partialAlignedSize = size&~(8-1);

    size_t i = 0;
    for (; i < fullAlignedSize; i += 32)
    
        Float16ToFloat32<align>(src + i + 0, dst + i + 0);
        Float16ToFloat32<align>(src + i + 8, dst + i + 8);
        Float16ToFloat32<align>(src + i + 16, dst + i + 16);
        Float16ToFloat32<align>(src + i + 24, dst + i + 24);
    
    for (; i < partialAlignedSize; i += 8)
        Float16ToFloat32<align>(src + i, dst + i);
    if (partialAlignedSize != size)
        Float16ToFloat32<false>(src + size - 8, dst + size - 8);

【讨论】:

【参考方案14】:

谢谢Code for decimal to single precision

我们实际上可以尝试将相同的代码编辑到半精度,但是使用 gcc C 编译器是不可能的,所以请执行以下操作

sudo apt install clang

那就试试下面的代码

// A C code to convert Decimal value to IEEE 16-bit floating point Half precision

#include <stdio.h>

void printBinary(int n, int i)

 

    int k;
    for (k = i - 1; k >= 0; k--) 
 
        if ((n >> k) & 1)
            printf("1");
        else
            printf("0");
    

 
typedef union 
    
    __fp16 f;
    struct
    
        unsigned int mantissa : 10;
        unsigned int exponent : 5;
        unsigned int sign : 1;
 
     raw;
 myfloat;
 

// Driver Code
int main()

    myfloat var;
    var.f = 11;
    printf("%d | ", var.raw.sign);
    printBinary(var.raw.exponent, 5);
    printf(" | ");
    printBinary(var.raw.mantissa, 10);
    printf("\n");
    return 0;

在终端中编译代码

clang code_name.c -o code_name
./code_name

这里

__fp16

clang C 编译器

支持的 2 字节浮点数据类型

【讨论】:

以上是关于32 位到 16 位浮点转换的主要内容,如果未能解决你的问题,请参考以下文章

在 C/C+ 中从 16 位线性 PCM 音频转换为 32 位浮点的最佳方法?

将32位浮点音频转换为16位

将 32 位浮点数转换为 16 位 PCM 范围

如何将 A16B16G16R16F 转换为 ARGB32?

将一个 32 位浮点数转换为两个 16 位 uint 数,然后再次转换回该 32 位浮点数

matlab强制转换uint16转int16