Print Precision of Dyadic Fractions Varies by Language

http://www.exploringbinary.com/print-precision-of-dyadic-fractions-varies-by-language/


Interestingly, programming languages vary in how much precision they allow in printed floating-point fractions. You would think they’d all be the same, allowing you to print as many decimal places as you ask for. After all, a floating-point fraction is a dyadic fraction; it has as many decimal places as it has bits in its fractional representation.

Consider the dyadic fraction 5,404,319,552,844,595/253. Its decimal expansion is 0.59999999999999997779553950749686919152736663818359375, and its binary expansion is 0.10011001100110011001100110011001100110011001100110011. Both are 53 digits long. The ideal programming language lets you print all 53 decimal places, because all are meaningful. Unfortunately, many languages won’t let you do that; they typically cap the number of decimal places at between 15 and 17, which for our example might be 0.59999999999999998.

I can guess why this is so. We view printed values as decimal numbers, not binary approximations. 0.59999999999999997779553950749686919152736663818359375 looks like 0.6, and in fact is what’s stored in a double-precision floating-point variable that is assigned the value 0.6 (it’s the closest double-precision approximation of 0.6). If you print this variable, you can live with it being 0.59, or a finite version thereof. But all those digits after the last 9 look like garbage, and in fact they are with respect to 0.6.

Now change your perspective. Assume you care about binary approximations. You might want to see which dyadic fraction a decimal fraction rounds to. You might want to print a variable that has been assigned a dyadic fraction explicitly. You might want to know the value of a small negative power of two. In these cases, you’ll want to see the entire decimal expansion, “garbage” and all.

Let’s see how good various programming languages are at giving you this precision.

Summary of Results

I tested the printing precision of eight different languages, some on both Windows and Linux, for a total of twelve environments. I determined the maximum precision each language allows when printing a double-precision floating-point value (AKA double). I picked two cases for each, the smallest and largest (positive) dyadic fractions that could be printed in their entirety.

The smallest fraction is a negative power of two. The smallest negative power of two that fits in a double is 2-1074. In decimal, it is 323 0s followed by 751 other digits. In binary, it is 1073 0s followed by a 1 (of course all those digits don’t fit in a double — they are implied by the exponent).

The largest fraction is of the form (2n – 1)/2n, or 1 – 2-n. For a double, the largest value possible is 1 – 2-53. In decimal, it is 15 9s followed by 38 other digits. In binary, it is 53 1s.

Here’s a summary of what I found:

Longest Dyadic Fractions Printed in Full Precision
Language OS Smallest Fraction Largest Fraction
GCC (C) Linux 2-1074 1 – 2-53
Visual C++ Windows 2-24 1 – 2-17
Java Windows 2-24 1 – 2-16
Visual Basic Windows 2-21 1 – 2-15
PHP Windows 2-40 1 – 2-40
JavaScript (Firefox) Windows 2-100 1 – 2-53
JavaScript (IE) Windows 2-20 1 – 2-16
VBScript (IE) Windows 2-21 1 – 2-15
ActivePerl Windows 2-24 1 – 2-17
Python Windows 2-24 2-1074 1 – 2-17 1 – 2-53
Perl Linux 2-1074 1 – 2-53
Python Linux 2-117 1 – 2-53

The GNU Compiler Collection (GCC), and specifically its C compiler, is what I’ll call the gold standard. It prints all dyadic fractions to full precision. Perl on Linux, which presumably is built using GCC and glibc, does the same.

JavaScript in Firefox is curious. It prints the largest double fraction, 1 – 2-53, but only prints powers of two up to 2-100. It seems like an arbitrary maximum, perhaps due to a buffer size limit. PHP is similar. It picks an arbitrary maximum of 40 decimal places, regardless of the fraction. Python on Linux also has an interesting limit — it prints powers of two up to 2-117.

