【问题标题】:converting doubles to long doubles, understanding of 'binary vs. decimal' ambiguity? and is there a standard solution?将双打转换为长双打,了解“二进制与十进制”的歧义?有标准解决方案吗?
【发布时间】:2021-11-14 23:51:00
【问题描述】:

[编辑 2021-09-26]

对不起!,我不得不承认我在这里问废话,解释如下。我认为我不应该将此作为“答案”发布,而是作为编辑:

我仍然很好奇 0.1 的“double”值如何转换为 long double!

但问题的重点是,使用“双精度”计算的电子表格程序存储值的方式是,计算精度更高的程序会错误地读取它们。我现在 - 只是现在,我瞎了 :-( - 明白它不是!存储一个“双”二进制值,而是一个字符串!

在这个 gnumeric 中,程序犯了很少的错误之一,它使用固定的字符串长度并将'0.1' 存储为
'0.10000000000000001',从
'0.10000000000000000555xx' 向上取整。 LO Calc 和 Excel 存储 - 我认为更好 - 在往返 'bin -> dec -> bin' 中幸存下来的最短字符串,即'0.1'。这也可以作为更精确的程序的交换。

所以这个问题已经解决了,问题没有“解决”,但我可以解决它。

仍然很好奇:会,如果是的话,哪些步骤会加倍:
0 01111111011 (1).1001100110011001100110011001100110011001100110011010
转换为(80 位)长双精度:
0 011111111111011 1.10011001100110011001100110011001100110011001100110**10** **00000000000**
或者,如果,如果有哪些(其他)步骤,可以发送至:
0 011111111111011 1.10011001100110011001100110011001100110011001100110**01** **10011001101**

[/编辑]

原始问题:

请耐心等待,这个问题一定是老问题了,但我还没有找到答案……我瞎了?,

简而言之:

是否有任何 CPU、FPU 开关、命令、宏、库、技巧或优化的标准代码 sn-p 是 doe 的:'将双精度值转换为长双精度值(具有更好的精度!)并保持相应'十进制值'!而不是“精确但有偏差”的“位值”?

[编辑 2021-09-23]

我发现了一些可以完成这项工作的东西,任何人都可以提出如何“安装”它以及内部的哪些功能可以“调用”以在其他程序(debian linux 系统)中使用它?

Ulf (ulfjack) Adams 在他的“ryu”项目“https://github.com/ulfjack/ryu”中宣布了针对此类问题(打印输出?)的解决方案。他评论说:

'##柳
Ryu 生成保持往返安全的浮点数的最短十进制表示。也就是说,正确的解析器可以恢复准确的原始数字。例如,考虑二进制 32 位浮点数 00111110100110011001100110011010。存储的值正是 0.300000011920928955078125。但是,这个浮点数也是最接近十进制数0.3 的数字,所以这就是 Ryu 输出的内容。'

(恕我直言,它应该是“最接近的 IEEE 浮点数”)

他也宣布该算法“快”,但与其他算法相比可能“快”,计算“最短”与计算固定长度字符串相比“快”不一样?

[/编辑]

假设我有一个电子表格,它以双精度格式存储值,其中的值由于“二进制文件不完全可表示”而偏离其十进制对应值。
例如。 '0.1',我可能将其键入为'0.1' 或给定公式'=1/10',存储的“值”为“双”将是相同的:
0 01111111011 (1).1001100110011001100110011001100110011001100110011010 这是appr。 0.10000000000000000555112~ 十进制。

现在我已经稍微调整了我的电子表格程序,它现在可以使用“长双打”。 (我真的!这样做了,它是 gnumeric,不要尝试使用 MS Excel 或 LibreOffice Calc!)。我的系统以及大多数 Intel 硬件上的 80 位格式(1 位符号,15 位指数,64 位尾数,来自标准化的前导“1”存储在位中!(不是“隐式”和“左侧”,如'双打'))。

在新工作表中,我可以愉快地键入 '0.1' or '=1/10' 并得到(估计,无法测试):
0 011111111111011 1.100110011001100110011001100110011001100110011001100110011001101 0.100000000000000000001355253~ 十进制,很好:-)

如果我打开我的'旧'文件'公式'!将被重新解释并显示更精确的值,但“值”!'0,1'! 不是!重新诠释。相反 - 恕我直言 - 来自双精度值的位被放入长结构中,构建一个尾数,如 1.1001100110011001100110011001100110011001100110011010**00000000000**
完全保留十进制 -> 二进制(双)转换的舍入误差,再次生成十进制表示:
0.10000000000000000555112~

[编辑 2021-09-23]

