An old Visual C printf bug affects Perl

The other day, I wrote some code that printed some floating point numbers. Here is the shortest program that illustrates what I did:

#include <float.h>
#include <math.h>
#include <stdio.h>

int main(void) {
    double x = nextafter(100.0, DBL_MAX);
    printf("%.18f\n", x);
    return 0;
}

I compiled this with Visual Studio 2015 tools, ran, and got the output:

100.000000000000014211

I decided to do some checking with Perl, and wrote the following:

#!/usr/bin/env perl

use 5.024; # why not?!
use warnings;

use Data::Float qw( nextafter max_number );

printf "%.18f\n", nextafter(100.0, max_number);

To my surprise, the output was:

100.000000000000010000

Ooooppppssss!!!

The problem might be caused by a bug in Perl or it might be caused by a difference between VC 2013 and VC 2015.

Wait, where did VC 2013 come in?

You see, you can’t build perl with Visual Studio 2015 tools on Windows out of the box yet due to this change:

… the FILE type was completely defined in <stdio.h>, so it was possible for user code to reach into a FILE and muck with its internals. We have refactored the stdio library to improve encapsulation of the library implementation details. As part of this, FILE as defined in <stdio.h> is now an opaque type and its members are inaccessible from outside of the CRT itself.

Part of the responsibility for the shortcoming is mine: A few days after I reported the initial problem, Tony Cook asked me to test his fix, and I simply failed to follow through. I should get back on that, but, for now let’s focus on this particular bug.

Visual Studio 2013 has more than a few warts. For example, it doesn’t support inline in C source, you must use __inline. In fact, this list of improvements should give a better idea of VS 2013’s shortcomings. The wart that created this situation is listed in the same document:

The old formatting algorithms would generate only a limited number of digits, then would fill the remaining decimal places with zero. This is usually good enough to generate strings that will round-trip back to the original floating point value, but it’s not great if you want the exact value (or the closest decimal representation thereof). The new formatting algorithms generate as many digits as are required to represent the value (or to fill the specified precision).

Both Python and Ruby build with MSVS 2015 tools on Windows, so they do not exhibit this problem:

x = 100.0.next_float;
printf "%.18f\n", x;

prints

100.000000000000014211

and

# see http://stackoverflow.com/a/6163157/100754
# No, I don't have numpy on this machine
import ctypes
import sys

_libm = ctypes.cdll.LoadLibrary('msvcrt.dll')
_funcname = '_nextafter'

_nextafter = getattr(_libm, _funcname)
_nextafter.restype = ctypes.c_double
_nextafter.argtypes = [ctypes.c_double, ctypes.c_double]

def nextafter(x, y):
    "Returns the next floating-point number after x in the direction of y."
    return _nextafter(x, y)

x = nextafter(100.0, sys.float_info.max);
print("{:.18f}".format(x));

prints

100.000000000000014211

as expected.

Conclusion

It looks obvious in hindsight, but at first I was really perplexed by why the little C program and Perl program were printing out different strings mainly because I had not realized at first that I had compiled the little C program using VS 2015 tools. Once I noticed that, everything fell in place.

I should go back and take a look at tony’s patch to move ahead on making perl build using the most recent Microsoft tools. I have been avoiding that because it opens another can of worms, mainly the fact that config.vc and config_H.vc don’t take into account more recent progress towards C99 compatibility in Visual Studio. For example, Perl’s configuration ignores the fact that stdbool.h and stdint.h exist, bool is now a keyword etc. The last one causes problems with CSS::Sass. I have been slowly figuring out what settings should be used for VS 2013, but this gives me one more reason to try to make some more progress on that front soon.

UPDATE (2016/06/10): There has been some progress in getting Perl to build with VS2015.

PS: You can discuss this post on r/perl