LongFloat.st
branchjv
changeset 18017 7fef9e17913f
parent 18011 deb0c3355881
parent 14691 ba94000e28b2
child 18024 c0e669065f6d
--- a/LongFloat.st	Wed Jan 23 10:08:55 2013 +0000
+++ b/LongFloat.st	Mon Jan 28 21:53:19 2013 +0000
@@ -38,6 +38,10 @@
  extern errno;
 #endif
 
+#if !defined (WIN32)
+# include <locale.h>
+#endif
+
 #if defined (_AIX)
 # include <float.h>
 #endif
@@ -234,46 +238,46 @@
     (thus, longFloat code is not guaranteed to be portable).
 
     NO GARANTY:
-        on systems which do not support 'long doubles', LongFloats are (silently)
-        represented as 'doubles'.
+	on systems which do not support 'long doubles', LongFloats are (silently)
+	represented as 'doubles'.
 
     Representation:
-        gcc-i386:
-            80bit extended IEE floats stored in in 96bits (12bytes);
-            64 bit mantissa,
-            16 bit exponent,
-            19 decimal digits (approx)
-
-        borland-i386 (WIN32):
-            80bit extended IEE floats stored in in 80bits (10bytes);
-            64 bit mantissa,
-            16 bit exponent,
-            19 decimal digits (approx)
-
-        gcc-sparc:
-            128bit quad IEE floats (16bytes);
-            112 bit mantissa,
-            16 bit exponent,
-            34 decimal digits (approx)
+	gcc-i386:
+	    80bit extended IEE floats stored in in 96bits (12bytes);
+	    64 bit mantissa,
+	    16 bit exponent,
+	    19 decimal digits (approx)
+
+	borland-i386 (WIN32):
+	    80bit extended IEE floats stored in in 80bits (10bytes);
+	    64 bit mantissa,
+	    16 bit exponent,
+	    19 decimal digits (approx)
+
+	gcc-sparc:
+	    128bit quad IEE floats (16bytes);
+	    112 bit mantissa,
+	    16 bit exponent,
+	    34 decimal digits (approx)
 
     Mixed mode arithmetic:
-        longFloat op longFloat   -> longFloat
-        longFloat op fix         -> longFloat
-        longFloat op fraction    -> longFloat
-        longFloat op integer     -> longFloat
-        longFloat op shortFloat  -> longFloat
-        longFloat op float       -> longFloat
-        longFloat op complex     -> complex
+	longFloat op longFloat   -> longFloat
+	longFloat op fix         -> longFloat
+	longFloat op fraction    -> longFloat
+	longFloat op integer     -> longFloat
+	longFloat op shortFloat  -> longFloat
+	longFloat op float       -> longFloat
+	longFloat op complex     -> complex
 
     Range and Precision of Storage Formats: see LimitedPrecisionReal >> documentation
 
     [author:]
-        Claus Gittinger
+	Claus Gittinger
 
     [see also:]
-        Number
-        Float ShortFloat Fraction FixedPoint Integer Complex
-        FloatArray DoubleArray
+	Number
+	Float ShortFloat Fraction FixedPoint Integer Complex
+	FloatArray DoubleArray
 "
 ! !
 
@@ -403,11 +407,11 @@
     "do not write a literal constant here - we cannot depend on the underlying C-compiler here..."
 
     Pi isNil ifTrue:[
-        DefaultPrintFormat := '.19'.  "/ 19 valid digits
-
-        "/ enough digits for 128bit IEEE quads
-        Pi := self readFrom:'3.1415926535897932384626433832795029'. "/ 3.14159265358979323846264338327950288419716939937510582097494459q
-        E  := self readFrom:'2.7182818284590452353602874713526625'.
+	DefaultPrintFormat := '.19'.  "/ 19 valid digits
+
+	"/ enough digits for 128bit IEEE quads
+	Pi := self readFrom:'3.1415926535897932384626433832795029'. "/ 3.14159265358979323846264338327950288419716939937510582097494459q
+	E  := self readFrom:'2.7182818284590452353602874713526625'.
     ].
 
     "