没有最终深入研究...看起来在某些情况下存储和读取使用字符串,有时“更长的字符串”得到00555112~,而在其他情况下存储一个圆形字符串0,10000000000000001和' long' 版本在加载时会生成0,100000000000000010003120,甚至更糟。

[/编辑]

正如主题中所说,这是一种模棱两可的情况,可以完全保留双位给出的值,或者!将其解释为“四舍五入的占位符”并尝试将其“最初预期的十进制值”取回,但不能同时使用。我在玩'保持十进制值',可以!这样做例如通过特定的四舍五入,但这既复杂又昂贵 - 就计算工作而言。

正如我在过去几周看到的那样,IEEE、CPU 和库开发人员都是高技能人员,他们明智地预见并实施了类似问题的解决方案:

是否有任何“标准”方法、CPU、FPU 或编译器切换,或优化代码 sn-p 这样做?

将双精度值转换为长双精度值(具有更好的精度!)并保留相应的十进制值而不是偏离的“位值”?

如果“否”,有没有人深入研究过这个问题并对我有什么好的建议?

best regards,

b.

【问题讨论】:

  • A double 没有“对应的十进制值”。 double 中没有信息表明用户最初输入的是“0.1”而不是“0.1000000000000000055511151231257827021181583404541015625”。如果您想添加一些假设,例如用户从未键入超过十个有效数字,然后将由此产生的double 转换为由相同数字产生的long double,那么解决方案很简单:将double 转换为十进制有效数字(例如,在C 中,sprintf%.10g),然后转换为long doublestrtold)。
  • 但是,这种假设是错误的。用户有时会输入更长的数字。
  • 感谢@Eric,'sprintf 和 strtold' - 我说得对,这就是'字符串数学'并且非常昂贵的注册。表现? “四舍五入”会更快吗?问题是有没有更好的? '有时输入......' - 是的,当然,但是!我可以确定他们没有输入'0.10000000000000000555112'作为双精度,或者如果!他们这样做了...工作表/转换不接受它,将低于 0.1~125xxx 的所有内容计算为“0.10~0000000”,并用“最近”的 0.1~555111 代替......有了这个结论,我可以削减过头了,问题是哪个是最好的方法...
  • 必须补充一点......我记得浮点数、双精度数等的“十进制值”是(关于有多个可能无限长的字符串在做同样的事情)'最短十进制字符串在转换回二进制表示时产生相同的二进制'???从这个意义上说,二进制值具有!一个相应的十进制值(一个!,对于精确中点的极少数情况,最多两个,对于它们,IEEE 默认为二进制偶数('0' 作为最后一位),因此它只有一个!)'对应的十进制',以及 0.10 的所有内容~0055xx' 或类似的会是错误的。 ???
  • 找到最接近二进制浮点数的十进制数字,反之亦然是一个复杂的问题。它很“简单”,因为它可以用小学数学来完成,只需将数字执行到所需的小数位数即可。但是,由于double 格式的数字可能超过 10^308,这可能需要数百位数字。如此好的现代二进制到十进制和十进制到二进制例程使用发表在学术论文中的高级算法。研究人员已经找到了处理数字的方法,例如 1.23456789e308,而无需从头开始计算所有内容……

标签: floating-point type-conversion double ieee-754 long-double


【解决方案1】:

我认为我无法为您的问题提供明确的答案,但要说的内容超出了 cmets 的范围,所以就这样吧。

仍然很好奇:会,如果是的话,哪些步骤会加倍: 0 01111111011 (1).1001100110011001100110011001100110011001100110011010 转换为(80 位)long double: 0 011111111111011 1.10011001100110011001100110011001100110011001100110**10** **00000000000**

如果我正确理解了您的问题,我相信答案是:“总是”。据我所知,将一个浮点类型转换为另一种精度更高的浮点类型时,额外的精度总是用0填充。 我用这个程序测试了这个假设:

#include <stdio.h>

#define LDSIZE 10
typedef unsigned char uchar;