What follows are the details of how I determined the values in the table. For each value, I show two pieces of code: one that prints the longest fraction, and one that fails to print the next longer fraction. In other words, I determine the maximum by going beyond it.

1. GCC C (Linux)

Smallest Fraction

This code prints all 1,074 decimal places of 2-1074:

#include "stdio.h"
int main()
{
/* Print 2^-1074 */
double dyadic = /* Compute as (2^-64)^16 * 2^-50 to break up */
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.00000000000000088817841970012523233890533447265625;
printf ("%1.1074f",dyadic);
}

This code does not print 2-1075. This is expected. 2-1075 underflows to 0 in a double, so 1075 decimal places of 0 are printed instead:

#include "stdio.h"
int main()
{
/* Print 2^-1075 */
double dyadic = /* Compute as (2^-64)^16 * 2^-51 to break up */
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.000000000000000444089209850062616169452667236328125;
printf ("%1.1075f",dyadic);
}

Largest Fraction

This code prints all 53 decimal places of 1 – 2-53:

#include "stdio.h"
int main()
{
/* Print 1 - 2^-53 */
double dyadic =
   0.99999999999999988897769753748434595763683319091796875;
printf ("%1.53f",dyadic);
}

This code does not print 1 – 2-54; it prints 1 followed by 54 decimal places of 0s (the compiler, during decimal to binary conversion, rounds this “halfway” case up to 1.0):

#include "stdio.h"
int main()
{
/* Print 1 - 2^-54 */
double dyadic =
   0.999999999999999944488848768742172978818416595458984375;
printf ("%1.54f",dyadic);
}

2. Visual C++ (Windows)

Smallest Fraction

This code prints all 24 decimal places of 2-24:

#include "stdio.h"
int main()
{
/* Print 2^-24 */
double dyadic = 0.000000059604644775390625;
printf ("%1.24f",dyadic);
}

This code does not print 2-25, but instead 0.0000000298023223876953130:

#include "stdio.h"
int main()
{
/* Print 2^-25 */
double dyadic = 0.0000000298023223876953125;
printf ("%1.25f",dyadic);
}

Largest Fraction

This code prints all 17 decimal places of 1 – 2-17:

#include "stdio.h"
int main()
{
/* Print 1 - 2^-17 */
double dyadic = 0.99999237060546875;
printf ("%1.17f",dyadic);
}

This code does not print 1 – 2-18, but instead 0.999996185302734380:

#include "stdio.h"
int main()
{
/* Print 1 - 2^-18 */
double dyadic = 0.999996185302734375;
printf ("%1.18f",dyadic);
}

3. Java (Windows)

Smallest Fraction

This code prints all 24 decimal places of 2-24:

class precision {
   /* Print 2^-24 */
   public static void main(String[] args) {
      double dyadic = 0.000000059604644775390625;
      System.out.printf("%1.24f\n",dyadic);
    }
}

This code does not print 2-25, but instead 0.0000000298023223876953120:

class precision {
   /* Print 2^-25 */
   public static void main(String[] args) {
      double dyadic = 0.0000000298023223876953125;
      System.out.printf("%1.25f\n",dyadic);
    }
}

Largest Fraction

This code prints all 16 decimal places of 1 – 2-16:

class precision {
   /* Print 1 - 2^-16 */
   public static void main(String[] args) {
      double dyadic = 0.9999847412109375;
      System.out.printf("%1.16f\n",dyadic);
    }
}

This code does not print 1 – 2-17, but instead 0.99999237060546880:

class precision {
   /* Print 1 - 2^-17 */
   public static void main(String[] args) {
      double dyadic = 0.99999237060546875;
      System.out.printf("%1.17f\n",dyadic);
    }
}

4. Visual Basic (Windows)

Smallest Fraction

This code prints all 21 decimal places of 2-21:

Module Precision
    'Print 2^-21
    Sub Main()
        Dim dyadic As Double
        dyadic = 0.000000476837158203125
        Console.WriteLine("{0:F21}", dyadic)
    End Sub
