【问题标题】:How to convert a float to a non standard encoding如何将浮点数转换为非标准编码
【发布时间】:2011-04-05 14:15:53
【问题描述】:

我正在编写一个创建 ICC 颜色格式的程序。这些格式指定了一个名为 s15Fixed16Number 的数据类型,它有一个符号位、15 个整数位和 16 个小数位。 IEEE 754 32 位浮点数有一个符号位、8 个指数位和 23 个小数位。

我需要从文本框中获取输入,并将它们转换为 s15Fixed16Number。在 Google 图书上进行了一些搜索 this,但这是关于将十进制数转换为 s15Fixed16Number。我想我可以使用链接中解释的方法,但我还没有进行任何测试来确定它的准确性。我想我也可以尝试转换从文本框中输入的字符,但我还没有考虑那么多。

我正在使用 Cocoa,但我认为这并不重要;任何 C 函数都应该可以工作。以下是 s15Fixed16Number 格式的一些示例值:

              -32768.0 = 0x80000000
                     0 = 0x00000000
                   1.0 = 0x00010000
 32767 + (65535/65536) = 0x7FFFFFFF

我想距离那个数值计算课已经有一段时间了!

【问题讨论】:

  • 如果示例转换正确,则 s15Fixed16Number 没有符号位:- 它只是一个标准的二进制补码符号编码。例如-32768.0 = 0x80000000 意味着:-1.0 = 0xffff0000
  • 这得到了 ICC 配置文件格式规范的支持,该规范声称 s15Fixed16Number:“此类型表示具有 16 个小数位的固定有符号 4 字节/32 位数量。”。它没有说它有符号位,但数字是有符号的。
  • 该标准只给出了一个(假的)负数示例,编码为 s15Fix16Number,值为 -32768.9999。这显然超出了有效范围。目前尚不清楚 -1 应该编码为什么;目前尚不清楚最小有效编码值是多少(-32768.00000 或 -32767.99999)。附录头部使用的类型为:typedef long icS15Fixed16Number;.
  • @Jonathon,我没有看到您在我刚刚从答案中的 URL 检索到的规范副本中引用的示例。从规范来看,恕我直言,最小值是 -32768.0。
  • @RBerteig:我引用的示例在 ICC 3.0 标准中,第 51 页第 7.2 节中定义了 s15Fixed16Number 此类型表示固定的有符号 4 字节/32 位数量,有 16 个小数位。编码是这样的:s15.16。 然后有一个表格,标题为“三刺激值”和“编码值”以及 4 对值:{ -32768.9999 = 0xffffffff }, { 0 = 0x00000000 }, { 1.0 = 0x00010000 }, {32767.9999 = 0x7fffffff }。这是来自International Color Consortium 网站。

标签: c++ c cocoa math types


【解决方案1】:

不要对浮点数的内部表示忘乎所以。定点值只是整数,具有恒定的比例因子。请记住,浮点数的精度比目标格式的精度更有限,因此对于大值,预期值可能在低 9 位中关闭。

//s15Fixed16Number is presumably typedef'ed to unsigned int
float foo = 1.0f;
int fooFixedSigned = (int)(foo * 65536);
s15Fixed16Number fooFixed = (s15Fixed16Number)(abs(fooFixedSigned));
if (foo < 0) fooFixed = fooFixed | (1 << 31);
//you'll also need to explicitly check for overflows and underflows and handle them however is appropriate to your situation

编辑:将 & 更正为 |

【讨论】:

  • 正如 Alan 所展示的,定点值可以通过乘以或除以单位值来转换为浮点值或从浮点值转换。这种格式通过指定一个符号位引发了一个小变化。
  • 你应该使用long而不是int——前者至少有32位,而后者只能保证有16位。
  • 对不起,不。关闭,但没有。该表示明确表示它是 2 的补码。你不能通过取绝对值来得到它。此外,您尝试使用按位 AND 运算符设置符号位将清除 除了符号位之外的每一位。
  • 原始问题指定了一个明确的符号位,而不是二进制补码。但你说得对,按位运算符不正确,那是个错误。
  • 这个问题确实有,但它引用的规范没有,并且样本值显然没有带符号的幅度。这个问题误解了规范。此外,如果您想转换为有符号幅度形式,那么您可能还需要保证乘法中的溢出不会为超出范围的正数设置符号位。
【解决方案2】:

假设您的 C 环境使用 2 的补码整数,那么这比看起来要简单得多。

typedef long s1516;  // 32bit 2's complement signed integer
s1516 floattos1516(double f) {
    return (s1516)(f * 65536. + 0.5);
}

表示是一个定点值,有 16 位小数。这与分母始终为 65536(或 216)的有理数相同。要从浮点值形成这样的有理数,只需乘以分母。那么这只是一个适当的四舍五入的问题,以及对整数类型的截断。

标准选择了他们所做的形式,因为如果您的系统使用 2 的补码整数运算,这才有效。虽然最左边的位确实代表符号,但它不是浮点表示中使用的符号位。

如果您的计算确实是float 而不是double,您会发现您的计算精度不如接近满刻度的数字的定点值中可用的精度高。如果你在double 中计算,那么你的计算总是比结果更精确。

编辑:

from the ICC 提供了明显的最新规范,即 规范 ICC.1:2004-10(配置文件版本 4.2.0.0)。第 5.1.3 节:

5.1.3 s15Fixed16Number

一个固定的有符号 4 字节/32 位数量,它有 16个小数位如表3所示。

表 3——s15Fixed16Number 数字编码 -32768,0 80000000h 0 00000000h 1,0 00010000h 32767 + (65535/65536) 7FFFFFFFh

除了对小数点表示的本地化偏好之外,这些值与我的理解完全一致,即表示只是带符号的 2 的补码整数,应该除以 65536 以获得它们的值。

到表示的自然转换是简单地乘以 65536,然后简单地从它除。选择合适的舍入规则是一个偏好问题。

满量程范围是从 -32768.0 (0x80000000) 到大约 32767.9999847412 (0x7fffffff) (含)。

我同意如果规范恰好以十六进制显示任何负值的表示会更清楚。我浏览了整个文档,发现以十进制和十六进制表示的唯一值是 CIE XYZ 色度坐标,根据定义,范围从 0 到 1,因此不能作为示例负值。

【讨论】:

  • 您的代码在插入我的测试框架时缺少错误检查来发现范围问题(作为输出中的额外列)。此外,当输入为负数时(为 -1.0 给出 0xFFFF0001),结果是严重错误的。 +0.5 的四舍五入导致与我的答案略有偏差,但因此您的答案可能比我的要好。
  • -1.0 正好是 0xFFFF0000 假设编码是我理解的。然而,四舍五入可能不是最好的答案。我应该将此特定片段标记为未经测试,但我使用类似的片段定期从浮点转换为定点。
  • @Jonathon,我认为你在考虑规范。读起来只能有简单自然的意思,引用的样本值是一致的。
  • 感谢 RBertig。我选择了这个答案,因为我意识到您可能是正确的,因为您认为规范正在谈论定点 2 的补码数。我可能会使用双精度来确保获得所需的精度,并且可能会添加一些错误检查。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 2020-06-19
  • 2011-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多