int main()
{
    int i;
    unsigned char xbuf[16];
    float f = 0.1;
    double d1 = f;
    double d2 = 0.1;
    long double ld1 = f;
    long double ld2 = d1;
    long double ld3 = d2;
    long double ld4 = 0.1L;

    printf("  f = %.30f\n", f);
    printf(" d1 = %.60f\n", d1);
    printf(" d2 = %.60f\n", d2);
    printf("ld1 = %.72Lf\n", ld1);
    printf("ld2 = %.72Lf\n", ld2);
    printf("ld3 = %.72Lf\n", ld3);
    printf("ld4 = %.72Lf\n", ld4);

    printf("\n");

    printf("  f = ");
    for(i = sizeof(float)-1; i >= 0; i--) printf("%02x", ((uchar *)&f)[i]);
    printf("\n");

    printf(" d1 = ");
    for(i = sizeof(double)-1; i >= 0; i--) printf("%02x", ((uchar *)&d1)[i]);
    printf("\n");

    printf(" d2 = ");
    for(i = sizeof(double)-1; i >= 0; i--) printf("%02x", ((uchar *)&d2)[i]);
    printf("\n");

    printf("ld1 = ");
    for(i = LDSIZE-1; i >= 0; i--) printf("%02x", ((uchar *)&ld1)[i]);
    printf("\n");

    printf("ld2 = ");
    for(i = LDSIZE-1; i >= 0; i--) printf("%02x", ((uchar *)&ld2)[i]);
    printf("\n");

    printf("ld3 = ");
    for(i = LDSIZE-1; i >= 0; i--) printf("%02x", ((uchar *)&ld3)[i]);
    printf("\n");

    printf("ld4 = ");
    for(i = LDSIZE-1; i >= 0; i--) printf("%02x", ((uchar *)&ld4)[i]);
    printf("\n");
}

输出是:

  f = 0.100000001490116119384765625000
 d1 = 0.100000001490116119384765625000000000000000000000000000000000
 d2 = 0.100000000000000005551115123125782702118158340454101562500000
ld1 = 0.100000001490116119384765625000000000000000000000000000000000000000000000
ld2 = 0.100000001490116119384765625000000000000000000000000000000000000000000000
ld3 = 0.100000000000000005551115123125782702118158340454101562500000000000000000
ld4 = 0.100000000000000000001355252715606880542509316001087427139282226562500000

  f = 3dcccccd
 d1 = 3fb99999a0000000
 d2 = 3fb999999999999a
ld1 = 3ffbcccccd0000000000
ld2 = 3ffbcccccd0000000000
ld3 = 3ffbccccccccccccd000
ld4 = 3ffbcccccccccccccccd

很明显,在任何情况下,向上转换后的“新”精度始终为 0。仅当变量(fd2ld4)从 @ 获得“新”初始化时987654331@ 是否接收全精度(对于long double,这必须是“0.1L”)。

或者如果,以及如果使用哪个(其他)步骤,可以: 0 011111111111011 1.10011001100110011001100110011001100110011001100110**01** **10011001101**

据我所知,答案是“从不”。根本没有任何信息可以使转换填写 0 以外的任何内容。

LO Calc 和 Excel 存储 - 我认为更好 - 在往返 'bin -> dec -> bin' 中幸存的最短字符串,即'0.1'

调查 Excel 和其他电子表格的功能听起来像是一个很有前途的研究方向。我自己一直想知道Excel。 (但什么是“LO Calc”?哦,你一定是指 Libre Office。)

如果我理解正确,“在往返 'bin -> dec -> bin' 中幸存下来的最短字符串”就是你所说的 Ulf Adams 的“Ryu”试图做的事情。听起来我也想调查一下。


关于是否用十进制而不是二进制做浮点的基本问题,你在评论中写道,你收到了一个合唱

'不,不,不,不,不!不!不!不!从来没有!,性能!!!,编译器和库的支持?!?,兼容性!不!!!不!!!不!!! ......或者也许在一个非常遥远的未来'。

我认为在这里我们可能不得不承认,无论我们喜欢与否,世界其他地方可能是对的,而我们可能是错的。多年来,世界其他地方一直在努力研究二进制浮点数,而十进制一直处于次要地位,我怀疑这是一个错误,一定有一些很好的理由。

我发现,IEEE 754-1985 上的维基百科文章的 "History" 部分读起来很有趣。我记得在 1980 年代使用 VAX 浮点格式时,我隐约意识到英特尔正在做一些不同的事情,但我从未意识到在诸如“逐渐下溢”之类的事情上存在重大的技术和哲学辩论持续了多年,我想我不知道从英特尔 8087 协处理器开始的东西最终变成了 IEEE-754。但我的观点是,这些格式受到了极大的关注,所以我真的不认为选择二进制而不是十进制是偶然的。我认为效率、易于实现以及与二进制整数运算的兼容性是主要问题,尽管可能还有其他问题。

[脚注:前一段可能暗示 VAX 浮动和 Intel/IEEE-754 之间的区别是十进制与二进制,但不,当然 VAX 浮动也是二进制。]

我的“项目”不是!关于以微米为单位测量到月球的距离,

当然,这是答案的一部分。 没有人可以以微米为单位测量到月球的距离。这是一个毫无意义的概念。现实世界中的测量总是不准确——通常是多种不准确混合在一起。在长距离的情况下,我们会遇到以下正交问题:(a)我们可能没有足够好的尺子来进行测量;(b)距离可能会随时间而变化;(c)通常甚至不明显如何定义测量的距离:是从质心到质心,还是从最近点(最高山)到最近点,还是什么?