End Module

This code does not print 2-22, but instead 0.0000002384185791015630:

Module Precision
    'Print 2^-22
    Sub Main()
        Dim dyadic As Double
        dyadic = 0.0000002384185791015625
        Console.WriteLine("{0:F22}", dyadic)
    End Sub
End Module

Largest Fraction

This code prints all 15 decimal places of 1 – 2-15:

Module Precision
    'Print 1 - 2^-15
    Sub Main()
        Dim dyadic As Double
        dyadic = 0.999969482421875
        Console.WriteLine("{0:F15}", dyadic)
    End Sub
End Module

This code does not print 1 – 2-16, but instead 0.9999847412109380:

Module Precision
    'Print 1 - 2^-16
    Sub Main()
        Dim dyadic As Double
        dyadic = 0.9999847412109375
        Console.WriteLine("{0:F16}", dyadic)
    End Sub
End Module

5. PHP (Windows)

Smallest Fraction

This code prints all 40 decimal places of 2-40:

<?php
 /* Print 2^-40 */
 $dyadic = 0.0000000000009094947017729282379150390625;
 printf ("%1.40f",$dyadic);
?>

This code does not print 2-41; 0.0000000000004547473508864641189575195312 is printed instead:

<?php
 /* Print 2^-41 */
 $dyadic = 0.00000000000045474735088646411895751953125;
 printf ("%1.41f",$dyadic);
?>

Largest Fraction

This code prints all 40 decimal places of 1 – 2-40:

<?php
 /* Print 1 - 2^-40 */
 $dyadic = 0.9999999999990905052982270717620849609375;
 printf ("%1.40f",$dyadic);
?>

This code does not print 1 – 2-41; instead, it prints 0.9999999999995452526491135358810424804688:

<?php
 /* Print 1 - 2^-41 */
 $dyadic = 0.99999999999954525264911353588104248046875;
 printf ("%1.41f",$dyadic);
?>

6. JavaScript in Firefox (Windows)

Smallest Fraction

This code prints all 100 decimal places of 2-100:

<script type="text/javascript">
  /* Print 2^-100 */
  var dyadic = /* Compute as 2^-50 * 2^-50 to break into 2 lines */
    0.00000000000000088817841970012523233890533447265625 *
    0.00000000000000088817841970012523233890533447265625;
  document.write (dyadic.toFixed(100));
</script>

This code does not print 2-101. Instead, it prints nothing. The Firefox error console gives the message “precision 101 out of range,” complaining about toFixed(101):

<script type="text/javascript">
  /* Print 2^-101 */
  var dyadic = /* Compute as 2^-50 * 2^-51 to break into 2 lines */
    0.00000000000000088817841970012523233890533447265625 *
    0.000000000000000444089209850062616169452667236328125;
  document.write (dyadic.toFixed(101));
</script>

This code is the same as above but with “toFixed(100).” It therefore can’t print 2-101. Instead it rounds the last two digits, ‘25′, to ‘3′ (I didn’t want to paste a 100 digit decimal here):

<script type="text/javascript">
  /* Print 2^-101 */
  var dyadic = /* Compute as 2^-50 * 2^-51 to break into 2 lines */
    0.00000000000000088817841970012523233890533447265625 *
    0.000000000000000444089209850062616169452667236328125;
  document.write (dyadic.toFixed(100));
</script>

Largest Fraction

This code prints all 53 decimal places of 1 – 2-53:

<script type="text/javascript">
   /* Print 1 - 2^-53 */
  var dyadic =
     0.99999999999999988897769753748434595763683319091796875;
  document.write (dyadic.toFixed(53));
</script>

This code does not print 1 – 2-54, but instead 1 followed by 54 decimal places of 0s:

<script type="text/javascript">
   /* Print 1 - 2^-54 */
  var dyadic =
     0.999999999999999944488848768742172978818416595458984375;
  document.write (dyadic.toFixed(54));
