Skip Menu |

This queue is for tickets about the DBD-InterBase CPAN distribution.

Report information
The Basics
Id: 55841
Status: open
Priority: 0/
Queue: DBD-InterBase

People
Owner: Nobody in particular
Requestors: mjp [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Normal
Broken in: 0.48
Fixed in: (no value)



Subject: High-scale data conversion incorrect
Overview: NUMERIC and DECIMAL results of a high-enough scale are mangled when converted to their perl equivalents. The attached script demonstrates the problem in more detail, and the final section of this bug report has a link to another user suffering the same problem. How to reproduce: my $dbh = DBI->connect('dbi:InterBase:...', $user, $pass); my ($r) = $dbh->selectrow_array('SELECT CAST(1 AS NUMERIC(11, 10)) FROM rdb$database'); print "CAST(1 AS NUMERIC(11, 10)) => $r\n"; Expected output: CAST(1 AS NUMERIC(11, 10)) => 1 Actual output: CAST(1 AS NUMERIC(11, 10)) => -4.1410065408 Platform: Linux x86, DBD-IB-0.48, DBI-1.609, perl-5.10.1, Firebird 2.1 See also: http://perlmonks.org/?node_id=821607 ("Wrong calculation result with data retrieved with DBD::InterBase from view")
Subject: scale.t.pl
#! /usr/bin/perl # Usage: scale.pl dbname [user [pass]] # ./scale.pl employee.fdb SYSDBA masterkey use strict; use warnings; use DBI; use constant PRECISION => 18; use Test::More tests => (PRECISION - 1); my $dbh = DBI->connect(@ARGV, { RaiseError => 1}); diag "perl $^V"; diag "DBD::InterBase $DBD::InterBase::VERSION"; diag "DBI $DBI::VERSION"; diag "IB/FB server " . ($dbh->func('version', 'ib_database_info'))->{version}; for my $scale (1 .. (PRECISION - 1)) { my $cast = 'CAST(1 AS NUMERIC(' . PRECISION . ", $scale))"; my ($r) = $dbh->selectrow_array("SELECT $cast FROM rdb\$database") || $dbh->err; is($r, 1, "$cast => 1"); } __END__ # vim: set et ts=4 ft=perl:
Attached patch addresses the scale issue for me.
Subject: dbd-interbase-0_48-scale.diff
diff --git a/dbdimp.c b/dbdimp.c index 6aee963..932a103 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -1266,46 +1266,56 @@ AV *dbd_st_fetch(SV *sth, imp_sth_t *imp_sth) * nobody has a problem with it. */ { - ISC_INT64 q, r; - char buf[25], prec_buf[25]; - long double prec; - q = *((ISC_INT64 *) (var->sqldata)); - - /* - * I deliberately use two integers instead - * of casting the scaled int64 to a double. - * This avoids rounding errors in conversions - * to IEEE float, which is the whole reason - * for InterBase support of INT64. + static ISC_INT64 const scales[] = { 1LL, + 10LL, + 100LL, + 1000LL, + 10000LL, + 100000LL, + 1000000LL, + 10000000LL, + 100000000LL, + 1000000000LL, + 10000000000LL, + 100000000000LL, + 1000000000000LL, + 10000000000000LL, + 100000000000000LL, + 1000000000000000LL, + 10000000000000000LL, + 100000000000000000LL }; + ISC_INT64 i; /* significand */ + char buf[22]; /* NUMERIC(18,2) = -92233720368547758.08 + '\0' */ + + i = *((ISC_INT64 *) (var->sqldata)); + + /* We use the system snprintf(3) and system-specific + * format codes. :( On my perl, I was unable to + * persuade sv_setpvf to handle INT64 values with + * IVdf (and there is no I64f). + * - MJP 2010-03-25 */ -/* - * Define INT64 printf formats for various platforms - * using #defines eases the adding of a new platform (compiler/library) - */ - #if defined(_MSC_VER) /* Microsoft C compiler/library */ -# define P_INT64_RPEC "%.*I64f" -# define P_INT64_FULL "%s%I64d%s" +# define DBD_IB_INT64f "I64d" #elif defined(__BORLANDC__) /* Borland compiler/library */ -# define P_INT64_RPEC "%.*Lf" -# define P_INT64_FULL "%s%Ld%s" +# define DBD_IB_INT64f "Ld" #elif defined (__FreeBSD__) /* FreeBSD */ -# define P_INT64_RPEC "%.*Lf" -# define P_INT64_FULL "%s%qd%s" +# define DBD_IB_INT64f "qd" #else /* others: linux, various unices */ -# define P_INT64_RPEC "%.*Lf" -# define P_INT64_FULL "%s%lld%s" +# define DBD_IB_INT64f "lld" #endif - - prec = abs((int) (q % (int) - pow(10.0, (double) -var->sqlscale))) / - (long double) pow(10.0, (double) -var->sqlscale); - - sprintf(prec_buf, P_INT64_RPEC, (int) -var->sqlscale, prec); - - r = (ISC_INT64) (q / (int) pow(10.0, (double) -var->sqlscale)); - sprintf(buf, P_INT64_FULL, ((q < 0) && !(r < 0))? "-": "", r, - prec ? prec_buf + 1 : ""); + if (var->sqlscale == 0) { + snprintf(buf, sizeof(buf), "%"DBD_IB_INT64f, i); + } else { + ISC_INT64 divisor, remainder; + divisor = scales[-var->sqlscale]; + remainder = (i%divisor); + if (remainder < 0) remainder = -remainder; + + snprintf(buf, sizeof(buf), + "%"DBD_IB_INT64f".%0.*"DBD_IB_INT64f, + i/divisor, -var->sqlscale, remainder); + } sv_setpvn(sv, buf, strlen(buf)); }
On Tue Mar 23 12:11:07 2010, MJP wrote: Show quoted text
> Overview: > NUMERIC and DECIMAL results of a high-enough scale are mangled when > converted to their perl equivalents. The attached script demonstrates > the problem in more detail, and the final section of this bug report
has Show quoted text
> a link to another user suffering the same problem. > > How to reproduce: > > my $dbh = DBI->connect('dbi:InterBase:...', $user, $pass); > my ($r) = $dbh->selectrow_array('SELECT CAST(1 AS NUMERIC(11, 10)) > FROM rdb$database'); > print "CAST(1 AS NUMERIC(11, 10)) => $r\n"; > > Expected output: > > CAST(1 AS NUMERIC(11, 10)) => 1 > > Actual output: > > CAST(1 AS NUMERIC(11, 10)) => -4.1410065408 > > Platform: > > Linux x86, DBD-IB-0.48, DBI-1.609, perl-5.10.1, Firebird 2.1 > > See also: > > http://perlmonks.org/?node_id=821607 > ("Wrong calculation result with data retrieved with DBD::InterBase > from view") >
it is now solved in the git version https://github.com/mariuz/perl-dbd-interbase/ * Fix corrupted representation of high-scale NUMERIC/DECIMAL values (t/scale.t) (RT#55841) https://github.com/mariuz/perl-dbd-interbase/blob/master/Changes