Index: src/math.c ================================================================== --- src/math.c +++ src/math.c @@ -213,10 +213,24 @@ if( r.approx==0 && A.m%B.m!=0 ) r.approx = 1; r.m = A.m/B.m; r.e = A.e - B.e; return r; } + +/* +** Test if A is infinite. +*/ +int sqlite4_num_isinf(sqlite4_num A){ + return A.e>SQLITE4_MX_EXP && A.m!=0; +} + +/* +** Test if A is NaN. +*/ +int sqlite4_num_isnan(sqlite4_num A){ + return A.e>SQLITE4_MX_EXP && A.m==0; +} /* ** Compare numbers A and B. Return: ** ** 1 if ASQLITE4_MX_EXP ){ if( B.m==0 ) return 0; return B.sign ? 3 : 1; } if( A.sign!=B.sign ){ + if ( A.m==0 && B.m==0 ) return 2; return A.sign ? 1 : 3; } adjustExponent(&A, &B); if( A.sign ){ sqlite4_num t = A; @@ -321,11 +336,11 @@ i = incr; }else{ i = 0; } if( nIn<=0 ) goto not_a_valid_number; - if( nIn>=incr*2 + if( nIn>=incr*3 && ((c=zIn[i])=='i' || c=='I') && ((c=zIn[i+incr])=='n' || c=='N') && ((c=zIn[i+incr*2])=='f' || c=='F') ){ r.e = SQLITE4_MX_EXP+1; @@ -333,11 +348,11 @@ return r; } while( i='0' && c<='9' ){ - if( c==0 && nDigit==0 ){ + if( c=='0' && nDigit==0 ){ if( seenRadix && r.e > -(SQLITE4_MX_EXP+1000) ) r.e--; continue; } nDigit++; if( nDigit<=18 ){ @@ -347,44 +362,61 @@ if( c!='0' ) r.approx = 1; if( !seenRadix ) r.e++; } }else if( c=='.' ){ seenRadix = 1; - }else{ + }else if( c=='e' || c=='E' ){ + int exp = 0; + int expsign = 0; + int nEDigit = 0; + if( zIn[i]=='-' ){ + expsign = 1; + i += incr; + }else if( zIn[i]=='+' ){ + i += incr; + } + if( i>=nIn ) goto not_a_valid_number; + while( i'9' ) goto not_a_valid_number; + if( c=='0' && nEDigit==0 ) continue; + nEDigit++; + if( nEDigit>3 ) goto not_a_valid_number; + exp = exp*10 + c - '0'; + } + if( expsign ) exp = -exp; + r.e += exp; break; - } - } - if( c=='e' || c=='E' ){ - int exp = 0; - int expsign = 0; - int nEDigit = 0; - if( zIn[i]=='-' ){ - expsign = 1; - i += incr; - }else if( zIn[i]=='+' ){ - i += incr; - } - if( i>=nIn ) goto not_a_valid_number; - while( i'9' ) break; - if( c=='0' && nEDigit==0 ) continue; - nEDigit++; - if( nEDigit>3 ) goto not_a_valid_number; - exp = exp*10 + c - '0'; - } - if( expsign ) exp = -exp; - r.e += exp; - } - if( c!=0 ) goto not_a_valid_number; + }else{ + goto not_a_valid_number; + } + } return r; not_a_valid_number: r.e = SQLITE4_MX_EXP+1; r.m = 0; return r; } + +/* +** Convert an sqlite4_int64 to a number and return that number. +*/ +sqlite4_num sqlite4_num_from_int64(sqlite4_int64 n){ + sqlite4_num r; + r.approx = 0; + r.e = 0; + r.sign = n < 0; + if( n>=0 ){ + r.m = n; + }else if( n!=SMALLEST_INT64 ){ + r.m = -n; + }else{ + r.m = 1+(u64)LARGEST_INT64; + } + return r; +} /* ** Convert an integer into text in the buffer supplied. The ** text is zero-terminated and right-justified in the buffer. ** A pointer to the first character of text is returned. @@ -467,12 +499,14 @@ removeTrailingZeros(zNum, &n); if( n>0 ){ zOut[0] = '.'; memcpy(zOut+1, zNum, n); nOut += n; + zOut[n+1] = 0; + }else{ + zOut[0] = 0; } - zOut[n+1] = 0; return nOut; } if( x.e<0 && x.e >= -n-5 ){ /* Values less than 1 and with no more than 5 subsequent zeros prior ** to the first significant digit. Ex: 0.0000012345 */ Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -4345,11 +4345,11 @@ */ typedef struct sqlite4_num sqlite4_num; struct sqlite4_num { unsigned char sign; /* Sign of the overall value */ unsigned char approx; /* True if the value is approximate */ - unsigned short e; /* The exponent. */ + short e; /* The exponent. */ sqlite4_uint64 m; /* The significant */ }; /* ** CAPI4REF: Operations On SQLite Number Objects Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -462,11 +462,11 @@ ** These macros are designed to work correctly on both 32-bit and 64-bit ** compilers. */ #define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) #define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) -#define LARGEST_UINT64 (0xffffffff|(((i64)0xffffffff)<<32)) +#define LARGEST_UINT64 (0xffffffff|(((u64)0xffffffff)<<32)) /* ** Round up a number to the next larger multiple of 8. This is used ** to force 8-byte alignment on 64-bit architectures. */ ADDED test/num.test Index: test/num.test ================================================================== --- /dev/null +++ test/num.test @@ -0,0 +1,91 @@ +# 2001 September 15 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing the sqlite_*_printf() interface. +# +# $Id: printf.test,v 1.31 2009/02/01 00:21:10 drh Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test num-1.1.1 { + sqlite4_num_compare 20 20 +} {equal} +do_test num-1.1.2 { + sqlite4_num_compare 20 2e1 +} {equal} +do_test num-1.1.3 { + sqlite4_num_compare -00034 -3.4e1 +} {equal} +do_test num-1.1.4 { + sqlite4_num_compare -inf +inf +} {lesser} +do_test num-1.1.5 { + sqlite4_num_compare -inf 0 +} {lesser} +do_test num-1.1.6 { + sqlite4_num_compare inf 4 +} {greater} +do_test num-1.1.7 { + sqlite4_num_compare nan 7 +} {incomparable} +# Is +0 > -0? +#do_test num-equal-1.1.4 { +# sqlite4_num_compare +0 -0 +#} {equal} + +do_test num-2.1.1 { + sqlite4_num_to_text [sqlite4_num_from_text 37] +} {37} +do_test num-2.1.2 { + sqlite4_num_to_text [sqlite4_num_from_text 37 2] +} {37} +do_test num-2.1.4 { + sqlite4_num_compare [sqlite4_num_from_text 2.9e2X 5] 290 +} {equal} +do_test num-2.1.5 { + sqlite4_num_isnan [sqlite4_num_from_text inf 2] +} {true} +do_test num-2.1.6 { + sqlite4_num_isinf [sqlite4_num_from_text inf 3] +} {true} + +do_test num-3.1.1 { + sqlite4_num_to_text [sqlite4_num_add 5 7] +} {12} + +do_test num-4.1.1 { + sqlite4_num_to_text [sqlite4_num_sub 9 3] +} {6} +do_test num-4.1.2 { + sqlite4_num_to_text [sqlite4_num_sub 5 12] +} {-7} +do_test num-4.2.1 { + sqlite4_num_compare [sqlite4_num_sub 1 1] [sqlite4_num_sub -1 -1] +} {equal} + +do_test num-5.1.1 { + sqlite4_num_to_text [sqlite4_num_mul 9 8] +} {72} + +do_test num-6.1.1 { + sqlite4_num_to_text [sqlite4_num_div 6 5] +} {1.2} +do_test num-6.1.2 { + sqlite4_num_compare 2 [sqlite4_num_div 2 1] +} {equal} +do_test num-6.1.3 { + sqlite4_num_to_text [sqlite4_num_div 2 1] +} {2} +do_test num-6.1.4 { + sqlite4_num_to_text [sqlite4_num_div 22 10] +} {2.2} +finish_test Index: test/test_main.c ================================================================== --- test/test_main.c +++ test/test_main.c @@ -4347,10 +4347,209 @@ return TCL_ERROR; } sqlite4_test_control(SQLITE4_TESTCTRL_OPTIMIZATIONS, db, mask); return TCL_OK; } + +#define NUM_FORMAT "sign:%d approx:%d e:%d m:%lld" + +/* Append a return value representing a sqlite4_num. +*/ +static void append_num_result( Tcl_Interp *interp, sqlite4_num A ){ + char buf[100]; + sprintf( buf, NUM_FORMAT, A.sign, A.approx, A.e, A.m ); + Tcl_AppendResult(interp, buf, 0); +} + +/* Convert a string either representing a sqlite4_num (listing its fields as +** returned by append_num_result) or that can be parsed as one. Invalid +** strings become NaN. +*/ +static sqlite4_num test_parse_num( char *arg ){ + sqlite4_num A; + int sign, approx, e; + if( sscanf( arg, NUM_FORMAT, &sign, &approx, &e, &A.m)==4 ){ + A.sign = sign; + A.approx = approx; + A.e = e; + return A; + } else { + return sqlite4_num_from_text(arg, -1, 0); + } +} + +/* Convert return values of sqlite4_num to strings that will be readable in +** the tests. +*/ +static char *describe_num_comparison( int code ){ + switch( code ){ + case 0: return "incomparable"; + case 1: return "lesser"; + case 2: return "equal"; + case 3: return "greater"; + default: return "error"; + } +} + +/* Compare two numbers A and B. Returns "incomparable", "lesser", "equal", +** "greater", or "error". +*/ +static int test_num_compare( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv /* Text of each argument */ +){ + sqlite4_num A, B; + int cmp; + if( argc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " NUM NUM\"", 0); + return TCL_ERROR; + } + + A = test_parse_num( argv[1] ); + B = test_parse_num( argv[2] ); + cmp = sqlite4_num_compare(A, B); + Tcl_AppendResult( interp, describe_num_comparison( cmp ), 0); + return TCL_OK; +} + +/* Create a sqlite4_num from a string. The optional second argument specifies +** how many bytes may be read. +*/ +static int test_num_from_text( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv /* Text of each argument */ +){ + sqlite4_num A; + int len; + if( argc!=2 && argc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " STRING\" or \"", argv[0], " STRING INTEGER\"", 0); + return TCL_ERROR; + } + + if( argc==3 ){ + if ( Tcl_GetInt(interp, argv[2], &len) ) return TCL_ERROR; + }else{ + len = -1; + } + + A = sqlite4_num_from_text( argv[1], len, 0 ); + append_num_result(interp, A); + return TCL_OK; +} + +static int test_num_to_text( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv /* Text of each argument */ +){ + char text[30]; + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " NUM\"", 0); + return TCL_ERROR; + } + sqlite4_num_to_text( test_parse_num( argv[1] ), text ); + Tcl_AppendResult( interp, text, 0 ); + return TCL_OK; +} + +static int test_num_binary_op( + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv, /* Text of each argument */ + sqlite4_num (*op) (sqlite4_num, sqlite4_num) +){ + sqlite4_num A, B, R; + if( argc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " NUM NUM\"", 0); + return TCL_ERROR; + } + A = test_parse_num(argv[1]); + B = test_parse_num(argv[2]); + R = op(A, B); + append_num_result(interp, R); + return TCL_OK; +} + +static int test_num_add( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv /* Text of each argument */ +){ + return test_num_binary_op( interp, argc, argv, sqlite4_num_add ); +} + +static int test_num_sub( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv /* Text of each argument */ +){ + return test_num_binary_op( interp, argc, argv, sqlite4_num_sub ); +} + +static int test_num_mul( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv /* Text of each argument */ +){ + return test_num_binary_op( interp, argc, argv, sqlite4_num_mul ); +} + +static int test_num_div( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv /* Text of each argument */ +){ + return test_num_binary_op( interp, argc, argv, sqlite4_num_div ); +} + +static int test_num_predicate( + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv, /* Text of each argument */ + int (*pred) (sqlite4_num) +){ + sqlite4_num A; + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " NUM\"", 0); + return TCL_ERROR; + } + A = test_parse_num(argv[1]); + Tcl_AppendResult(interp, pred(A) ? "true" : "false", 0); + return TCL_OK; +} + +static int test_num_isinf( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv /* Text of each argument */ +){ + return test_num_predicate( interp, argc, argv, sqlite4_num_isinf ); +} + +static int test_num_isnan( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + char **argv /* Text of each argument */ +){ + return test_num_predicate( interp, argc, argv, sqlite4_num_isnan ); +} /* ** Register commands with the TCL interpreter. */ int Sqlitetest1_Init(Tcl_Interp *interp){ @@ -4400,11 +4599,20 @@ { "sqlite_delete_function", (Tcl_CmdProc*)delete_function }, { "sqlite_delete_collation", (Tcl_CmdProc*)delete_collation }, { "sqlite4_get_autocommit", (Tcl_CmdProc*)get_autocommit }, { "sqlite4_stack_used", (Tcl_CmdProc*)test_stack_used }, { "printf", (Tcl_CmdProc*)test_printf }, - { "sqlite4IoTrace", (Tcl_CmdProc*)test_io_trace }, + { "sqlite4IoTrace", (Tcl_CmdProc*)test_io_trace }, + { "sqlite4_num_compare", (Tcl_CmdProc*)test_num_compare }, + { "sqlite4_num_from_text", (Tcl_CmdProc*)test_num_from_text }, + { "sqlite4_num_to_text", (Tcl_CmdProc*)test_num_to_text }, + { "sqlite4_num_add", (Tcl_CmdProc*)test_num_add }, + { "sqlite4_num_sub", (Tcl_CmdProc*)test_num_sub }, + { "sqlite4_num_mul", (Tcl_CmdProc*)test_num_mul }, + { "sqlite4_num_div", (Tcl_CmdProc*)test_num_div }, + { "sqlite4_num_isinf", (Tcl_CmdProc*)test_num_isinf }, + { "sqlite4_num_isnan", (Tcl_CmdProc*)test_num_isnan }, }; static struct { char *zName; Tcl_ObjCmdProc *xProc; void *clientData;