</script>

7. JavaScript in Internet Explorer (Windows)

Smallest Fraction

This code prints all 20 decimal places of 2-20:

<script type="text/javascript">
  /* Print 2^-20*/
  var dyadic = 0.00000095367431640625;
  document.write (dyadic.toFixed(20));
</script>

This code does not print 2-21. Instead, it prints nothing. The Internet Explorer error console gives the message “Error: The number of fractional digits is out of range,” complaining about toFixed(21):

<script type="text/javascript">
  /* Print 2^-21*/
  var dyadic = 0.000000476837158203125;
  document.write (dyadic.toFixed(21));
</script>

This code is the same as above but with “toFixed(20).” It therefore can’t print 2-21. Instead it prints 0.00000047683715820313:

<script type="text/javascript">
  /* Print 2^-21*/
  var dyadic = 0.000000476837158203125;
  document.write (dyadic.toFixed(20));
</script>

Largest Fraction

This code prints all 16 decimal places of 1 – 2-16:

<script type="text/javascript">
   /* Print 1 - 2^-16*/
  var dyadic =
     0.9999847412109375;
  document.write (dyadic.toFixed(16));
</script>

This code does not print 1 – 2-17, but instead 0.99999237060546870:

<script type="text/javascript">
   /* Print 1 - 2^-17 */
  var dyadic =
     0.99999237060546875;
  document.write (dyadic.toFixed(17));
</script>

8. VBScript in Internet Explorer (Windows)

Smallest Fraction

This code prints all 21 decimal places of 2-21:

<script type="text/vbscript">
 'Print 2^-21
 dyadic = 0.000000476837158203125
 document.write(FormatNumber(dyadic,21))
</script>

This code does not print 2-22, but instead 0.0000002384185791015630:

<script type="text/vbscript">
 'Print 2^-22
 dyadic = 0.0000002384185791015625
 document.write(FormatNumber(dyadic,22))
</script>

Largest Fraction

This code prints all 15 decimal places of 1 – 2-15:

<script type="text/vbscript">
 'Print 1 - 2^-15
 dyadic = 0.999969482421875
 document.write(FormatNumber(dyadic,15))
</script>

This code does not print 1 – 2-16, but instead 0.9999847412109380:

<script type="text/vbscript">
 'Print 1 - 2^-16
 dyadic = 0.9999847412109375
 document.write(FormatNumber(dyadic,16))
</script>

9. ActivePerl (Windows)

Smallest Fraction

This code prints all 24 decimal places of 2-24:

# Print 2^-24
$dyadic = 0.000000059604644775390625;
printf "%1.24f",$dyadic;

This code does not print 2-25, but instead 0.0000000298023223876953130:

# Print 2^-25
$dyadic = 0.0000000298023223876953125;
printf "%1.25f",$dyadic;

Largest Fraction

This code prints all 17 decimal places of 1 – 2-17:

# Print 1 - 2^-17
$dyadic = 0.99999237060546875;
printf "%1.17f",$dyadic;

This code does not print 1 – 2-18, but instead 0.999996185302734380:

# Print 1 - 2^-18
$dyadic = 0.999996185302734375;
printf "%1.18f",$dyadic;

10. Python (Windows)

(Updated to reflect Python 3.1.)

Smallest Fraction


This code prints all 24 decimal places of 2-24:

# Print 2^-24
dyadic = 0.000000059604644775390625;
print(format(dyadic,"1.24f"));

This code does not print 2-25, but instead 0.0000000298023223876953130:

# Print 2^-25
dyadic = 0.0000000298023223876953125;
print(format(dyadic,"1.25f"));

This code prints all 1,074 decimal places of 2-1074:

#Print 2^-1074 (Compute as (2^-50)^21 * 2^-24 to break up)
dyadic = 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.00000000000000088817841970012523233890533447265625;
dyadic *= 0.000000059604644775390625;
print(format(dyadic,"1.1074f"));

