{"id":29542,"date":"2020-08-31T10:00:19","date_gmt":"2020-08-31T17:00:19","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=29542"},"modified":"2020-08-31T13:55:30","modified_gmt":"2020-08-31T20:55:30","slug":"introducing-the-half-type","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/introducing-the-half-type\/","title":{"rendered":"Introducing the Half type!"},"content":{"rendered":"<p>The <code>IEEE 754<\/code> specification defines many floating point types, including: <code>binary16<\/code>, <code>binary32<\/code>, <code>binary64<\/code> and <code>binary128<\/code>. Most developers are familiar with <code>binary32<\/code> (equivalent to <code>float<\/code> in C#) and <code>binary64<\/code> (equivalent to <code>double<\/code> in C#). They provide a standard format to represent a wide range of values with a precision acceptable for many applications. .NET has always had <code>float<\/code> and <code>double<\/code> and with .NET 5 Preview 7, we&#8217;ve added a new <code>Half<\/code> type (equivalent to <code>binary16<\/code>)!<\/p>\n<p>A <code>Half<\/code> is a binary floating-point number that occupies 16 bits. With half the number of bits as float, a <code>Half<\/code> number can represent values in the range \u00b165504. More formally, the <code>Half<\/code> type is defined as a base-2 16-bit interchange format meant to support the exchange of floating-point data between implementations. One of the primary use cases of the <code>Half<\/code> type is to save on storage space where the computed result does not need to be stored with full precision. Many computation workloads already take advantage of the <code>Half<\/code> type: machine learning, graphics cards, the latest processors, native SIMD libraries etc. With the new <code>Half<\/code> type, we expect to unlock many applications in these workloads.<\/p>\n<h3>Let&#8217;s explore the <code>Half<\/code> type:<\/h3>\n<p>The 16 bits in the <code>Half<\/code> type are split into:<\/p>\n<ol>\n<li>Sign bit: 1 bit<\/li>\n<li>Exponent bits: 5 bits<\/li>\n<li>Significand bits: 10 bits (with 1 implicit bit that is not stored)<\/li>\n<\/ol>\n<p>Despite that fact that the significand is made up of 10 bits, the total precision is really 11 bits. The format is assumed to have an implicit leading bit of value 1 (unless the exponent field is all zeros, in which case the leading bit has a value 0). To represent the number 1 in the <code>Half<\/code> format, we&#8217;d use the bits:<\/p>\n<pre class=\"prettyprint\">0\u00a001111\u00a00000000000\u00a0=\u00a01<\/pre>\n<div>\n<p>The leading bit (our sign bit) is <code>0<\/code>, indicating a positive number. The exponent bits are <code>01111<\/code>, or <code>15<\/code> in decimal. However, the exponent bits don&#8217;t represent the exponent directly. Instead, an exponent bias is defined that lets the format represent both positive and negative exponents. For the <code>Half<\/code> type, that exponent bias is <code>15<\/code>. The true exponent is derived by subtracting <code>15<\/code> from the stored exponent. Therefore, <code>01111<\/code> represents the exponent <code>e = 01111 (in binary) - 15 (the exponent bias) = 0<\/code>. The significand is <code>0000000000<\/code>, which can be interpreted as the number <code>.significand(in base 2)<\/code> in base 2, <code>0<\/code> in our case. If, for example, the significand was <code>0000011010 (26 in decimal)<\/code>, we can divide its decimal value <code>26<\/code> by the number of values representable in <code>10 bits (1 &lt;&lt; 10)<\/code>: so the significand <code>0000011010 (in binary)<\/code> is <code>26 \/ (1 &lt;&lt; 10) = 26 \/ 1024 = 0.025390625<\/code> in decimal. Finally, because our stored exponent bits <code>(01111)<\/code> are not all <code>0<\/code>, we have an implicit leading bit of <code>1<\/code>. Therefore,<\/p>\n<div>\n<pre class=\"prettyprint\">0 01111 0000000000 = 2^0 * (1 + 0\/1024) = 1<\/pre>\n<\/div>\n<\/div>\n<div>\n<div>\n<div>In\u00a0general,\u00a0the\u00a016\u00a0bits\u00a0of\u00a0a\u00a0<code>Half<\/code>\u00a0value\u00a0are\u00a0interpreted\u00a0as\u00a0<code>-1^(sign\u00a0bit)\u00a0*\u00a02^(storedExponent\u00a0-\u00a015)\u00a0*\u00a0(implicitBit\u00a0+\u00a0(significand\/1024))<\/code>.\u00a0A\u00a0special\u00a0case\u00a0exists\u00a0for\u00a0the\u00a0stored\u00a0exponent\u00a0<code>00000<\/code>.\u00a0In\u00a0this\u00a0case,\u00a0the\u00a0bits\u00a0are\u00a0interpreted\u00a0as\u00a0<code>-1^(sign\u00a0bit)\u00a0*\u00a02^(-14)\u00a0*\u00a0(0\u00a0+\u00a0(significand\/1024))<\/code>.\u00a0Let&#8217;s\u00a0look\u00a0at\u00a0the\u00a0bit\u00a0representations of some other numbers in the <code>Half<\/code> format:<\/div>\n<\/div>\n<\/div>\n<div>\n<div>\n<h3><\/h3>\n<h3>Smallest positive non-zero value<\/h3>\n<div>\n<div>\n<p><code><code><\/code><\/code><\/p>\n<pre class=\"prettyprint\">0\u00a000000\u00a00000000001\u00a0=\u00a0-1^(0)\u00a0*\u00a02^(-14)\u00a0*\u00a0(0\u00a0+\u00a01\/1024)\u00a0\u2248\u00a00.000000059604645<\/pre>\n<p><code>\n<\/code>\u00a0(Note\u00a0the\u00a0implicit\u00a0bit\u00a0is\u00a00\u00a0here\u00a0because\u00a0the\u00a0stored\u00a0exponents\u00a0bits\u00a0are\u00a0all\u00a00)<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div><\/div>\n<div>\n<div>\n<h3>Largest\u00a0normal\u00a0number<\/h3>\n<\/div>\n<\/div>\n<div>\n<pre class=\"prettyprint\">0\u00a011110\u00a01111111111\u00a0=\u00a0-1^(0)\u00a0*\u00a02^(15)\u00a0*\u00a0(1\u00a0+\u00a01023\/1024)\u00a0\u2248\u00a065504<\/pre>\n<\/div>\n<div><\/div>\n<div>\n<h3>Negative\u00a0Infinity<\/h3>\n<div>\n<pre class=\"prettyprint\">1 11111 0000000000 = -Infinity<\/pre>\n<\/div>\n<\/div>\n<div><\/div>\n<div>\n<div>A\u00a0peculiarity\u00a0of\u00a0the\u00a0format\u00a0is\u00a0that\u00a0it\u00a0defines\u00a0both\u00a0positive\u00a0and\u00a0negative\u00a00:<\/div>\n<div>\n<pre class=\"prettyprint\">1\u00a000000\u00a00000000000\u00a0=\u00a0-0<\/pre>\n<\/div>\n<div><\/div>\n<div>\n<pre class=\"prettyprint\">0\u00a000000\u00a00000000000\u00a0=\u00a0+0<\/pre>\n<\/div>\n<div>\n<div>\n<h3>Conversions\u00a0to\/from\u00a0float\/double<\/h3>\n<div>\n<div>A\u00a0<code>Half<\/code>\u00a0can\u00a0be\u00a0converted\u00a0to\/from\u00a0a\u00a0float\/double\u00a0by\u00a0simply\u00a0casting\u00a0it:<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div>\n<pre class=\"prettyprint\">float f = (float)<code>half<\/code>; <code>Half<\/code> h = (<code>Half<\/code>)floatValue;<\/pre>\n<div>\n<div>\n<p>Any <code>Half<\/code> value, because <code>Half<\/code> uses only 16 bits, can be represented as a <code>float\/double<\/code> without loss of precision. However, the inverse is not true. Some precision may be lost when going from <code>float\/double<\/code> to <code>Half<\/code>. In .NET 5.0, the <code>Half<\/code> type is primarily an interchange type with no arithmetic operators defined on it. It only supports parsing, formatting and comparison operators. All arithmetic operations will need an explicit conversion to a <code>float\/double<\/code>. Future versions will consider adding arithmetic operators directly on <code>Half<\/code>.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div>\n<div>\n<div>\n<p>As library authors, one of the points to consider is that a language can add support for a type in the future. It is conceivable that C# adds a <code>half<\/code> type in the future. Language support would enable an identifier such as <code>f16<\/code>(similar to the <code>f<\/code> that exists today) and implicit\/explicit conversions. Thus, the library defined type <code>Half<\/code> needs to be defined in a manner that does not result in any breaking changes if <code>half<\/code> becomes a reality. Specifically, we needed to be careful about adding operators to the <code>Half<\/code> type. Implicit conversions to <code>float\/double<\/code> could lead to potential breaking changes if language support is added. On the other hand, having a <code>Float\/Double<\/code> property on the <code>Half<\/code> type felt less than ideal. In the end, we decided to add explicit operators to convert to\/from <code>float\/double<\/code>. If C# does add support for <code>half<\/code>, no user code would break, since all casts would be explicit.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div>\n<div>\n<h3>Adoption<\/h3>\n<div>\n<div>We expect that <code>Half<\/code> will find its way into many codebases. The <code>Half<\/code> type plugs a gap in the .NET ecosystem and we expect many numerics libraries to take advantage of it. In the open source arena, ML.NET is expected to start using <code>Half<\/code>, the Apache Arrow project&#8217;s C# implementation has an <a href=\"https:\/\/issues.apache.org\/jira\/browse\/ARROW-9048\">open issue<\/a> for it and the DataFrame library tracks a related issue <a href=\"https:\/\/github.com\/dotnet\/corefxlab\/issues\/2930\">here<\/a>. As more intrinsics are unlocked in .NET for x86 and ARM processors, we expect that computation performance with <code>Half<\/code> can be accelerated and result in more efficient code!<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>The IEEE 754 specification defines many floating point types, including: binary16, binary32, binary64 and binary128. Most developers are familiar with binary32 (equivalent to float in C#) and binary64 (equivalent to double in C#). They provide a standard format to represent a wide range of values with a precision acceptable for many applications. .NET has always [&hellip;]<\/p>\n","protected":false},"author":11778,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,196,328,756,636,688,691],"tags":[4,9,30,43,96,7167],"class_list":["post-29542","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-dotnet-core","category-aiml","category-csharp","category-fsharp","category-machine-learning","category-ml-dotnet","tag-net","tag-net-core","tag-announcement","tag-bcl","tag-ml-net","tag-numerics"],"acf":[],"blog_post_summary":"<p>The IEEE 754 specification defines many floating point types, including: binary16, binary32, binary64 and binary128. Most developers are familiar with binary32 (equivalent to float in C#) and binary64 (equivalent to double in C#). They provide a standard format to represent a wide range of values with a precision acceptable for many applications. .NET has always [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/29542","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/11778"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=29542"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/29542\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=29542"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=29542"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=29542"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}