【问题标题】:Displaying IEEE-754 quadruple-precision (binary128) floating point values in scientific notation in C#在 C# 中以科学计数法显示 IEEE-754 四精度(binary128)浮点值
【发布时间】:2014-05-20 01:04:51
【问题描述】:

我正在尝试将线程上下文中的原始二进制数据转换为人类可读的格式,但在尝试将 quadruple-precision floating point 值转换为 C# 中的可读格式时出现空。

最终,我想以标准科学计数法显示它,例如1.234567×1089。我不担心过程中的精度损失 - 我只是想合理地了解价值是什么。

我的第一个想法是通过提高指数手动将值计算为双精度值,但当然在许多情况下我会超过最大值。我不介意失去精度,但根本无法显示它是不可接受的。

我可以使用某种简单的数学技巧吗?

【问题讨论】:

  • 您是否允许四精度计算以转换为十进制,或者您是否必须使用整数和双精度数来做所有事情?是否有四精度函数pow 可用?
  • 多少精度损失是可以接受的?如果您愿意接受大约 10**-12 的相对误差,那么您可以使用其指数和尾数的前 53 位计算该值的对数(例如以 10 为底),然后检索小数分别来自该日志的积分部分和小数部分的指数和有效数。
  • @MarkDickinson 我理解了单个单词,但我失去了整体含义。愿意发布一个更“ELI5”的答案吗?
  • @MarkDickinson 在精度方面,我不太介意。我只是为了在调试某些东西时做出合理的猜测而显示它。原始的十六进制也会显示出来——我只想要一个“人类可读”的形式来让事情更清楚。
  • @Polynomial:我已经尝试过回答;我希望你不介意 Python - 我知道一点 C#,但我需要更长的时间才能用 C# 给出答案,而且我应该很快就会赶飞机!

标签: c# math floating-point ieee-754


【解决方案1】:

您可以安装处理该问题的第三方库。例如,看起来QPFloat 为您提供了一个名为System.Quadruple 的新struct,它覆盖了ToString,所以您可以尝试一下。

(我想知道 .NET 什么时候会支持 System.Quadruple 之类的东西。)

【讨论】:

  • 虽然我更愿意自己处理它,但 QPFloat 完成了这项工作。我仍然必须手动将代码组合在一起以将原始数据转换为类型,但至少我有一个可以安全地保持这种精度级别的类型。
【解决方案2】:

因此,这里是扩展我之前发表的评论的答案。我希望你不介意我使用的是 Python,因为我知道在哪里可以找到该语言所需的一切;也许其他人可以将其翻译成 C# 中的合适答案。

假设您有一个 128 位序列,表示 IEEE 754 binary128 格式的数字,并且我们目前以无符号整数 x 的形式读取了这 128 位。例如:

>>> x = 0x4126f07c18386f74e697bd57a865a9d0

(我想这在 C# 中会有点混乱,因为据我所知它没有 128 位整数类型;您需要使用两个 64 位整数作为高位和低级词,或使用BigInteger 类型。)

我们可以像往常一样通过位运算提取指数和有效位(我假设您已经做到了这一点,但为了完整性我想包括计算):

>>> significand_mask = (1 << 112) - 1
>>> exponent_mask = (1 << 127) - (1 << 112)
>>> trailing_significand = x & significand_mask
>>> significand = 1.0 + float(trailing_significand) / (2.0**112) 
>>> biased_exponent = (x & exponent_mask) >> 112
>>> exponent = biased_exponent - 16383

请注意,虽然指数是精确的,但此时我们已经失去了significand 的大部分精度,只保留了 52-53 位的精度。

>>> significand
1.9393935334951098
>>> exponent
295

所以表示的值大约是1.9393935334951098 * 2**295,或大约1.234567e+89。但是你不能在这个阶段直接进行计算,因为它可能会溢出Double(在这种情况下它不会,但如果指数更大你会遇到问题)。所以这就是日志的来源:让我们计算x所代表的值的自然对数:

>>> from math import log, exp
>>> log_of_value = log(significand) + exponent*log(2)
>>> log_of_value
205.14079357778544

然后我们可以除以log(10)得到小数部分的指数和尾数:除法的商给出十进制指数,而余数给出有效数的log,所以我们必须对其应用exp 以检索实际有效位:

>>> exp10, mantissa10 = divmod(log_of_value, log(10))
>>> exp10
89.0
>>> significand10 = exp(mantissa10)
>>> significand10
1.234566999999967

并很好地格式化答案:

>>> print("{:.10f}e{:+d}".format(significand10, int(exp10)))
1.2345670000e+89

这是基本思想:通常,要做到这一点,您还需要处理符号位和零、次正规数、无穷大和 NaN 的特殊位模式。根据应用程序,您可能不需要所有这些。

在将整数有效数转换为双精度浮点数时,首先会涉及一些精度损失,而且在取对数和指数时也会有一些精度损失。精度损失的最坏情况发生在指数较大时,因为较大的指数会放大log(2) 计算中涉及的绝对误差,这反过来又会在采用exp 获得最终有效数时产生更大的相对误差。但由于(无偏的)指数不超过 16384,因此限制误差并不难。我还没有进行正式的计算,但这对于binary128 格式范围内的大约 12 位精度应该是有好处的,对于小指数的数字来说精度应该会好一些。

【讨论】:

    【解决方案3】:

    这方面的技巧很少......

    1. 计算数字的十六进制字符串

      尾数和指数是二进制的,所以应该没有问题,只是不要忘记为每个2^4 指数部分添加零并将尾数移动exponent&amp;3 位。负指数需要很少的调整,但非常相似。

      所有这些都可以通过位和移位操作来完成,因此如果编码正确,不会造成精度损失...

    2. 将十六进制字符串转换为十进制字符串

      这里也有很多例子here是我的。您还可以稍微调整一下以跳过零处理以提高速度...

    3. 现在扫描 dec 字符串

      如果您在上面的链接中查看我的 dec2hexhex2dec 转换,那么您需要找到的扫描已经存在:

      • 左右第一个非零小数的位置
      • 小数点位置

      从这些您可以轻松计算指数

    4. 将 dec 字符串转换为尾数 * 10^exponet 形式

      这很简单,只需删除零...并将小数点转换为新位置,然后添加指数部分...

    5. 为尾数添加符号

      你可以直接在项目符号中添加它#1,#2,但如果你最后这样做,那么它会为你节省一些ifs ...

    希望这会有所帮助...

    【讨论】:

      猜你喜欢
      • 2013-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-06
      • 1970-01-01
      • 1970-01-01
      • 2012-01-06
      • 1970-01-01
      相关资源
      最近更新 更多