double的十进制表示中的连续零的数量

时间:2013-11-28 19:53:11

标签: floating-point ieee-754

IEEE 754双精度数的精确十进制表示中连续的非前导非尾随零(相应的nines)的最大数量是多少?

上下文

考虑将double转换为十进制,向上舍入(分别向下)的问题,当您能够使用的唯一原语是转换为最接近的现有函数时(正确舍入到任何所需的数字)数字)。

您可以获得一些额外的数字并自行删除它们。例如,要将点1.875向下舍入到一位数后,您可以将其转换为距离点(1.871.875)之后的两位或三位数的最近的十进制表示形式,然后自己删除数字以获得预期答案1.8

对于某些数字和要打印的其他位数的选择,此方法会产生错误的结果。例如,对于距离double最近的0.799999996,转换为十进制,在点后生成0.800.800并舍入到最近的,2,3或4位数, 0.8000。当期望的结果为0.8时,在转换后删除其他数字会生成结果0.7

存在有限数量的double,存在许多额外的数字,它们足以在初始转换中打印,以便在截断所获得的十进制表示后始终计算正确的结果。此数字与double的精确十进制表示中可能出现的最大九或零数相关。

相关

此问题与this question about rounding down in the conversion of a double to decimal有关,并且是this question about the correctly rounded conversion of decimal representations to doubles的双重问题。

5 个答案:

答案 0 :(得分:9)

[简短版本:答案是20.重新解决问题,找到2^e / 10^d形式的数字的良好理性近似值;然后使用连续分数为每个合适的de找到最佳近似值。]

答案似乎是20:也就是说,有一些IEEE 754 binary64浮点数的示例,其十进制扩展具有20个连续的零,但没有21个连续的零它们的十进制扩展(不包括前导和尾随零)。对于九个字符串也是如此。

对于第一部分,我需要做的就是展示这样的浮动。值0x1.9527560bfbed8p-1000可以完全表示为binary64 float,其十进制扩展包含20个零的字符串:

1.4770123739081015758322326613397693800319378788862225686396638475789157389044026850930817635789180868803699741668118826590044503912865915000931065333265410967343958956370955236330760696646247901278074331738806828003156818618589682432778455224012594723731303304343292224317331720902511661748324604219378419442700000000000000000000740694966568985212687104794747958616712153948337746429554804241586090095019654323133732729258896166004754316995632195371041441104566613036026346868128222593894931067078171989365490315525401375255259854894072456336393577718955037826961967325532389800834191597056333066925969522850884268136311674777047673845172073566950098844307658716553833345849153012040436628485227928616281881622762650607683099224232137203216552734375E-301

关于9的问题部分,0x1.c23142c9da581p-405的十进制扩展包含20个9的字符串:

2.12818792307269553358078502102171540639252016258831784842556110831434197718043638405555406495645619729155240037555858106390933161420388023706431461384056688295540725831155392678607931808851292893574214797681879999999999999999999941026584542575391157788777223962620780080784703190447744595561259568772261019375946489162743091583251953125E-122

