Subject: | Result of encode_signed_number wrong for small negative numbers |
The attached script demonstrates that the encode_signed_number method
returns wrong results for small negative numbers (approx. 0 < $number <
-1e-6). The problem is that the signedness has to be determined *after*
the rounding was done. In the attached script the encode_signed_number
method is redefined on the fly and the 2nd call returns the correct result.
If you prefer it, I can turn this into a formal patch.
Regards,
Slaven
Subject: | polylineencoder_encode_signed_number.pl |
#!/usr/bin/perl
use Geo::Google::PolylineEncoder;
$test_number = -0.000001;
warn Geo::Google::PolylineEncoder->encode_signed_number($test_number);
*Geo::Google::PolylineEncoder::encode_signed_number = sub {
my ($self, $orig_num) = @_;
# Take the decimal value and multiply it by 1e5, flooring the result:
# Note 1: we limit the number to 5 decimal places with sprintf to avoid
# perl's rounding errors (they can throw the line off by a big margin sometimes)
# From Geo::Google: use the correct floating point precision or else
# 34.06694 - 34.06698 will give you -3.999999999999999057E-5 which doesn't
# encode properly. -4E-5 encodes properly.
# Note 2: we use sprintf(%8.0f ...) rather than int() for similar reasons
# (see perldoc -f int), though there's not much in it and the sprintf approach
# ends up doing more of a round() than a floor() in some cases:
# floor = -30 num=-30 *int=-29 1e5=-30 %3.5f=-0.00030 orig=-0.000300000000009959
# floor = 119 *num=120 int=119 1e5=120 %3.5f=0.00120 orig=0.0011999999999972
# We don't use floor() to avoid a dependency on POSIX
# do this in a series of steps so we can see what's going on in the debugger:
my $num3_5 = sprintf('%3.5f', $orig_num)+0;
my $num_1e5 = $num3_5 * 1e5;
my $num = sprintf('%8.0f', $num_1e5)+0;
my $is_negative = $num < 0;
# my $int = int($num_1e5);
# my $floor = floor($num_1e5);
# warn "floor = $floor\tnum=$num\tint=$int\t1e5=$num_1e5\t%3.5f=$num3_5\torig=$orig_num\n"
# if ($floor != $num or $num != $int);
# Convert the decimal value to binary.
# Note that a negative value must be inverted and provide padded values toward the byte boundary
# (perl ints are already manipulatable in binary, so do nothing)
# Shift the binary value:
$num = $num << 1;
# If the original decimal value was negative, invert this encoding:
if ($is_negative) {
$num = ~$num;
}
return $self->encode_number($num);
};
warn Geo::Google::PolylineEncoder->encode_signed_number($test_number);