Largest Fraction


This code prints all 17 decimal places of 1 – 2-17:

# Print 1 - 2^-17
dyadic = 0.99999237060546875;
print(format(dyadic,"1.17f"));

This code does not print 1 – 2-18, but instead 0.999996185302734380:

# Print 1 - 2^-18
dyadic = 0.999996185302734375;
print(format(dyadic,"1.18f"));

This code prints all 53 decimal places of 1 – 2-53:

# Print 1 - 2^-53
dyadic = 0.99999999999999988897769753748434595763683319091796875;
print(format(dyadic,"1.53f"));

11. Perl (Linux)

Smallest Fraction

This code prints all 1,074 decimal places of 2-1074:

# Print 2^-1074
$dyadic = #Compute as (2^-64)^16 * 2^-50 to break up
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.00000000000000088817841970012523233890533447265625;
printf "%1.1074f",$dyadic;

This code does not print 2-1075. This is expected. 2-1075 underflows to 0 in a double, so 1075 decimal places of 0 are printed instead:

# Print 2^-1075
$dyadic = #Compute as (2^-64)^16 * 2^-51 to break up
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.0000000000000000000542101086242752217003726400434970855712890625*
0.000000000000000444089209850062616169452667236328125;
printf "%1.1075f",$dyadic;

Largest Fraction

This code prints all 53 decimal places of 1 – 2-53:

# Print 1 - 2^-53
$dyadic = 0.99999999999999988897769753748434595763683319091796875;
printf "%1.53f",$dyadic;

This code does not print 1 – 2-54, but instead 1 followed by 54 decimal places of 0s:

# Print 1 - 2^-54
$dyadic = 0.999999999999999944488848768742172978818416595458984375;
printf "%1.54f",$dyadic;

12. Python (Linux)

Smallest Fraction

This code prints all 117 digits of 2-117:

# Print 2^-117
# Compute as (2^-32)^3 * 2^-21 to break up
dyadic = 0.00000000023283064365386962890625;
dyadic *= 0.00000000023283064365386962890625;
dyadic *= 0.00000000023283064365386962890625;
dyadic *= 0.000000476837158203125;
print(format(dyadic,"1.117f"));

This code does not print 2-118. Instead, it prints the first 117 digits of 2-118 (it cuts off the last digit):

# Print 2^-118
# Compute as (2^-32)^3 * 2^-22 to break up
dyadic = 0.00000000023283064365386962890625;
dyadic *= 0.00000000023283064365386962890625;
dyadic *= 0.00000000023283064365386962890625;
dyadic *= 0.0000002384185791015625;
print(format(dyadic,"1.118f"));

Largest Fraction

This code prints all 53 decimal places of 1 – 2-53:

# Print 1 - 2^-53
dyadic = 0.99999999999999988897769753748434595763683319091796875;
print(format(dyadic,"1.53f"));

This code prints 1 – 2-53 with a 0 appended (the interpreter, during decimal to binary conversion, appears to be rounding this “halfway” case down to 1 – 2-53):

# Print 1 - 2^-54
dyadic = 0.999999999999999944488848768742172978818416595458984375;
print(format(dyadic,"1.54f"));

Should They All Be Like GCC?

Should all languages allow printing to maximum precision like GCC? I don’t see why not. Granted, there’s probably not much floating-point being done in scripting languages, but why not support it if it’s easy to implement? One unknown is compatibility. Are there programs that depend on the current imprecise output?

I tested the waters by submitting bug reports for Visual C++ and PHP. Microsoft took over ten months to consider my request but they acknowledged the problem and said they would change it in a future release (it seems to apply to all of Visual Studio by the way). On the other hand, PHP took only three hours to reject my request without a good explanation.

I don’t plan on submitting more bug reports at the moment, but if you decide to, let us know so we can track it.

Other Languages and Environments

