www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - printf bugs

reply Sean Kelly <sean f4.ca> writes:
You probably already know these, but I thought I'd report them anyway.  The
code:

# long i = 1;
# double d = 1.0;
# real r = 2.0;
# printf( "%i %f %f\n", i, d, r );
# printf( "%f\n", d );
# printf( "%f\n", r );
# if( r == 2.0 )
#     printf( "real == 2.0\n" );
# else
#     printf( "real != 2.0\n" );
# printf( "real %s 2.0\n", r == 2.0 ? "==" : "!=" );

prints:

1 0.000000 0.000000
1.000000
-0.000000
real == 2.0
real Error: Access Violation

There are a few things going on here.  Real always prints as -0.0, floats print
as 0.0 if they aren't the first element in the format string, and strings are a
tad broken :)
Jul 01 2004
next sibling parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
well... these aren't really bugs since printf is doing exactly what it is
supposed to do. Maybe I'm being too picky about the word "bug" (for example
%f is only for type "float" not "double" or "real"). Walter said he is
writing a printf replacement and since vararg functions can detect the types
of the inputs the format strings will presumably become much simpler - if
there are format strings at all.

"Sean Kelly" <sean f4.ca> wrote in message
news:cc1qqb$2dq7$1 digitaldaemon.com...
 You probably already know these, but I thought I'd report them anyway.

 code:

 # long i = 1;
 # double d = 1.0;
 # real r = 2.0;
 # printf( "%i %f %f\n", i, d, r );
 # printf( "%f\n", d );
 # printf( "%f\n", r );
 # if( r == 2.0 )
 #     printf( "real == 2.0\n" );
 # else
 #     printf( "real != 2.0\n" );
 # printf( "real %s 2.0\n", r == 2.0 ? "==" : "!=" );

 prints:

 1 0.000000 0.000000
 1.000000
 -0.000000
 real == 2.0
 real Error: Access Violation

 There are a few things going on here.  Real always prints as -0.0, floats

 as 0.0 if they aren't the first element in the format string, and strings

 tad broken :)

Jul 01 2004
next sibling parent Sean Kelly <sean f4.ca> writes:
In article <cc1suk$2gkq$1 digitaldaemon.com>, Ben Hinkle says...
well... these aren't really bugs since printf is doing exactly what it is
supposed to do. Maybe I'm being too picky about the word "bug" (for example
%f is only for type "float" not "double" or "real"). Walter said he is
writing a printf replacement and since vararg functions can detect the types
of the inputs the format strings will presumably become much simpler - if
there are format strings at all.

Good point. I just downloaded 0.94 and floating point has completely changed for this input set. The bad news is that it doesn't work at all now :) I think I'm going to roll back to a previous version of the compiler and wait until printf is finished before moving forward. Is there a place where old builds are stored? I couldn't find one on the FTP site. Sean
Jul 01 2004
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
In article <cc1suk$2gkq$1 digitaldaemon.com>, Ben Hinkle says...
well... these aren't really bugs since printf is doing exactly what it is
supposed to do. Maybe I'm being too picky about the word "bug" (for example
%f is only for type "float" not "double" or "real").

Just to be picky, %f is actually for type "double," at least according to the C99 standard. But if D had been calling the Windows version of printf then it's quite possible their version just has different/older rules. I know I've found quite a few discrepancies with scanf. Sean
Jul 01 2004
parent Ben Hinkle <bhinkle4 juno.com> writes:
Sean Kelly wrote:

 In article <cc1suk$2gkq$1 digitaldaemon.com>, Ben Hinkle says...
well... these aren't really bugs since printf is doing exactly what it is
supposed to do. Maybe I'm being too picky about the word "bug" (for
example %f is only for type "float" not "double" or "real").

Just to be picky, %f is actually for type "double," at least according to the C99 standard.

oh yeah. duh. for some reason I was thinking of scanf. maybe because of the recent scanf + complex question. oh well!
 But if D had been calling the Windows version of printf
 then it's
 quite possible their version just has different/older rules.  I know I've
 found quite a few discrepancies with scanf.
 
 Sean