@@ -595,25 +599,25 @@
     LONGFLOAT result, val;
 
     if (__isSmallInteger(aNumber)) {
-        val = (LONGFLOAT)(__intVal(aNumber));
+	val = (LONGFLOAT)(__intVal(aNumber));
 doMul:
-        result = __longFloatVal(self) * val;
-        __qMKLFLOAT(newFloat, result);
-        RETURN ( newFloat );
+	result = __longFloatVal(self) * val;
+	__qMKLFLOAT(newFloat, result);
+	RETURN ( newFloat );
     }
     if (aNumber != nil) {
-        if (__qIsLongFloat(aNumber)) {
-            val = __longFloatVal(aNumber);
-            goto doMul;
-        }
-        if (__qIsFloatLike(aNumber)) {
-            val = (LONGFLOAT)(__floatVal(aNumber));
-            goto doMul;
-        }
-        if (__qIsShortFloat(aNumber)) {
-            val = (LONGFLOAT)(__shortFloatVal(aNumber));
-            goto doMul;
-        }
+	if (__qIsLongFloat(aNumber)) {
+	    val = __longFloatVal(aNumber);
+	    goto doMul;
+	}
+	if (__qIsFloatLike(aNumber)) {
+	    val = (LONGFLOAT)(__floatVal(aNumber));
+	    goto doMul;
+	}
+	if (__qIsShortFloat(aNumber)) {
+	    val = (LONGFLOAT)(__shortFloatVal(aNumber));
+	    goto doMul;
+	}
     }
 %}.
     ^ aNumber productFromLongFloat:self
@@ -628,25 +632,25 @@
     LONGFLOAT result, val;
 
     if (__isSmallInteger(aNumber)) {
-        val = (LONGFLOAT)(__intVal(aNumber));
+	val = (LONGFLOAT)(__intVal(aNumber));
 doAdd:
-        result = __longFloatVal(self) + val;
-        __qMKLFLOAT(newFloat, result);
-        RETURN ( newFloat );
+	result = __longFloatVal(self) + val;
+	__qMKLFLOAT(newFloat, result);
+	RETURN ( newFloat );
     }
     if (aNumber != nil) {
-        if (__qIsLongFloat(aNumber)) {
-            val = __longFloatVal(aNumber);
-            goto doAdd;
-        }
-        if (__qIsFloatLike(aNumber)) {
-            val = (LONGFLOAT)(__floatVal(aNumber));
-            goto doAdd;
-        }
-        if (__qIsShortFloat(aNumber)) {
-            val = (LONGFLOAT)(__shortFloatVal(aNumber));
-            goto doAdd;
-        }
+	if (__qIsLongFloat(aNumber)) {
+	    val = __longFloatVal(aNumber);
+	    goto doAdd;
+	}
+	if (__qIsFloatLike(aNumber)) {
+	    val = (LONGFLOAT)(__floatVal(aNumber));
+	    goto doAdd;
+	}
+	if (__qIsShortFloat(aNumber)) {
+	    val = (LONGFLOAT)(__shortFloatVal(aNumber));
+	    goto doAdd;
+	}
     }
 %}.
     ^ aNumber sumFromLongFloat:self
@@ -661,25 +665,25 @@
     LONGFLOAT result, val;
 
     if (__isSmallInteger(aNumber)) {
-        val = (LONGFLOAT)(__intVal(aNumber));
+	val = (LONGFLOAT)(__intVal(aNumber));
 doSub:
-        result = __longFloatVal(self) - val;
-        __qMKLFLOAT(newFloat, result);
-        RETURN ( newFloat );
+	result = __longFloatVal(self) - val;
+	__qMKLFLOAT(newFloat, result);
+	RETURN ( newFloat );
     }
     if (aNumber != nil) {
-        if (__qIsLongFloat(aNumber)) {
-            val = __longFloatVal(aNumber);
-            goto doSub;
-        }
-        if (__qIsFloatLike(aNumber)) {
-            val = (LONGFLOAT)(__floatVal(aNumber));
-            goto doSub;
-        }
-        if (__qIsShortFloat(aNumber)) {
-            val = (LONGFLOAT)(__shortFloatVal(aNumber));
-            goto doSub;
-        }
+	if (__qIsLongFloat(aNumber)) {
+	    val = __longFloatVal(aNumber);
+	    goto doSub;
+	}
+	if (__qIsFloatLike(aNumber)) {
+	    val = (LONGFLOAT)(__floatVal(aNumber));
+	    goto doSub;
+	}
+	if (__qIsShortFloat(aNumber)) {
+	    val = (LONGFLOAT)(__shortFloatVal(aNumber));
+	    goto doSub;
+	}
     }
 %}.
     ^ aNumber differenceFromLongFloat:self
