diff --git a/Foundation/CPDecimalNumber.j b/Foundation/CPDecimalNumber.j index 692c37742..8773fe5d1 100644 --- a/Foundation/CPDecimalNumber.j +++ b/Foundation/CPDecimalNumber.j @@ -234,11 +234,13 @@ var CPDefaultDcmHandler = [CPDecimalNumberHandler decimalNumberHandlerWithRoundi } // initializers +/*! + Initialise a CPDecimalNumber object with NaN + @return the reference to the receiver CPDecimalNumber +*/ - (id)init { - self = [super init]; - - return self; + return [self initWithDecimal:CPDecimalMakeNaN()]; } /*! @@ -248,7 +250,7 @@ var CPDefaultDcmHandler = [CPDecimalNumberHandler decimalNumberHandlerWithRoundi */ - (id)initWithDecimal:(CPDecimal)dcm { - if (self = [self init]) + if (self = [super init]) _data = CPDecimalCopy(dcm); return self; @@ -256,8 +258,11 @@ var CPDefaultDcmHandler = [CPDecimalNumberHandler decimalNumberHandlerWithRoundi /*! Initialise a CPDecimalNumber object with the given mantissa and exponent. - Note that since 'long long' doesn't exist in JS the mantissa is smaller - than possible in Cocoa. + Note: that since 'long long' doesn't exist in JS the mantissa is smaller + than possible in Cocoa and can thus not create the full number range + possible for a CPDecimal. Also note that at extreme cases where overflow + or truncation will occur to the parameters in Cocoa this method produces + different results to its Cocoa counterpart. @param mantissa the mantissa of the decimal number @param exponent the exponent of the number @param flag true if number is negative @@ -276,39 +281,82 @@ var CPDefaultDcmHandler = [CPDecimalNumberHandler decimalNumberHandlerWithRoundi return self; } +/*! + Initialise a CPDecimalNumber with a string. If the string is badly formed + or outside of the acceptable range of a CPDecimal then the number is + initialised to NaN. + @param numberValue the string to parse. + @return the reference to the receiver CPDecimalNumber +*/ - (id)initWithString:(CPString)numberValue { return [self initWithString:numberValue locale:nil]; } +/*! + Initialise a CPDecimalNumber with a string using the given locale. If the + string is badly formed or outside of the acceptable range of a CPDecimal + then the number is initialised to NaN. NOTE: Locales are currently + not supported. + @param numberValue the string to parse + @param locale the CPLocale object to use when parsing the number string + @return the reference to the receiver CPDecimalNumber +*/ - (id)initWithString:(CPString)numberValue locale:(CPDictionary)locale { if (self = [self init]) { - _data = CPDecimalMakeWithString(numberValue,locale); - if (!_data) - [CPException raise:CPInvalidArgumentException reason:"A CPDecimalNumber has been passed an invalid string. '" + numberValue + "'"]; + _data = CPDecimalMakeWithString(numberValue, locale); } return self; } // class methods +/*! + Return a new CPDecimalNumber object with the contents of a CPDecimal object + @param dcm the CPDecimal object to copy + @return the new CPDecimalNumber object +*/ + (CPDecimalNumber)decimalNumberWithDecimal:(CPDecimal)dcm { return [[self alloc] initWithDecimal:dcm]; } +/*! + Create a new CPDecimalNumber object with the given mantissa and exponent. + See \c -initWithMantissa:exponent:isNegative: for some extra notes. + @param mantissa the mantissa of the decimal number + @param exponent the exponent of the number + @param flag true if number is negative + @return the new CPDecimalNumber object +*/ + (CPDecimalNumber)decimalNumberWithMantissa:(unsigned long long)mantissa exponent:(short)exponent isNegative:(BOOL)flag { return [[self alloc] initWithMantissa:mantissa exponent:exponent isNegative:flag]; } +/*! + Create a new CPDecimalNumber with a string. If the string is badly formed + or outside of the acceptable range of a CPDecimal then the number is + initialised to NaN. + @param numberValue the string to parse. + @return the new CPDecimalNumber object +*/ + (CPDecimalNumber)decimalNumberWithString:(CPString)numberValue { return [[self alloc] initWithString:numberValue]; } +/*! + Create a new CPDecimalNumber with a string using the given locale. If the + string is badly formed or outside of the acceptable range of a CPDecimal + then the number is initialised to NaN. NOTE: Locales are currently + not supported. + @param numberValue the string to parse + @param locale the CPLocale object to use when parsing the number string + @return the new CPDecimalNumber object +*/ + (CPDecimalNumber)decimalNumberWithString:(CPString)numberValue locale:(CPDictionary)locale { return [[self alloc] initWithString:numberValue locale:locale]; @@ -319,11 +367,19 @@ var CPDefaultDcmHandler = [CPDecimalNumberHandler decimalNumberHandlerWithRoundi return CPDefaultDcmHandler; } +/*! + 340282366920938463463374607431768211455e127 + 99999999999999999999999999999999999999e127 +*/ + (CPDecimalNumber)maximumDecimalNumber { return [[self alloc] initWithDecimal:_CPDecimalMakeMaximum()]; } +/*! + -340282366920938463463374607431768211455e127 + -99999999999999999999999999999999999999e127 +*/ + (CPDecimalNumber)minimumDecimalNumber { return [[self alloc] initWithDecimal:_CPDecimalMakeMinimum()]; diff --git a/Tests/Foundation/CPDecimalNumberTest.j b/Tests/Foundation/CPDecimalNumberTest.j index 844a1cecf..83b768086 100644 --- a/Tests/Foundation/CPDecimalNumberTest.j +++ b/Tests/Foundation/CPDecimalNumberTest.j @@ -108,7 +108,7 @@ dcmn = [CPDecimalNumber minimumDecimalNumber]; dcm = [dcmn decimalValue]; - [self assert:CPDecimalMinExponent equals:dcm._exponent message:"minimumDecimalNumber: - exponent"]; + [self assert:CPDecimalMaxExponent equals:dcm._exponent message:"minimumDecimalNumber: - exponent"]; [self assert:[9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9] equals:dcm._mantissa message:"minimumDecimalNumber: - mantissa"]; [self assert:YES equals:dcm._isNegative message:"minimumDecimalNumber: - sign"]; @@ -139,63 +139,26 @@ dcm = [dcmn boolValue]; [self assert:true equals:dcm message:"boolValue: - should be true"]; - // exceptions - try { - dcmn = [[CPDecimalNumber alloc] initWithString:@"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"]; - [self fail:"initWithString: TEX1: overflow from string"]; - } catch (e) - { - if ((e.isa) && [e name] == AssertionFailedError) - throw e; - } - try { - dcmn = [[CPDecimalNumber alloc] initWithString:@"foo"]; - [self fail:"initWithString: TEX2: invalid string"]; - } catch (e) - { - if ((e.isa) && [e name] == AssertionFailedError) - throw e; - } - try { - dcmn = [[CPDecimalNumber alloc] initWithString:@".123"]; - [self fail:"initWithString: TEX3: invalid string"]; - } catch (e) - { - if ((e.isa) && [e name] == AssertionFailedError) - throw e; - } - try { - dcmn = [[CPDecimalNumber alloc] initWithString:@"-.123"]; - [self fail:"initWithString: TEX4: invalid string"]; - } catch (e) - { - if ((e.isa) && [e name] == AssertionFailedError) - throw e; - } - try { - dcmn = [[CPDecimalNumber alloc] initWithString:@"0123"]; - [self fail:"initWithString: TEX5: invalid string"]; - } catch (e) - { - if ((e.isa) && [e name] == AssertionFailedError) - throw e; - } - try { - dcmn = [[CPDecimalNumber alloc] initWithString:@"1e200"]; - [self fail:"initWithString: TEX6: exponent overflow"]; - } catch (e) - { - if ((e.isa) && [e name] == AssertionFailedError) - throw e; - } - try{ - dcmn = [[CPDecimalNumber alloc] initWithString:@"12312e-23421"]; - [self fail:"initWithString: TEX7: exponent underflow"]; - } catch (e) - { - if ((e.isa) && [e name] == AssertionFailedError) - throw e; - } + dcmn = [[CPDecimalNumber alloc] initWithString:@"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"]; + [self assertTrue:[dcm compare:[CPDecimalNumber notANumber]] message:"initWithString: overflow should return NaN"]; + + dcmn = [[CPDecimalNumber alloc] initWithString:@"foo"]; + [self assertTrue:[dcm compare:[CPDecimalNumber notANumber]] message:"initWithString: overflow should return NaN"]; + + dcmn = [[CPDecimalNumber alloc] initWithString:@".123"]; + [self assertTrue:[dcm compare:[CPDecimalNumber notANumber]] message:"initWithString: overflow should return NaN"]; + + dcmn = [[CPDecimalNumber alloc] initWithString:@"-.123"]; + [self assertTrue:[dcm compare:[CPDecimalNumber notANumber]] message:"initWithString: overflow should return NaN"]; + + dcmn = [[CPDecimalNumber alloc] initWithString:@"0123"]; + [self assertTrue:[dcm compare:[CPDecimalNumber notANumber]] message:"initWithString: overflow should return NaN"]; + + dcmn = [[CPDecimalNumber alloc] initWithString:@"1e200"]; + [self assertTrue:[dcm compare:[CPDecimalNumber notANumber]] message:"initWithString: overflow should return NaN"]; + + dcmn = [[CPDecimalNumber alloc] initWithString:@"12312e-23421"]; + [self assertTrue:[dcm compare:[CPDecimalNumber notANumber]] message:"initWithString: overflow should return NaN"]; } - (void)testAdd diff --git a/Tests/Foundation/CPDecimalTest.j b/Tests/Foundation/CPDecimalTest.j index 2678122e7..3ff6a1032 100644 --- a/Tests/Foundation/CPDecimalTest.j +++ b/Tests/Foundation/CPDecimalTest.j @@ -68,27 +68,42 @@ [self assert:NO equals:dcm._isNaN message:"CPDecimalMakeWithString() Tf4: NaN is incorrectly set"]; dcm = CPDecimalMakeWithString(@"000000000000000000"); - [self assertNull:dcm message:"CPDecimalMakeWithString() Tf5: Should be invalid"]; + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Tf5: Should be invalid"]; - // too large return nil - [self assertNull:CPDecimalMakeWithString(@"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111") message:"CPDecimalMakeWithString() To1: number overflow handling"]; - [self assertNull:CPDecimalMakeWithString(@"-1e1000") message:"CPDecimalMakeWithString() To2: exponent overflow not caught"]; - [self assertNull:CPDecimalMakeWithString(@"-1e-2342") message:"CPDecimalMakeWithString() To3: exponent underflow not caught"]; + // too large return NaN + dcm = CPDecimalMakeWithString(@"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() To1: number overflow handling. Should return NaN"]; + dcm =CPDecimalMakeWithString(@"-1e1000"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() To2: exponent overflow not caught. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"-1e-2342"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() To3: exponent underflow not caught. Should return NaN"]; // Tests for invalid strings - [self assertNull:CPDecimalMakeWithString(@"abc") message:"CPDecimalMakeWithString() Ti1: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"123a") message:"CPDecimalMakeWithString() Ti2: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"12.7e") message:"CPDecimalMakeWithString() Ti3: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"e") message:"CPDecimalMakeWithString() Ti4: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"12 ") message:"CPDecimalMakeWithString() Ti5: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"1 2 3") message:"CPDecimalMakeWithString() Ti6: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"e10") message:"CPDecimalMakeWithString() Ti7: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"123ee") message:"CPDecimalMakeWithString() Ti8: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"0001") message:"CPDecimalMakeWithString() Ti9: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"-0001") message:"CPDecimalMakeWithString() Ti10: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@".123") message:"CPDecimalMakeWithString() Ti11: catch of invalid number string"]; - [self assertNull:CPDecimalMakeWithString(@"-.1") message:"CPDecimalMakeWithString() Ti12: catch of invalid number string"]; - + dcm = CPDecimalMakeWithString(@"abc"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti1: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"123a"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti2: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"12.7e"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti3: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"e"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti4: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"12 "); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti5: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"1 2 3"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti6: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"e10"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti7: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"123ee"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti8: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"0001"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti9: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"-0001"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti10: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@".123"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti11: catch of invalid number string. Should return NaN"]; + dcm = CPDecimalMakeWithString(@"-.1"); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithString() Ti12: catch of invalid number string. Should return NaN"]; + //test make with parts dcm = CPDecimalMakeWithParts(10127658,2); [self assert:2 equals:dcm._exponent message:"CPDecimalMakeWithParts() Tmp1: exponent"]; @@ -101,8 +116,10 @@ [self assert:YES equals:dcm._isNegative message:"CPDecimalMakeWithParts() Tmp2: sign"]; [self assert:NO equals:dcm._isNaN message:"CPDecimalMakeWithParts() Tmp2: NaN is incorrectly set"]; - [self assertNull:CPDecimalMakeWithParts(1,10000) message:"CPDecimalMakeWithParts() Tmp3: exponent overflow not caught"]; - [self assertNull:CPDecimalMakeWithParts(-1,-1000) message:"CPDecimalMakeWithParts() Tmp4: exponent underflow not caught"]; + dcm = CPDecimalMakeWithParts(1,10000); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithParts() Tmp3: exponent overflow not caught. Should return NaN"]; + dcm = CPDecimalMakeWithParts(-1,-1000); + [self assertTrue:dcm._isNaN message:"CPDecimalMakeWithParts() Tmp4: exponent underflow not caught. Should return NaN"]; } - (void)testZeros