Jul 01 2004
prev sibling next sibling parent reply "Walter" <newshound digitalmars.com> writes:
Reals in D correspond to long doubles in C, which means you need to be using
%Lf, not %f, to print them. Next, for strings use %.*s, not %s.

"Sean Kelly" <sean f4.ca> wrote in message
news:cc1qqb$2dq7$1 digitaldaemon.com...
 You probably already know these, but I thought I'd report them anyway.

 code:

 # long i = 1;
 # double d = 1.0;
 # real r = 2.0;
 # printf( "%i %f %f\n", i, d, r );
 # printf( "%f\n", d );
 # printf( "%f\n", r );
 # if( r == 2.0 )
 #     printf( "real == 2.0\n" );
 # else
 #     printf( "real != 2.0\n" );
 # printf( "real %s 2.0\n", r == 2.0 ? "==" : "!=" );

 prints:

 1 0.000000 0.000000
 1.000000
 -0.000000
 real == 2.0
 real Error: Access Violation

 There are a few things going on here.  Real always prints as -0.0, floats

 as 0.0 if they aren't the first element in the format string, and strings

 tad broken :)

Jul 02 2004
parent reply Sean Kelly <sean f4.ca> writes:
In article <cc3782$1idh$1 digitaldaemon.com>, Walter says...
Reals in D correspond to long doubles in C, which means you need to be using
%Lf, not %f, to print them. Next, for strings use %.*s, not %s.

Thanks. Talking to Ben made me realize the problem with reals. But thanks to the nifty new _arguments stuff, I suspect it won't be a problem for long :) I'm having issues with normal doubles in 0.94, but I suspect this is because the new version of printf is incomplete, since the version in 0.93 worked just fine. This is a related issue I ran into writing scanf. The %s parameter currently accepts both C strings and D strings. I had tried this to differentiate between them: if( _arguments[arg] == typeid( char[]* ) ) // D string else if( _arguments[arg] == typeid( char** ) ) // C string But both typeids are equivalent--the first condition is called for both D and C strings. And if I reverse them then the new first condition (char**) is called for both. Is there any way to tell the difference between D and C strings using the typeid code (or really pointers to D and C strings, since I can't deref a void pointer)? Assuming there isn't, I propose adding an extension to printf (%S) that corresponds to D strings. I've already added this to scanf, since the %.*s syntax doesn't make sense in this context. Sean
Jul 02 2004
parent reply Regan Heath <regan netwin.co.nz> writes:
On Fri, 2 Jul 2004 18:56:40 +0000 (UTC), Sean Kelly <sean f4.ca> wrote:

 In article <cc3782$1idh$1 digitaldaemon.com>, Walter says...
 Reals in D correspond to long doubles in C, which means you need to be 
 using
 %Lf, not %f, to print them. Next, for strings use %.*s, not %s.

Thanks. Talking to Ben made me realize the problem with reals. But thanks to the nifty new _arguments stuff, I suspect it won't be a problem for long :) I'm having issues with normal doubles in 0.94, but I suspect this is because the new version of printf is incomplete, since the version in 0.93 worked just fine. This is a related issue I ran into writing scanf. The %s parameter currently accepts both C strings and D strings. I had tried this to differentiate between them: if( _arguments[arg] == typeid( char[]* ) ) // D string else if( _arguments[arg] == typeid( char** ) ) // C string But both typeids are equivalent--the first condition is called for both D and C strings. And if I reverse them then the new first condition (char**) is called for both. Is there any way to tell the difference between D and C strings using the typeid code (or really pointers to D and C strings, since I can't deref a void pointer)? Assuming there isn't, I propose adding an extension to printf (%S) that corresponds to D strings. I've already added this to scanf, since the %.*s syntax doesn't make sense in this context.