@@ -694,39 +698,86 @@
     LONGFLOAT result, val;
 
     if (__isSmallInteger(aNumber)) {
-        val = (LONGFLOAT)(__intVal(aNumber));
+	val = (LONGFLOAT)(__intVal(aNumber));
 doDiv:
-        if (val == 0.0) goto badArg;
-
-        result = __longFloatVal(self) / val;
-        __qMKLFLOAT(newFloat, result);
-        RETURN ( newFloat );
+	if (val == 0.0) goto badArg;
+
+	result = __longFloatVal(self) / val;
+	__qMKLFLOAT(newFloat, result);
+	RETURN ( newFloat );
     }
     if (aNumber != nil) {
-        if (__qIsLongFloat(aNumber)) {
-            val = __longFloatVal(aNumber);
-            goto doDiv;
-        }
-        if (__qIsFloatLike(aNumber)) {
-            val = (LONGFLOAT)(__floatVal(aNumber));
-            goto doDiv;
-        }
-        if (__qIsShortFloat(aNumber)) {
-            val = (LONGFLOAT)(__shortFloatVal(aNumber));
-            goto doDiv;
-        }
+	if (__qIsLongFloat(aNumber)) {
+	    val = __longFloatVal(aNumber);
+	    goto doDiv;
+	}
+	if (__qIsFloatLike(aNumber)) {
+	    val = (LONGFLOAT)(__floatVal(aNumber));
+	    goto doDiv;
+	}
+	if (__qIsShortFloat(aNumber)) {
+	    val = (LONGFLOAT)(__shortFloatVal(aNumber));
+	    goto doDiv;
+	}
     }
 badArg: ;
 %}.
     ((aNumber == 0) or:[aNumber = 0.0]) ifTrue:[
-        "
-         No, you shalt not divide by zero
-        "
-        ^ ZeroDivide raiseRequestWith:thisContext.
+	"
+	 No, you shalt not divide by zero
+	"
+	^ ZeroDivide raiseRequestWith:thisContext.
     ].
     ^ aNumber quotientFromLongFloat:self
 !
 
+rem: aNumber
+    "return the floating point remainder of the receiver and the argument, aNumber"
+
+%{  /* NOCONTEXT */
+
+    /*
+     * notice:
+     * the following inline code handles some common cases,
+     * and exists as an optimization, to speed up those cases.
+     *
+     * Conceptionally, (and for most other argument types),
+     * mixed arithmetic is implemented by double dispatching
+     * (see the message send at the bottom)
+     */
+    OBJ newFloat;
+    LONGFLOAT result, val;
+
+    if (__isSmallInteger(aNumber)) {
+	if (aNumber != __mkSmallInteger(0)) {
+	    val = (LONGFLOAT)__intVal(aNumber);
+	    if (val == 0.0) goto badArg;
+computeResult:
+	    result = fmodl(__longFloatVal(self), val) ;
+	    __qMKLFLOAT(newFloat, result);
+	    RETURN ( newFloat );
+	}
+    } else if (__isFloatLike(aNumber)) {
+	val = (LONGFLOAT)__floatVal(aNumber);
+	goto computeResult;
+    } else if (__isShortFloat(aNumber)) {
+	val = (LONGFLOAT)__shortFloatVal(aNumber);
+	goto computeResult;
+    } else if (__isLongFloat(aNumber)) {
+	val = __longFloatVal(aNumber);
+	goto computeResult;
+    }
+badArg: ;
+%}.
+    ((aNumber == 0) or:[aNumber = 0.0]) ifTrue:[
+	"
+	 No, you shalt not divide by zero
+	"
+	^ ZeroDivide raiseRequestWith:thisContext.
+    ].
+    ^ aNumber remainderFromLongFloat:self
+!
+
 abs
     "return the absolute value of the receiver
      reimplemented here for speed"