There are lots of things I didn’t try, like Fortran, Java on Linux, JavaScript in Opera, Safari, and Chrome, etc. If you are so inclined, please experiment and let us know what you find.

Dingbat

7 Responses to “Print Precision of Dyadic Fractions Varies by Language”

  1. Rasmus Says:

    This is actually configurable in PHP. In your php.ini file, try this:

    precision=1024

    and run your PHP tests again.

  2. Rick Regan Says:

    Hi Rasmus,

    Thanks for taking the time to comment.

    I tried changing php.ini as you suggested and I saw no difference.

    In the PHP source, in \ext\standard\formatted_print.c, there is this line of code: #define MAX_FLOAT_PRECISION 40. I’ve been assuming this was the culprit.

    P.S. Thanks for reopening the PHP bug!

  3. Christophe Poucet Says:

    Don’t forget dc and bc :) .

  4. Kevin Gadd Says:

    If memory serves, PHP also has a hard-coded limit of 40 digits after the decimal place when serializing numbers using serialize().

    This actually means that not only is precision lost when printing out doubles like the one you use in your example, but the precision may also be lost when serializing/unserializing them, which is painful.

    This may have changed by now, though; I last researched it near the end of PHP4’s lifetime. It was a surprise to me, to say the least (we were having weird issues with floating point numbers in some of our automated tests that turned out to be due to serialize()/unserialize().)

  5. Rick Regan Says:

    Kevin,

    I tried out serialize and unserialize — you’re right, there are similar limitations.

    2-143 is the smallest negative power of two that PHP can serialize. However, it can only unserialize up to 2-40!

    I will amend the bug report.

    Here are the testcases:

    This code works — it prints:
    d:8.9683101716788292539118693330554632401936764280097009392452370
    16894662929189507849514484405517578125E-44;

    <?php
     //Serialize 2^-143
     $dyadic = //Compute as (2^-50)^2 * 2^-43 to break up
     0.00000000000000088817841970012523233890533447265625*
     0.00000000000000088817841970012523233890533447265625*
     0.0000000000001136868377216160297393798828125;
     echo serialize ($dyadic);
    ?>
    

    This code fails — it prints:
    d:4.4841550858394146269559346665277316200968382140048504696226185
    08447331464594753924757242202758789062E-44;

    <?php
     //Serialize 2^-144
     $dyadic = //Compute as (2^-50)^2 * 2^-44 to break up
     0.00000000000000088817841970012523233890533447265625*
     0.00000000000000088817841970012523233890533447265625*
     0.00000000000005684341886080801486968994140625;
     echo serialize ($dyadic);
    ?>
    

    This code works — it prints:
    0.0000000000009094947017729282379150390625

    <?php
     //Serialize and unserialize 2^-40
     $dyadic = 0.0000000000009094947017729282379150390625;
     printf ("%1.40f",unserialize(serialize($dyadic)));
    ?>
    

    This code fails — it prints:
    0.0000000000004547473508864641189575195312

    <?php
     //Serialize and unserialize 2^-41
     $dyadic = 0.00000000000045474735088646411895751953125;
     printf ("%1.41f",unserialize(serialize($dyadic)));
    ?>
    

    (I did not try the “largest fraction” testcases but I’d imagine they’d have similar limitations.)

  6. Rick Regan Says:

    Christophe,

    Since you mentioned it, I tried out bc and dc. Of course, they’re arbitrary-precision calculators, so they should handle any value.

    bc
    ==

    bc
    scale=2000
    dyadic=2^-2000
    dyadic

    (prints out 2^-2000 accurately, a 2000 digit number, bigger than can fit in a double.)

    dc
    ==

    dc
    (I pasted the 2000 digit 2^-1000 value here)
    p
    (prints out 2^-2000 accurately)

  7. Rick Regan Says:

    I updated the table (Windows only for now) to reflect Python 3.1 (thanks to Mark Dickinson) — see http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too/#comment-4273.

Leave a Comment