The new printf would not necessarily have to even use %s %S %d %l etc.. in fact why should you need to tell it what type the arguments are at all? it already knows! IMO the new printf should work something like this... int a; long b; real c; printf("my int is $1 my long is $2 and my real is $3",a,b,c); printf("my long is $2 my real is $3 and my int is $1",a,b,c); or similar, basically you specify the position of the arg to use, this allows this also: printf("regan was $1 and also $1 again",a); *if* you/walter are worried about compatibilty, then lets have a printf that works like the C one, but also a new variant that works something like I have described, simply call it 'print' perhaps. the above proposal would be incomplete without the ability to print a type like another type, so for example if you want to print a char like an int, then you can use a cast i.e. char c; printf("regan was $1",cast(int)c); Something just occured to me, if you say printf("regan was $1",&c); (like in C printf("%p",&c) or printf("%x",&c); what TypeInfo does printf get? it's the address of a char, is that ever going to be handled differently to the address of a long or real or.. so should the TypeInfo be "char *" or should it simply tell you it's an "l-value"? Regan. -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
Jul 02 2004
next sibling parent Daniel Horn <hellcatv hotmail.com> writes:
Now that would make quick and dirty printf programs really fun to write :-)
and the translator folks would love it!

Regan Heath wrote:
 
 IMO the new printf should work something like this...
 
 int a;
 long b;
 real c;
 
 printf("my int is $1 my long is $2 and my real is $3",a,b,c);
 printf("my long is $2 my real is $3 and my int is $1",a,b,c);
 
 or similar, basically you specify the position of the arg to use, this 
 allows this also:
 
 printf("regan was $1 and also $1 again",a);
 
 *if* you/walter are worried about compatibilty, then lets have a printf 
 that works like the C one, but also a new variant that works something 
 like I have described, simply call it 'print' perhaps.
 
 the above proposal would be incomplete without the ability to print a 
 type like another type, so for example if you want to print a char like 
 an int, then you can use a cast i.e.
 
 char c;
 printf("regan was $1",cast(int)c);
 
 Something just occured to me, if you say
 
 printf("regan was $1",&c);
 (like in C printf("%p",&c) or printf("%x",&c);
 
 what TypeInfo does printf get? it's the address of a char, is that ever 
 going to be handled differently to the address of a long or real or.. so 
 should the TypeInfo be "char *" or should it simply tell you it's an 
 "l-value"?
 
 Regan.
 

Jul 02 2004
prev sibling next sibling parent reply Sean Kelly <sean f4.ca> writes:
Regan Heath wrote:
 
 The new printf would not necessarily have to even use %s %S %d %l etc.. 
 in fact why should you need to tell it what type the arguments are at 
 all? it already knows!
 
 IMO the new printf should work something like this...
 
 int a;
 long b;
 real c;
 
 printf("my int is $1 my long is $2 and my real is $3",a,b,c);
 printf("my long is $2 my real is $3 and my int is $1",a,b,c);
 
 or similar, basically you specify the position of the arg to use, this 
 allows this also:
 
 printf("regan was $1 and also $1 again",a);

I see two problems with this. First, as per my post I don't know if there's any way to differentiate between a dynamic D array and a pointer to a sequence of the same type. Hopefully Walter can suggest a method that may work. Second, printf specifiers contain not only type information but formatting information as well. This is really only an issue with floating point types, but in that case the user may want to specify whether they want scientific notation, decimal notation, etc. Sean
Jul 02 2004
parent reply Regan Heath <regan netwin.co.nz> writes:
On Fri, 02 Jul 2004 18:00:09 -0700, Sean Kelly <sean f4.ca> wrote:

 Regan Heath wrote:
 The new printf would not necessarily have to even use %s %S %d %l etc.. 
 in fact why should you need to tell it what type the arguments are at 
 all? it already knows!

 IMO the new printf should work something like this...

 int a;
 long b;
 real c;

 printf("my int is $1 my long is $2 and my real is $3",a,b,c);
 printf("my long is $2 my real is $3 and my int is $1",a,b,c);

 or similar, basically you specify the position of the arg to use, this 
 allows this also:

 printf("regan was $1 and also $1 again",a);

I see two problems with this. First, as per my post I don't know if there's any way to differentiate between a dynamic D array and a pointer to a sequence of the same type.

I was wondering the same thing, however, do you ever need to differentiate? What are you going to print in either case, an address right? or..
 Hopefully Walter can suggest a method that may work.  Second, printf 
 specifiers contain not only type information but formatting information 
 as well.  This is really only an issue with floating point types, but in 
 that case the user may want to specify whether they want scientific 
 notation, decimal notation, etc.

True, e, f and g and also d, i, o and x behave differently. We could incorporate this information into the format string another way, more like the width and precision specifiers. I am struggling to come up with a nice compact way to do it... Regan -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
Jul 03 2004
parent reply Sean Kelly <sean f4.ca> writes:
Regan Heath wrote:
 On Fri, 02 Jul 2004 18:00:09 -0700, Sean Kelly <sean f4.ca> wrote:
 
 I see two problems with this.  First, as per my post I don't know if 
 there's any way to differentiate between a dynamic D array and a 
 pointer to a sequence of the same type.

I was wondering the same thing, however, do you ever need to differentiate? What are you going to print in either case, an address right? or..

How each type is handled is different. D arrays have a length specifier and are not null terminated (indeed, they may legally contain multiple nulls) while C arrays have no length specifier and are null terminated. So the routine to find the end of each type will be different. This turned out to be a problem for me with scanf. I've added %S as the D alternative to %s and %C as the D alternative to %C but that leaves me with no good solution for scansets (%[a-b]). I'm currently defaulting to D-style arrays in this case but the code will crash if a C-style array is passed.
 Hopefully Walter can suggest a method that may work.  Second, printf 
 specifiers contain not only type information but formatting 
 information as well.  This is really only an issue with floating point 
 types, but in that case the user may want to specify whether they want 
 scientific notation, decimal notation, etc.

True, e, f and g and also d, i, o and x behave differently. We could incorporate this information into the format string another way, more like the width and precision specifiers. I am struggling to come up with a nice compact way to do it...

Possibly something like this $[1f]. The braces could enclose all needed information about type formatting. Or if consistency with scanf is desired then perhaps a different brace type: %{1f}. Still not ideal, but it's something. Sean
Jul 03 2004
parent Regan Heath <regan netwin.co.nz> writes:
On Sat, 03 Jul 2004 16:05:08 -0700, Sean Kelly <sean f4.ca> wrote:

 Regan Heath wrote:
 On Fri, 02 Jul 2004 18:00:09 -0700, Sean Kelly <sean f4.ca> wrote:

 I see two problems with this.  First, as per my post I don't know if 
 there's any way to differentiate between a dynamic D array and a 
 pointer to a sequence of the same type.

I was wondering the same thing, however, do you ever need to differentiate? What are you going to print in either case, an address right? or..

How each type is handled is different. D arrays have a length specifier and are not null terminated (indeed, they may legally contain multiple nulls) while C arrays have no length specifier and are null terminated. So the routine to find the end of each type will be different. This turned out to be a problem for me with scanf. I've added %S as the D alternative to %s and %C as the D alternative to %C but that leaves me with no good solution for scansets (%[a-b]). I'm currently defaulting to D-style arrays in this case but the code will crash if a C-style array is passed.

Ahh.. I see.. you were talking about the value of the array, not the reference itself. What typeinfo does it give for a D array? and what for a char *? I suspect eventually the typeinfo will be different, and you will be able to treat them as you need to. Walter has mentioned several times that TypeInfo will get better. I dont think we can write this new printf variant until the TypeInfo updates/changes are complete.
 Hopefully Walter can suggest a method that may work.  Second, printf 
 specifiers contain not only type information but formatting 
 information as well.  This is really only an issue with floating point 
 types, but in that case the user may want to specify whether they want 
 scientific notation, decimal notation, etc.

True, e, f and g and also d, i, o and x behave differently. We could incorporate this information into the format string another way, more like the width and precision specifiers. I am struggling to come up with a nice compact way to do it...

Possibly something like this $[1f]. The braces could enclose all needed information about type formatting. Or if consistency with scanf is desired then perhaps a different brace type: %{1f}. Still not ideal, but it's something.

See my post to Juliano, I came up with some ideas. Regan -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
Jul 03 2004
prev sibling parent reply Juliano Ravasi Ferraz <contact write-my-first-name-here.info> writes:
Regan Heath wrote:
 printf("my int is $1 my long is $2 and my real is $3",a,b,c);
 printf("my long is $2 my real is $3 and my int is $1",a,b,c);

What if we have more than 9 arguments? "$10" is the 10th argument or the 1st argument followed by a zero? We may also need aditional information on the format codes: - number of characters to reserve (strings, integer, etc); - max number of character before trunc (strings mostly); - how to print thousands separator (localizable; integers and floats); - hex char case (0X1EF vs 0x1ef) (integers only); - how to decorate octals and hexadecimals (localizable; integers only); - precision and significant digits (floats); - full representation or scientific notation (floats). And some of the above information may also appear among the arguments (for example, if you want to dynamic calculate column sizes, the number of characters to reserve would be better placed among the arguments). -- Miles "Threat Eliminated." - Tio
Jul 03 2004
parent Regan Heath <regan netwin.co.nz> writes:
On Sat, 03 Jul 2004 14:16:49 -0300, Juliano Ravasi Ferraz 
<contact write-my-first-name-here.info> wrote:

 Regan Heath wrote:
 printf("my int is $1 my long is $2 and my real is $3",a,b,c);
 printf("my long is $2 my real is $3 and my int is $1",a,b,c);

What if we have more than 9 arguments? "$10" is the 10th argument or the 1st argument followed by a zero?

The way the original printf avoids this problem is that when it uses numbers i.e. for width/precison etc it always follows them with either a . or the type character. So in this case we'd have to use another $ char i.e. printf("$1$0 $10$"); the first is param 1 followed by a 0, the second is param 10. The other alternative is to use letters, that gives a slightly higher limit of 26, which only delays the problem.
 We may also need aditional information on the format codes:
 - number of characters to reserve (strings, integer, etc);
 - max number of character before trunc (strings mostly);
 - how to print thousands separator (localizable; integers and floats);
 - hex char case (0X1EF vs 0x1ef) (integers only);
 - how to decorate octals and hexadecimals (localizable; integers only);
 - precision and significant digits (floats);
 - full representation or scientific notation (floats).

 And some of the above information may also appear among the arguments 
 (for example, if you want to dynamic calculate column sizes, the number 
 of characters to reserve would be better placed among the arguments).

We want to try and do these things similarly to the orginal, so dynamic sizes would use the * symbol, width and precision would remain the same.. perhaps.. printf("$5.3,1$"); width of 5, '.' to seperate, precision of 3, ',' to seperate, then param number. That leaves the differences between d, o, and x and also f, g and e which we can add as flags eg. Existing flags – + 0 blank (' ') # New flags o - octal representation x/X - hex representation e - scientific (floats) g - most compact (floats) eg. printf(" $x,5.3,1$ "); - in hex printf(" $ x,5.3,1$ "); - in hex pad with spaces printf(" $0x,5.3,1$ "); - in hex pad with zeroes printf(" $#x,5.3,1$ "); - in hex prefix with 0x printf(" $0o,*.*,1$ "); - in octal prefix with zeroes, dynamic width and precision Regan -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
Jul 03 2004
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
In 0.93, the code:

int main()
{
     char c;
     printf( "%i %i\n", char.init, c.init );
     printf( "%i %i\n", char.min, c.min );
     printf( "%i %i\n", char.max, c.max );
     return 0;
}

prints:

255 255
0 0
255 255

And in 0.94 it prints:

-1 255
0 0
-1 -1

I'm not sure offhand if this is a bug with properties returning signed 
numbers sometimes or if it's just a problem with printf.  If I change 
the code to this:

int main()
{
     char c;
     printf( "%u %u\n", char.init, c.init );
     printf( "%u %u\n", char.min, c.min );
     printf( "%u %u\n", char.max, c.max );
     return 0;
}

This is the output in 0.94:

4294967295 255
0 0
4294967295 4294967295


Sean
Jul 04 2004
parent reply J C Calvarese <jcc7 cox.net> writes:
In article <ccarcn$jsl$1 digitaldaemon.com>, Sean Kelly says...
In 0.93, the code:

int main()
{
     char c;
     printf( "%i %i\n", char.init, c.init );
     printf( "%i %i\n", char.min, c.min );
     printf( "%i %i\n", char.max, c.max );
     return 0;
}

Why are you using "%i"? What does it mean? (Maybe it needs to be added to our specifier list: http://www.prowiki.org/wiki4d/wiki.cgi?HowTo/printf) I'd think you want either "%u" for decimal or %x for hex.
prints:

255 255
0 0
255 255

And in 0.94 it prints:

-1 255
0 0
-1 -1

I'm not sure offhand if this is a bug with properties returning signed 
numbers sometimes or if it's just a problem with printf.  If I change 
the code to this:

int main()
{
     char c;
     printf( "%u %u\n", char.init, c.init );
     printf( "%u %u\n", char.min, c.min );
     printf( "%u %u\n", char.max, c.max );
     return 0;
}

This is the output in 0.94:

4294967295 255

That's odd. I thought they'd both be 255. Well, I've never completely understood printf (and I don't think I'll be sad when if it becomes obsolete).
0 0
4294967295 4294967295

Again, I thought they'd both be 255.
Sean

jcc7
Jul 04 2004
parent reply Regan Heath <regan netwin.co.nz> writes:
On Mon, 5 Jul 2004 06:36:46 +0000 (UTC), J C Calvarese <jcc7 cox.net> 
wrote:
 In article <ccarcn$jsl$1 digitaldaemon.com>, Sean Kelly says...
 In 0.93, the code:

 int main()
 {
     char c;
     printf( "%i %i\n", char.init, c.init );
     printf( "%i %i\n", char.min, c.min );
     printf( "%i %i\n", char.max, c.max );
     return 0;
 }

Why are you using "%i"? What does it mean? (Maybe it needs to be added to our specifier list: http://www.prowiki.org/wiki4d/wiki.cgi?HowTo/printf)

It's identical to %d. (according to MSDN) type | output format -------------------- d int | Signed decimal integer. i int | Signed decimal integer. o int | Unsigned octal integer. u int | Unsigned decimal integer. x int | Unsigned hexadecimal integer, using “abcdef.” X int | Unsigned hexadecimal integer, using “ABCDEF.” (from MSDN)
 I'd think you want either "%u" for decimal or %x for hex.

 prints:

 255 255
 0 0
 255 255

 And in 0.94 it prints:

 -1 255
 0 0
 -1 -1

 I'm not sure offhand if this is a bug with properties returning signed
 numbers sometimes or if it's just a problem with printf.  If I change
 the code to this:

 int main()
 {
     char c;
     printf( "%u %u\n", char.init, c.init );
     printf( "%u %u\n", char.min, c.min );
     printf( "%u %u\n", char.max, c.max );
     return 0;
 }

 This is the output in 0.94:

 4294967295 255

That's odd. I thought they'd both be 255.

As would I, I think however, something stranger is going on, this code: void main() { int v; v = char.init; printf("%i %i\n",char.init,v); } prints: 255 255 yet this code: void main() { int v; //v = char.init; printf("%i %i\n",char.init,v); } prints: -1 0 err.. weird.
 Well, I've never completely understood printf (and I don't think I'll be 
 sad
 when if it becomes obsolete).

printf is just so useful...
 0 0
 4294967295 4294967295

Again, I thought they'd both be 255.

Regan -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
Jul 05 2004
parent reply Arcane Jill <Arcane_member pathlink.com> writes:
In article <opsanm6fgc5a2sq9 digitalmars.com>, Regan Heath says...
 Well, I've never completely understood printf (and I don't think I'll be 
 sad
 when if it becomes obsolete).

printf is just so useful...

Right, so the only way it can ever become obsolete is if something even better comes along. So if that happens, I won't be sad either. You wouldn't exactly be losing a function - just gaining another one. Jill
Jul 05 2004
parent Regan Heath <regan netwin.co.nz> writes:
On Mon, 5 Jul 2004 21:04:23 +0000 (UTC), Arcane Jill 
<Arcane_member pathlink.com> wrote:

 In article <opsanm6fgc5a2sq9 digitalmars.com>, Regan Heath says...
 Well, I've never completely understood printf (and I don't think I'll 
 be
 sad
 when if it becomes obsolete).

printf is just so useful...

Right, so the only way it can ever become obsolete is if something even better comes along. So if that happens, I won't be sad either. You wouldn't exactly be losing a function - just gaining another one.

Yeah, I know.. the posts on the new printf seem promising. IMO TypeInfo deficiencies are all that are holding it back. Regan. -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
Jul 05 2004