@@ -774,25 +825,25 @@
     LONGFLOAT result, val;
 
     if (__isSmallInteger(aNumber)) {
-        val = (LONGFLOAT)(__intVal(aNumber));
+	val = (LONGFLOAT)(__intVal(aNumber));
 doDiv:
-        result = __longFloatVal(self) / val;
-        __qMKLFLOAT(newFloat, result);
-        RETURN ( newFloat );
+	result = __longFloatVal(self) / val;
+	__qMKLFLOAT(newFloat, result);
+	RETURN ( newFloat );
     }
     if (aNumber != nil) {
-        if (__qIsLongFloat(aNumber)) {
-            val = __longFloatVal(aNumber);
-            goto doDiv;
-        }
-        if (__qIsShortFloat(aNumber)) {
-            val = (LONGFLOAT)(__shortFloatVal(aNumber));
-            goto doDiv;
-        }
-        if (__qIsFloatLike(aNumber)) {
-            val = (LONGFLOAT)(__floatVal(aNumber));
-            goto doDiv;
-        }
+	if (__qIsLongFloat(aNumber)) {
+	    val = __longFloatVal(aNumber);
+	    goto doDiv;
+	}
+	if (__qIsShortFloat(aNumber)) {
+	    val = (LONGFLOAT)(__shortFloatVal(aNumber));
+	    goto doDiv;
+	}
+	if (__qIsFloatLike(aNumber)) {
+	    val = (LONGFLOAT)(__floatVal(aNumber));
+	    goto doDiv;
+	}
     }
 %}.
     ^ aNumber quotientFromLongFloat:self
@@ -1341,9 +1392,9 @@
     int len ;
 
     if (__isStringLike(@global(DefaultPrintFormat))) {
-        fmt = (char *) __stringVal(@global(DefaultPrintFormat));
+	fmt = (char *) __stringVal(@global(DefaultPrintFormat));
     } else {
-        fmt = ".19";
+	fmt = ".19";
     }
     /*
      * build a printf format string
@@ -1352,12 +1403,12 @@
     strncpy(fmtBuffer+1, fmt, 10);
     if (sizeof(LONGFLOAT) == sizeof(double)) {
 #ifdef SYSV
-        strcat(fmtBuffer, "lg");
+	strcat(fmtBuffer, "lg");
 #else
-        strcat(fmtBuffer, "G");
+	strcat(fmtBuffer, "G");
 #endif
     } else {
-        strcat(fmtBuffer, "LG");
+	strcat(fmtBuffer, "LG");
     }
 
     /*
@@ -1370,33 +1421,33 @@
     __END_PROTECT_REGISTERS__
 
     if (len >= 0 && len < sizeof(buffer)-3) {
-        /*
-         * kludge to make integral float f prints as "f.0" (not as "f" as printf does)
-         * (i.e. look if string contains '.' or 'e' and append '.0' if not)
-         */
-        for (cp = buffer; *cp; cp++) {
-            if ((*cp == '.') || (*cp == ',') || (*cp == 'E') || (*cp == 'e')) break;
-        }
-        if (!*cp && (cp[-1] >= '0') && (cp[-1] <= '9')) {
-            if (__isCharacter(@global(DecimalPointCharacterForPrinting))) {
-                *cp++ = __intVal(__characterVal(@global(DecimalPointCharacterForPrinting)));
-            } else {
-                *cp++ = '.';
-            }
-            *cp++ = '0';
-            *cp = '\0';
-        } else {
-            if (cp && (*cp == '.')) {
-                if (__isCharacter(@global(DecimalPointCharacterForPrinting))) {
-                    *cp = __intVal(__characterVal(@global(DecimalPointCharacterForPrinting)));
-                }
-            }
-        }
-
-        s = __MKSTRING(buffer);
-        if (s != nil) {
-            RETURN (s);
-        }
+	/*
+	 * kludge to make integral float f prints as "f.0" (not as "f" as printf does)
+	 * (i.e. look if string contains '.' or 'e' and append '.0' if not)
+	 */
+	for (cp = buffer; *cp; cp++) {
+	    if ((*cp == '.') || (*cp == ',') || (*cp == 'E') || (*cp == 'e')) break;
+	}
+	if (!*cp && (cp[-1] >= '0') && (cp[-1] <= '9')) {
+	    if (__isCharacter(@global(DecimalPointCharacterForPrinting))) {
+		*cp++ = __intVal(__characterVal(@global(DecimalPointCharacterForPrinting)));
+	    } else {
+		*cp++ = '.';
+	    }
+	    *cp++ = '0';
+	    *cp = '\0';
+	} else {
+	    if (cp && (*cp == '.')) {
+		if (__isCharacter(@global(DecimalPointCharacterForPrinting))) {
+		    *cp = __intVal(__characterVal(@global(DecimalPointCharacterForPrinting)));
+		}
+	    }
+	}
+
+	s = __MKSTRING(buffer);
+	if (s != nil) {
+	    RETURN (s);
+	}
     }
 %}.
     "