为了解释我如何找到上面的数字,并表明没有21个连续零的例子,我们需要更加努力。对于某些整数(a + eps)*10^da以及实数deps格式为a,其实数为{9}或0非零(我们可能假设a为正)和eps非零且小。例如,如果0 < abs(eps) < 10^-10,则a + eps小数点后面至少有10个零(如果eps为正数),或者小数点后面有10个零(如果eps为{负);乘以10^d可以移动零或九的字符串的位置。

但是我们对上述形式的数字感兴趣,这些数字同时可以表示为IEEE 754 binary64 float;换句话说,对于整数b*2^eb满足e的{​​{1}}形式的2^52 <= b <= 2^53形式的数字,其中e的范围有限(并且还有一些额外的一旦我们进入低于正常范围,b的限制,但我们可以稍后担心。

结合这一点,我们正在寻找整数(a + eps) * 10^d = b * 2^eabde的解决方案,以便eps 1}}很小,a是正数且2^52 <= b <= 2^53(我们后来会担心de的范围。重新排列,我们得到eps / b = 2^e / 10^d - a / b。换句话说,我们正在寻找具有有限分母的2^e / 10^d的良好有理近似。这是连续分数的经典应用:给定de,可以有效地找到具有2^53限制的分母的最佳有理逼近。

所以解决方案策略一般是:

for each appropriate d and e:
    find the best rational approximation a / b to 2^e / 10^d with denominator <= 2^53
    if (the error in this rational approximation is small enough):
        # we've got a candidate
        examine the decimal expansion of b*2^e

我们只有大约2千个值用于检查,最差的是每个这样的e几百d,因此整个计算上非常可行。

现在详细说明:&#34;足够小&#34;意思?哪个de是&#34;适当的&#34;?

至于&#34;足够小&#34;:让我们说我们正在寻找至少19个零或九的字符串,所以我们正在寻找{{{{ 1}}。因此,对于每个0 < abs(eps) <= 10^-19d,所有ea都可以找到b。请注意,由于abs(2^e / 10^d - a / b) <= 10^-19 * 2^-52的限制,只能有一个这样的分数b;如果还有另一个a / b,那么我们就会a' / b',这是一个矛盾。因此,如果存在这样的分数,它必然是给定分母界限的最佳有理逼近。

对于1 / 2^106 <= 1 / (b *b') <= abs(a / b - a' / b') <= 2 * 10^-19 * 2^-52d:要涵盖包含次正规的binary64范围,我们希望e的范围从e-1126。如果971过大,d将远小于2^e / 10^d,并且没有解决方案的希望; 2^-53是一个实际的约束。如果d <= 16 + floor(e*log10(2))太小(或太负),那么d将是一个整数,并且没有解决方案;为避免这种情况,我们需要2^e / 10^d

考虑到所有内容,让我们编写一些代码。 Python解决方案非常简单,部分归功于Fraction.limit_deminator方法的存在,它完全可以在极限范围内找到最佳有理逼近。

d > min(e, 0)

那里有足够的性能改进空间(使用Python&#39; from fractions import Fraction from itertools import groupby from math import floor, log10 def longest_run(s, c): """Length of the longest run of a given character c in the string s.""" runs = [list(g) for v, g in groupby(s, lambda k: k == c) if v] return max(len(run) for run in runs) if runs else 0 def closest_fraction(d, e): """Closest rational to 2**e/10**d with denominator at most 2**53.""" f = Fraction(2**max(e-d, 0) * 5**max(-d, 0), 2**max(0, d-e) * 5**max(0, d)) approx = f.limit_denominator(2**53) return approx.numerator, approx.denominator seen = set() emin = -1126 emax = 971 for e in range(emin, emax+1): dmin = min(e, 0) + 1 dmax = int(floor(e*log10(2))) + 16 for d in range(dmin, dmax+1): num, den = closest_fraction(d, e) x = float.fromhex('0x{:x}p{}'.format(den, e)) # Avoid duplicates. if x in seen: continue seen.add(x) digits = '{:.1000e}'.format(x).split('e')[0].replace('.','').strip('0') zero_run = longest_run(digits, '0') if zero_run >= 20: print "{} has {} zeros in its expansion".format(x.hex(), zero_run) nine_run = longest_run(digits, '9') if nine_run >= 20: print "{} has {} nines in its expansion".format(x.hex(), nine_run) 模块将是一个良好的开端:-);就目前而言,需要几分钟才能完成。以下是结果:

fractions

答案 1 :(得分:1)

显然,它至少是15,如Smalltalk / Squeak代码所示:

1.0 successor asTrueFraction printShowingMaxDecimalPlaces: 100.
-> '1.0000000000000002220446049250313080847263336181640625'

1.0 predecessor asTrueFraction printShowingMaxDecimalPlaces: 100.
-> '0.99999999999999988897769753748434595763683319091796875'

现在,它更多地涉及到证明不能超过15个连续的零。您搜索浮点数f=(A*10^n+B)*10^p

  • n&gt; 15
  • A是整数
  • 0&lt; B&lt; 1

但浮点数也必须表示为整数有效数和偏差指数f=s*2^e其中

  • s是整数
  • 0&lt; s&lt; 2 ^ 53&lt; 5 ^ 23

我们因此s=(A*2^n*5^n+B)*2^(p-e)*5^ps < 2^53

我的第一个回答是假的,这是要完成的......

一般来说,s可以写成,s = 2 ^ a * 5 ^ b + c,c不能被2和5整除。

A * 2 ^(n + p-e)* 5 ^(n + p)+ B * 2 ^(p-e)* 5 ^ p = 2 ^ a * 5 ^ b + c

我们可以搜索A = 1的构造,B = c * 2 ^(e-p)/ 5 ^ p <1,n + p-e = a,n + p = b,e-p = n-a。

B = C * 2 ^(N-A)/ 5 ^(B-N)

我尝试了所有对a,b,这样2 ^ 52&lt; 2 ^ a * 5 ^ b&lt; 2 ^ 53,但是找不到满足B&lt; 1的任何n> 15 ...尝试用A&gt; 1只会使事情变得更糟(它涉及减少a和b)。

因此,我不认为有任何双重连续16个零,但它不是一个美丽的示范...

答案 2 :(得分:1)

我没有这方面的解决方案,但这是我可能遵循的方法:

  • 跟踪到目前为止发现的最长的零字符串。调用长度L。由于aka.nice的答案,它至少有15个长。
  • 对于每个可能的指数和每个位置10^k,其中L+1连续零可能发生,你会得到一个凌乱的小背包问题。
  • 计算两个模10^{k+L},的相关53次幂并围绕它们,使它们有大约L+1个有效数字。
  • 使用原生整数数学查找两个模10^{k+L}的前26个幂的所有组合。
  • 同样地查找两个模10^{k+L}的最后26个幂的所有组合。
  • 对两半进行排序,并使用线性扫描查找能够为您提供非常接近隐含位负数的对。你可能只会做几场比赛。
  • 使用sprintf或其他内容检查每场比赛。

好像你必须运行这个循环几百万次,但它应该可以在几十个小时内在几十台现代计算机上运行。

我还要补充说,在浮点中有一个完全可以表示的整数,它有很多尾随零:87960930222080000000000000000000000有22个尾随零,并且是10F0CF064DD5920000000000000000十六进制。 (事实上​​,10 ^ 22可以完全表示为双,它显然有22个尾随零。你不能做得更好,因为5 ^ 23需要划分这样的有效数,这是不可能的。哦,好吧。)

答案 3 :(得分:0)

此代码将在十进制结束时删除0。

 private void button1_Click(object sender, EventArgs e)
        {
            String Str = textBox1.Text;
            String lstVal = GetDecimalString(Str);
           textBox2.Text = lstVal;
        }

    private static string GetDecimalString(String Str)
    {
        String lstVal = "";
        if (Str.IndexOf(".") > -1)
        {
            String[] Last = Str.Split('.');
            if (Last.Length == 1)
            {
                lstVal = Last[0];
            }
            else
            {
                lstVal = Last[1];
            }
            String TrimedData = lstVal;
            for (int i = lstVal.Length - 1; i >= 0; i--)
            {
                if (TrimedData.EndsWith("0") && TrimedData.Length > 3)
                {
                    TrimedData = TrimedData.Substring(0, i - 1);
                }
            }
            lstVal = TrimedData;

            if (lstVal.Length < 3)
                lstVal = lstVal.PadRight(3, '0');

            lstVal = String.Join(".", Last[0], lstVal);
        }
        else
        {
            lstVal = Str;
        }
        return lstVal;
    }

答案 4 :(得分:-2)

务实的解决方案是:

  • 调用现有函数返回一个只包含所需小数位数的字符串;
  • 将结果转换回双精度值;
  • 如果此值大于原始值,则递减最后一位数。