而且由于几乎所有真实世界的测量都存在不准确性,因此有限精度浮点算术是否存在不准确性,或者在十进制和二进制之间来回转换时是否存在不一致并不重要。

(我最喜欢的不可测量距离的例子是从纽约到洛杉矶的距离。你甚至不能用英里来测量它,更不用说英寸或微米了。除了上面的问题,还有(d)你在尝试测量穿过地球的直线,或跟随地球曲率的大圆周距离,或者如果你走直线,你会走的距离,计算你必须爬山和下山的每座山和山谷,或者最短的旅行距离实际道路,还是什么?)

但是给我们所有人 0.1 + 0.2 -> 0.3,对于所有小数,可靠的数学

不过,归根结底,我认为只有两类程序员关心获得 0.1 + 0.2 == 0.3:

  • 使用美元和美分(或任何其他十进制货币)编写会计软件的人
  • 第一次学习浮点的初级程序员

编写会计软件的人都学会了用便士工作(或者换句话说,他们自己定点点算术,而不是浮点数)。

刚开始的程序员只是被教导“浮点总是不精确的”(尽管这可以说是一种误导性的过度简化)。

其他人不关心完美精度(当然不是完美十进制精度),当然每个人都关心效率,硬件制造商似乎都在关注二进制以追求那个,所以小数会被冷落。

清楚地了解精度问题确实很重要:尽管浮点运算中的精度损失是一个非常重要的问题,但人们可以将整个职业生涯都投入到(设计很酷且不明显的变通方法,例如“Kahan summation algorithm”),对于大多数现实世界的程序,我认为二进制 ↔ 十进制转换引入的差异最终不会成为问题。

虽然,我想,我在上面做了自己的过度简化:可能有第三类程序员可能关心 0.1 + 0.2 ≟ 0.3,那就是:

  • 编写通用电子表格或计算器程序的程序员

听起来可能包括你……

【讨论】:

  • 嗨@Steve,很高兴收到你的来信,我喜欢你的风格和敏锐的分析,你是“好人”之一,>“现实世界中的每一个测量”——是的,我们是习惯了,但不习惯数学上的不准确!在互联网上阅读,我看到许多不熟悉 FP 不准确的“烦躁用户”的问题,并且每天都有新的人出现在这个问题上。 M.E. 并不“有效”,不能让太多人遇到问题,然后让他们走上痛苦的“学习曲线”。恕我直言 0.3000000000004 本身不是问题,但 0.1 + 0.2 != 0.3 是,...
  • 并且(恕我直言)很容易摆脱它,需要从游戏中删除 0.3000000000004 ... > “编写通用电子表格的程序员” - 恕我直言,这些都投入了很多!努力使这些事情正确,不幸的是主要是有问题的概念,并且在他们当前的重点之外使事情变得更糟。但是从“新手一厢情愿”的 POV 中与他们交谈非常非常困难,他们是 a.:“年轻、天真、傲慢”或 b.:“老而沮丧”或 c.:正在从一种。到 b。这在某种程度上是“系统稳定性”或“传统”。
  • 你好@Steve Summit,>“据我所知,答案是“从不”。 - 你介意试试:long double ld5 = d2 * pow( 10, 16 );ld5 = ( ld5 + 0.5 );ld5 = (long int)( ld5 );ld5 = ld5 / pow( 10, 16 );
  • 你好@Steve Summit,不是“反对”,而是扩大范围:据说诺贝尔奖的引力波实验使用“LIGO 探测器”测量 4 公里的距离,精度为 10^-18 米,这大约是 1:2,5E-22 并且超过了 double 甚至 80 位长数字的精度(它将适合四边形并且不超过跨度 1 - 最大浮点数!)。到目前为止,它超过了精确度 1/1000 头发与地月的对比。我怀疑他们用 Excel、Calc 或 gnumeric 计算 ;-) ...但请记住:奖项是荣誉和!大约 1.000.000 美元,因此肯定有!一些精度价值。
  • 还有一个,那就适合今天。 @Steve 说:“世界其他地方可能是对的,我们可能是错的”——我并不是说浮动的决定是错误的,我说的是:1. 现代电子表格计算的内容与 IEEE 的能力相差甚远: -( 2. 对 IEEE 功能的实际理解远非真正可能的 :-) 3. 采用更智能的设计,IEEE 会减少令人头疼的问题,并且它们的潜力会更快地发挥出来。
猜你喜欢
  • 2019-08-17
  • 1970-01-01
  • 1970-01-01
  • 2013-09-27
  • 2011-08-25
  • 2017-05-23
  • 2013-09-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多