inconsistent rounding of floating point values

Started by uckelman, May 10, 2018, 12:21:42 PM

Previous topic - Next topic

uckelman

rounding.png is an all-white rectangle, 500x373, so is 0.1865 megapixels. Exiftool displays megapixels with 3 decimal places for images in the range of [0.001,1) megapixels, and uses sprintf to produce that (q.v. Image/ExifTool/Exif.pm:4014). Here's the output I get from 10.96 for rounding.png on Windows, Linux, and Wine on Linux:

Windows:
Z:\Downloads\exiftool>exiftool.exe Z:\rounding.png
ExifTool Version Number         : 10.96
File Name                       : rounding.png
Directory                       : Z:/
File Size                       : 1177 bytes
File Modification Date/Time     : 2018:05:10 16:26:42+01:00
File Access Date/Time           : 2018:05:10 16:27:00+01:00
File Creation Date/Time         : 2018:05:10 16:26:42+01:00
File Permissions                : rw-rw-rw-
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 500
Image Height                    : 373
Bit Depth                       : 8
Color Type                      : RGB
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Pixels Per Unit X               : 2835
Pixels Per Unit Y               : 2835
Pixel Units                     : meters
Modify Date                     : 2018:05:10 15:26:42
Comment                         : Created with GIMP
Image Size                      : 500x373
Megapixels                      : 0.187


Linux:

[juckelman@midas Image-ExifTool-10.96]$ ./exiftool ~/rounding.png
ExifTool Version Number         : 10.96
File Name                       : rounding.png
Directory                       : /home/juckelman
File Size                       : 1177 bytes
File Modification Date/Time     : 2018:05:10 16:26:42+01:00
File Access Date/Time           : 2018:05:10 16:27:00+01:00
File Inode Change Date/Time     : 2018:05:10 16:26:42+01:00
File Permissions                : rw-rw-r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 500
Image Height                    : 373
Bit Depth                       : 8
Color Type                      : RGB
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Pixels Per Unit X               : 2835
Pixels Per Unit Y               : 2835
Pixel Units                     : meters
Modify Date                     : 2018:05:10 15:26:42
Comment                         : Created with GIMP
Image Size                      : 500x373
Megapixels                      : 0.186


Wine on Linux:

[juckelman@midas exiftool]$ ./exiftool.exe Z:\\home\\juckelman\\rounding.png
fixme:winediag:start_process Wine Staging 2.19 is a testing version containing experimental patches.
fixme:winediag:start_process Please mention your exact version when filing bug reports on winehq.org.
ExifTool Version Number         : 10.96
File Name                       : rounding.png
Directory                       : Z:/home/juckelman
File Size                       : 1177 bytes
File Modification Date/Time     : 2018:05:10 16:26:42+01:00
File Access Date/Time           : 2018:05:10 16:27:00+01:00
File Creation Date/Time         : 2018:05:10 16:26:42+01:00
File Permissions                : rw-rw-rw-
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 500
Image Height                    : 373
Bit Depth                       : 8
Color Type                      : RGB
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Pixels Per Unit X               : 2835
Pixels Per Unit Y               : 2835
Pixel Units                     : meters
Modify Date                     : 2018:05:10 15:26:42
Comment                         : Created with GIMP
Image Size                      : 500x373
Megapixels                      : 0.186


Windows sprintf rounds 0.1865 up to 0.187, while sprintf on Linux and on Wine on Linux round down to 0.186. (Apparently glibc's sprintf rounds differently from the msvc one, and the standard doesn't specify how rounding should be done.) This inconsistency makes it difficult to compare exiftool output produced from the same input on different OSes. Would it be possible to do do the rounding entirely in Perl rather than relying on sprintf for this?

For example where fixed-precision floating point numbers are output (also, e.g. LightValue, FocalLength35efl, ScaleFactor35efl, CircleOfConfusion, and some others), if the value passed to sprintf was first rounded by the following function, we'd have consistent output across platforms:

sub nearest {
  my $incr = shift;
  my $val = shift;
  return $incr * int(($val + 0.5 * $incr) / $incr);
}


So, in the case of megapixels, we could have:

  $places =  $val >= 1 ? 1 : ($val >= 0.001 ? 3 : 6);
  sprintf("%.*f", $places, nearest(10**(-$places), $val));

instead of

  sprintf("%.*f",  ($val >= 1 ? 1 : ($val >= 0.001 ? 3 : 6)), $val);

Phil Harvey

Quote from: uckelman on May 10, 2018, 12:21:42 PM
if the value passed to sprintf was first rounded by the following function, we'd have consistent output across platforms:

sub nearest {
  my $incr = shift;
  my $val = shift;
  return $incr * int(($val + 0.5 * $incr) / $incr);
}

Are you sure?  What happens if $val is the result of a calculation with limited precision, and is, say, 1.4999999 on one platform and 1.500000 on another?  Then with rounding to 0 decimal places, this would be 1 on the first platform and 2 on the second.  Isn't this is the same reason that sprintf rounds slightly differently?  Try adding the -n option to see the unformatted value on each platform.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

uckelman

Quote from: Phil Harvey on May 10, 2018, 01:15:14 PM
Quote from: uckelman on May 10, 2018, 12:21:42 PM
if the value passed to sprintf was first rounded by the following function, we'd have consistent output across platforms:

sub nearest {
  my $incr = shift;
  my $val = shift;
  return $incr * int(($val + 0.5 * $incr) / $incr);
}

Are you sure?  What happens if $val is the result of a calculation with limited precision, and is, say, 1.4999999 on one platform and 1.500000 on another?  Then with rounding to 0 decimal places, this would be 1 on the first platform and 2 on the second.
In that case, you would indeed get 1 on the first platform and 2 on the second, as the inputs are different. That would be fine.

QuoteIsn't this is the same reason that sprintf rounds slightly differently?  Try adding the -n option to see the unformatted value on each platform.

No. Floating point imprecision is not the reason for the different outputs I'm seeing above. Using -n shows the megapixels as 0.1865 each case, so sprintf is getting the same input. This is entirely down to glibc sprintf using a different rounding rule from msvc sprintf. (There's an explanation for the different rounding at the top of this: http://www.exploringbinary.com/inconsistent-rounding-of-printed-floating-point-numbers/)

Phil Harvey

OK, thanks for the explanation.  Why is it that Windows is always different?  Unfortunately there are 485 places in the code where sprintf is used to format floating-point values.  It would be nice to be consistent (even if Windows can't be), but I wonder if it is worth the effort to change all these.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

uckelman

It shouldn't be very hard to fix this systematically. I'll send you a patch in a few days.