@@ -1409,24 +1460,24 @@
     ^ ObjectMemory allocationFailureSignal raise.
 
     "
-        1.234 asLongFloat printString.
-        1.0 asLongFloat printString.
-        1e10 asLongFloat printString.
-        1.2e3 asLongFloat printString.
-        1.2e30 asLongFloat printString.
-        (1.0 uncheckedDivide:0) asLongFloat printString.
-        (0.0 uncheckedDivide:0) asLongFloat printString.
-        self pi printString.
-
-        DecimalPointCharacterForPrinting := $,.
-        1.234 asLongFloat printString.
-        1.0 asLongFloat printString.
-        1e10 asLongFloat printString.
-        1.2e3 asLongFloat printString.
-        1.2e30 asLongFloat printString.
-        (1.0 uncheckedDivide:0) asLongFloat printString.
-        (0.0 uncheckedDivide:0) asLongFloat printString.
-        DecimalPointCharacterForPrinting := $.
+	1.234 asLongFloat printString.
+	1.0 asLongFloat printString.
+	1e10 asLongFloat printString.
+	1.2e3 asLongFloat printString.
+	1.2e30 asLongFloat printString.
+	(1.0 uncheckedDivide:0) asLongFloat printString.
+	(0.0 uncheckedDivide:0) asLongFloat printString.
+	self pi printString.
+
+	DecimalPointCharacterForPrinting := $,.
+	1.234 asLongFloat printString.
+	1.0 asLongFloat printString.
+	1e10 asLongFloat printString.
+	1.2e3 asLongFloat printString.
+	1.2e30 asLongFloat printString.
+	(1.0 uncheckedDivide:0) asLongFloat printString.
+	(0.0 uncheckedDivide:0) asLongFloat printString.
+	DecimalPointCharacterForPrinting := $.
     "
 !
 
@@ -1447,23 +1498,23 @@
     int len;
 
     if (__isStringLike(formatString)) {
-        /*
-         * actually only needed on sparc: since thisContext is
-         * in a global register, which gets destroyed by printf,
-         * manually save it here - very stupid ...
-         */
-        __BEGIN_PROTECT_REGISTERS__
-
-        len = snprintf(buffer, sizeof(buffer), __stringVal(formatString), __longFloatVal(self));
-
-        __END_PROTECT_REGISTERS__
-
-        if (len < 0) goto fail;
-
-        s = __MKSTRING_L(buffer, len);
-        if (s != nil) {
-            RETURN (s);
-        }
+	/*
+	 * actually only needed on sparc: since thisContext is
+	 * in a global register, which gets destroyed by printf,
+	 * manually save it here - very stupid ...
+	 */
+	__BEGIN_PROTECT_REGISTERS__
+
+	len = snprintf(buffer, sizeof(buffer), __stringVal(formatString), __longFloatVal(self));
+
+	__END_PROTECT_REGISTERS__
+
+	if (len < 0) goto fail;
+
+	s = __MKSTRING_L(buffer, len);
+	if (s != nil) {
+	    RETURN (s);
+	}
     }
 fail: ;
 %}.
@@ -1491,41 +1542,41 @@
     int len;
     char *fmtBuffer;
 
-    if (sizeof(LONGFLOAT) == sizeof(double)) {
-#ifdef SYSV
-        fmtBuffer = "%.19lg";
-#else
-        fmtBuffer = "%.19G";
-#endif
-    } else {
-        fmtBuffer = "%.19LG";
-    }
     /*
      * build a printf format string
      */
+    if (sizeof(LONGFLOAT) == sizeof(double)) {
+#ifdef SYSV
+	fmtBuffer = "%.19lg";
+#else
+	fmtBuffer = "%.19G";
+#endif
+    } else {
+	fmtBuffer = "%.19LG";
+    }
 
     __BEGIN_PROTECT_REGISTERS__
     len = snprintf(buffer, sizeof(buffer), fmtBuffer, __longFloatVal(self));
     __END_PROTECT_REGISTERS__
 
     if (len >= 0 && len < sizeof(buffer)-3) {
-        /*
-         * kludge to make integral float f prints as "f.0" (not as "f" as printf does)
-         * (i.e. look if string contains '.' or 'e' and append '.0' if not)
-         */
-        for (cp = buffer; *cp; cp++) {
-            if ((*cp == '.') || (*cp == ',') || (*cp == 'E') || (*cp == 'e')) break;
-        }
-        if (!*cp && (cp[-1] >= '0') && (cp[-1] <= '9')) {
-            *cp++ = '.';
-            *cp++ = '0';
-            *cp = '\0';
-        }
-
-        s = __MKSTRING(buffer);
-        if (s != nil) {
-            RETURN (s);
-        }
+	/*
+	 * kludge to make integral float f prints as "f.0" (not as "f" as printf does)
+	 * (i.e. look if string contains '.' or 'e' and append '.0' if not)
+	 */
+	for (cp = buffer; *cp; cp++) {
+	    if ((*cp == '.') || (*cp == ',') || (*cp == 'E') || (*cp == 'e')) break;
+	}
+	if (!*cp && (cp[-1] >= '0') && (cp[-1] <= '9')) {
+	    *cp++ = '.';
+	    *cp++ = '0';
+	    *cp = '\0';
+	}
+
+	s = __MKSTRING(buffer);
+	if (s != nil) {
+	    RETURN (s);
+	}
     }
 %}.
     "
@@ -1538,26 +1589,26 @@
     ^ ObjectMemory allocationFailureSignal raise.
 
     "
-        1.0 asLongFloat storeString
-        1.234 asLongFloat storeString
-        1e10 asLongFloat storeString
-        1.2e3 asLongFloat storeString
-        1.2e30 asLongFloat storeString
-        LongFloat pi asLongFloat storeString
-        (1.0 uncheckedDivide:0) asLongFloat storeString
-        (0.0 uncheckedDivide:0) asLongFloat storeString
+	1.0 asLongFloat storeString
+	1.234 asLongFloat storeString
+	1e10 asLongFloat storeString
+	1.2e3 asLongFloat storeString
+	1.2e30 asLongFloat storeString
+	LongFloat pi asLongFloat storeString
+	(1.0 uncheckedDivide:0) asLongFloat storeString
+	(0.0 uncheckedDivide:0) asLongFloat storeString
 
      notice that the storeString is NOT affected by DecimalPointCharacterForPrinting:
 
-        DecimalPointCharacterForPrinting := $,.
-        1.234 asLongFloat storeString.
-        1.0 asLongFloat storeString.
-        1e10 asLongFloat storeString.
-        1.2e3 asLongFloat storeString.
-        1.2e30 asLongFloat storeString.
-        (1.0 uncheckedDivide:0) asLongFloat storeString.
-        (0.0 uncheckedDivide:0) asLongFloat storeString.
-        DecimalPointCharacterForPrinting := $.
+	DecimalPointCharacterForPrinting := $,.
+	1.234 asLongFloat storeString.
+	1.0 asLongFloat storeString.
+	1e10 asLongFloat storeString.
+	1.2e3 asLongFloat storeString.
+	1.2e30 asLongFloat storeString.
+	(1.0 uncheckedDivide:0) asLongFloat storeString.
+	(0.0 uncheckedDivide:0) asLongFloat storeString.
+	DecimalPointCharacterForPrinting := $.
     "
 ! !
 
@@ -2540,11 +2591,11 @@
 !LongFloat class methodsFor:'documentation'!
 
 version
-    ^ '$Header: /cvs/stx/stx/libbasic/LongFloat.st,v 1.71 2012-12-17 18:28:24 stefan Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic/LongFloat.st,v 1.73 2013-01-23 18:02:20 cg Exp $'
 !
 
 version_CVS
-    ^ '$Header: /cvs/stx/stx/libbasic/LongFloat.st,v 1.71 2012-12-17 18:28:24 stefan Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic/LongFloat.st,v 1.73 2013-01-23 18:02:20 cg Exp $'
 ! !