From e836373d9b8989ec3fd565c1536206906867249f Mon Sep 17 00:00:00 2001 From: GregoryTravis Date: Wed, 14 Aug 2024 12:45:28 -0400 Subject: [PATCH] Mixed Decimal/Float operations throw error or attach warning (#10725) --- CHANGELOG.md | 3 + .../Base/0.0.0-dev/src/Data/Decimal.enso | 262 ++++++++++++++---- .../Numeric/Internal/Decimal_Internal.enso | 3 + .../Table/0.0.0-dev/src/Internal/Storage.enso | 5 +- .../builtin/meta/EqualsAtomNode.java | 42 ++- .../src/Comparator_Spec.enso | 19 ++ .../src/Decimal_Constructor_Spec.enso | 27 ++ test/Base_Internal_Tests/src/Main.enso | 4 +- test/Base_Tests/src/Data/Decimal_Spec.enso | 248 +++++++++-------- 9 files changed, 440 insertions(+), 173 deletions(-) create mode 100644 test/Base_Internal_Tests/src/Decimal_Constructor_Spec.enso diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c6ca9869..8d63e85190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,14 @@ Cloud.][10660] - [Added Newline option to Text_Cleanse/Text_Replace.][10761] - [Support for reading from Tableau Hyper files.][10733] +- [Mixed Decimal/Float arithmetic now throws an error; mixed comparisons now + attach warnings.][10725] [10614]: https://github.com/enso-org/enso/pull/10614 [10660]: https://github.com/enso-org/enso/pull/10660 [10761]: https://github.com/enso-org/enso/pull/10761 [10733]: https://github.com/enso-org/enso/pull/10733 +[10725]: https://github.com/enso-org/enso/pull/10725 # Enso 2023.3 diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Decimal.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Decimal.enso index 90ab71dabc..e021caf556 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Decimal.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Decimal.enso @@ -80,7 +80,7 @@ polyglot java import org.enso.base.numeric.Decimal_Utils named methods, but which cannot take a `Math_Context`. In the case of `divide`, it is possible that the result will have a - non-terminating deicmal expansion. If the operation did not specify a + non-terminating decimal expansion. If the operation did not specify a `Math_Context`, or specified an explicit `Math_Context` with infinite precision, then it is impossible to represent the result as requested, and an `Arithmetic_Error` will be thrown. In this case, the solution is to specify @@ -89,6 +89,9 @@ type Decimal ## PRIVATE Value (big_decimal : BigDecimal) + ## PRIVATE + From_Float (big_decimal : BigDecimal) (original_value : Float) + ## ICON input_number Construct a `Decimal` from a `Text`, `Integer` or `Float`. @@ -103,35 +106,68 @@ type Decimal The textual format for a Decimal is defined at https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html#BigDecimal-java.lang.String-. - ! Error Conditions + ? Creating `Decimal`s and Converting to `Decimal` - - If the `Text` argument is incorrectly formatted, a `Number_Parse_Error` + When creating a `Decimal` from a literal floating-point value, the + preferred method is to express the literal as a string and use + `Decimal.from_text`, since this will give a `Decimal` that matches the + value precisely. + + To convert a `Float` or `Integer` to a `Decimal`, use its `.to_decimal` + method. + + You can also use the convenience method `dec` to convert any `Integer`, + `Float`, or `Text` value to a `Decimal`. `dec` does not attach a + warning. + + ! Error and Warning Conditions in creating and using `Decimal`s + + - If a `Text` argument is incorrectly formatted, a `Number_Parse_Error` is thrown. - If the construction of the Decimal results in a loss of precision, a - `Loss_Of_Numeric_Precision` warning is attached. This can only happen - if a `Math_Context` value is explicitly passed. + `Loss_Of_Numeric_Precision` warning is attached. This can happen in + the followoing ways: + - A `Float` value is implicitly converted to a `Decimal` (either by + calling `Decimal.from` or by passing the `Float` as a parameter of + type `Decimal`) + - A `Math_Context` value is explicitly passed, and the precision it + specifies is not sufficient to precisely represent the argument to + be converted + - If an arithmetic operation is used on mixed arguments (a `Decimal` + and a `Float`), an `Illegal_Argument` error is thrown. + - If an arithmetic operation is used on a `Decimal` that was implicitly + converted from a `Float` (either by calling `Decimal.from` or by + passing the `Float` as a parameter of type `Decimal`), an + `Illegal_Argument` error is thrown. + - If a floating-poing argument is `NaN` or `+/- Inf`, an + `Illegal_Argument` error is thrown. ^ Example Create a `Decimal` from a `Text`. - c = Decimal.new "12.345" + c = Decimal.from_text "12.345" ^ Example Create a `Decimal` from an `Integer`. - c = Decimal.new 12345 + c = 12345.to_decimal ^ Example Create a `Decimal` from a `Float`. - c = Decimal.new 12.345 + c = 12.345.to_decimal + + ^ Example + Create a `Decimal` from a value. + + c = dec 12.345 new : Text | Integer | Float -> Math_Context | Nothing -> Decimal ! Arithmetic_Error | Number_Parse_Error new (x : Text | Integer | Float) (mc : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error | Number_Parse_Error = handle_java_exception <| case x of _ : Text -> Decimal.from_text x mc _ : Integer -> Decimal.from_integer x mc - _ : Float -> Decimal.from_float x mc + _ : Float -> Decimal.from_float x mc explicit=False ## GROUP Conversions ICON convert @@ -148,17 +184,51 @@ type Decimal The textual format for a Decimal is defined at https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html#BigDecimal-java.lang.String-. - ! Error Conditions + ? Creating `Decimal`s and Converting to `Decimal` - - If `s` is incorrectly formatted, a `Number_Parse_Error` is thrown. + When creating a `Decimal` from a literal floating-point value, the + preferred method is to express the literal as a string and use + `Decimal.from_text`, since this will give a `Decimal` that matches the + value precisely. + + To convert a `Float` or `Integer` to a `Decimal`, use its `.to_decimal` + method. + + You can also use the convenience method `dec` to convert any `Integer`, + `Float`, or `Text` value to a `Decimal`. `dec` does not attach a + warning. + + ! Error and Warning Conditions in creating and using `Decimal`s + + - If a `Text` argument is incorrectly formatted, a `Number_Parse_Error` + is thrown. - If the construction of the Decimal results in a loss of precision, a - `Loss_Of_Numeric_Precision` warning is attached. This can only happen - if a `Math_Context` value is explicitly passed. + `Loss_Of_Numeric_Precision` warning is attached. This can happen in + the followoing ways: + - A `Float` value is implicitly converted to a `Decimal` (either by + calling `Decimal.from` or by passing the `Float` as a parameter of + type `Decimal`) + - A `Math_Context` value is explicitly passed, and the precision it + specifies is not sufficient to precisely represent the argument to + be converted + - If an arithmetic operation is used on mixed arguments (a `Decimal` + and a `Float`), an `Illegal_Argument` error is thrown. + - If an arithmetic operation is used on a `Decimal` that was implicitly + converted from a `Float` (either by calling `Decimal.from` or by + passing the `Float` as a parameter of type `Decimal`), an + `Illegal_Argument` error is thrown. + - If a floating-poing argument is `NaN` or `+/- Inf`, an + `Illegal_Argument` error is thrown. ^ Example Create a `Decimal` from a `Text`. d = Decimal.from_text "12.345" + + ^ Example + Create a `Decimal` from a `Text`. + + d = dec "12.345" from_text : Text -> Math_Context | Nothing -> Decimal ! Number_Parse_Error from_text (s : Text) (mc : Math_Context | Nothing = Nothing) -> Decimal ! Number_Parse_Error = handle_java_exception <| handle_number_format_exception <| @@ -176,16 +246,51 @@ type Decimal If a `Math_Context` is used, there is a possibility of a loss of precision. - ! Error Conditions + ? Creating `Decimal`s and Converting to `Decimal` + When creating a `Decimal` from a literal floating-point value, the + preferred method is to express the literal as a string and use + `Decimal.from_text`, since this will give a `Decimal` that matches the + value precisely. + + To convert a `Float` or `Integer` to a `Decimal`, use its `.to_decimal` + method. + + You can also use the convenience method `dec` to convert any `Integer`, + `Float`, or `Text` value to a `Decimal`. `dec` does not attach a + warning. + + ! Error and Warning Conditions in creating and using `Decimal`s + + - If a `Text` argument is incorrectly formatted, a `Number_Parse_Error` + is thrown. - If the construction of the Decimal results in a loss of precision, a - `Loss_Of_Numeric_Precision` warning is attached. This can only happen - if a `Math_Context` value is explicitly passed. + `Loss_Of_Numeric_Precision` warning is attached. This can happen in + the followoing ways: + - A `Float` value is implicitly converted to a `Decimal` (either by + calling `Decimal.from` or by passing the `Float` as a parameter of + type `Decimal`) + - A `Math_Context` value is explicitly passed, and the precision it + specifies is not sufficient to precisely represent the argument to + be converted + - If an arithmetic operation is used on mixed arguments (a `Decimal` + and a `Float`), an `Illegal_Argument` error is thrown. + - If an arithmetic operation is used on a `Decimal` that was implicitly + converted from a `Float` (either by calling `Decimal.from` or by + passing the `Float` as a parameter of type `Decimal`), an + `Illegal_Argument` error is thrown. + - If a floating-poing argument is `NaN` or `+/- Inf`, an + `Illegal_Argument` error is thrown. ^ Example Create a `Decimal` from an `Integer`. - d = Decimal.from_integer 12 + c = 12345.to_decimal + + ^ Example + Create a `Decimal` from an `Integer`. + + c = dec 12345 from_integer : Integer -> Math_Context | Nothing -> Decimal from_integer (i : Integer) (mc : Math_Context | Nothing = Nothing) -> Decimal = handle_java_exception <| @@ -211,24 +316,61 @@ type Decimal a `Float` always attaches a `Loss_Of_Numeric_Precision` warning to the result. - ! Error Conditions + ? Creating `Decimal`s and Converting to `Decimal` - - A `Loss_Of_Numeric_Precision` warning is always attached when - converting to `Decimal` from `Float`. - - If `f` is NaN or +/-Inf, an Illegal_Argument error is thrown. + When creating a `Decimal` from a literal floating-point value, the + preferred method is to express the literal as a string and use + `Decimal.from_text`, since this will give a `Decimal` that matches the + value precisely. + + To convert a `Float` or `Integer` to a `Decimal`, use its `.to_decimal` + method. + + You can also use the convenience method `dec` to convert any `Integer`, + `Float`, or `Text` value to a `Decimal`. `dec` does not attach a + warning. + + ! Error and Warning Conditions in creating and using `Decimal`s + + - If a `Text` argument is incorrectly formatted, a `Number_Parse_Error` + is thrown. + - If the construction of the Decimal results in a loss of precision, a + `Loss_Of_Numeric_Precision` warning is attached. This can happen in + the followoing ways: + - A `Float` value is implicitly converted to a `Decimal` (either by + calling `Decimal.from` or by passing the `Float` as a parameter of + type `Decimal`) + - A `Math_Context` value is explicitly passed, and the precision it + specifies is not sufficient to precisely represent the argument to + be converted + - If an arithmetic operation is used on mixed arguments (a `Decimal` + and a `Float`), an `Illegal_Argument` error is thrown. + - If an arithmetic operation is used on a `Decimal` that was implicitly + converted from a `Float` (either by calling `Decimal.from` or by + passing the `Float` as a parameter of type `Decimal`), an + `Illegal_Argument` error is thrown. + - If a floating-poing argument is `NaN` or `+/- Inf`, an + `Illegal_Argument` error is thrown. ^ Example Create a `Decimal` from a `Float`. - d = Decimal.from_integer 12.345 - from_float : Float -> Math_Context | Nothing -> Decimal ! Arithmetic_Error | Illegal_Argument - from_float (f : Float) (mc : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error | Illegal_Argument = + c = 12.345.to_decimal + + ^ Example + Create a `Decimal` from a `Float`. + + c = dec 12.345 + from_float : Float -> Math_Context | Nothing -> Boolean -> Decimal ! Arithmetic_Error | Illegal_Argument + from_float (f : Float) (mc : Math_Context | Nothing = Nothing) (explicit : Boolean = True) -> Decimal ! Arithmetic_Error | Illegal_Argument = is_exceptional = f.is_nan || f.is_infinite if is_exceptional then Error.throw (Illegal_Argument.Error "Cannot convert "+f.to_text+" to a Decimal") else - handle_java_exception <| attach_loss_of_numeric_precision f <| - case mc of - _ : Math_Context -> Decimal.Value <| handle_precision_loss f <| Decimal_Utils.fromFloat f mc.math_context - _ : Nothing -> Decimal.Value (Decimal_Utils.fromFloat f) + handle_java_exception <| + big_decimal = case mc of + _ : Math_Context -> handle_precision_loss f <| Decimal_Utils.fromFloat f mc.math_context + _ : Nothing -> Decimal_Utils.fromFloat f + if explicit then Decimal.Value big_decimal else + attach_loss_of_numeric_precision f (Decimal.From_Float big_decimal f) ## ALIAS greater than GROUP Operators @@ -328,9 +470,10 @@ type Decimal b = Decimal.new "20.33" a.add b (Math_Context.new 3) # => Decimal.new 30.5 + # TODO: restore checked return type (here and elsewhere) after https://github.com/enso-org/enso/issues/10736 add : Decimal -> Math_Context | Nothing -> Decimal ! Arithmetic_Error add self (that : Decimal) (math_context : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error = - handle_java_exception <| + error_if_from_float self that <| handle_java_exception <| case math_context of Nothing -> Decimal.Value (self.big_decimal.add that.big_decimal) _ -> Decimal.Value (self.big_decimal.add that.big_decimal math_context.math_context) @@ -388,7 +531,7 @@ type Decimal # => Decimal.new 10.1 subtract : Decimal -> Math_Context | Nothing -> Decimal ! Arithmetic_Error subtract self (that : Decimal) (math_context : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error = - handle_java_exception <| + error_if_from_float self that <| handle_java_exception <| case math_context of Nothing -> Decimal.Value (self.big_decimal.subtract that.big_decimal) _ -> Decimal.Value (self.big_decimal.subtract that.big_decimal math_context.math_context) @@ -447,7 +590,7 @@ type Decimal # => Decimal.new 207.8 multiply : Decimal -> Math_Context | Nothing -> Decimal ! Arithmetic_Error multiply self (that : Decimal) (math_context : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error = - handle_java_exception <| + error_if_from_float self that <| handle_java_exception <| case math_context of Nothing -> Decimal.Value (self.big_decimal.multiply that.big_decimal) _ -> Decimal.Value (self.big_decimal.multiply that.big_decimal math_context.math_context) @@ -511,7 +654,7 @@ type Decimal divide : Decimal -> Math_Context | Nothing -> Decimal ! Arithmetic_Error divide self (that : Decimal) (math_context : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error = extra_message = " Please use `.divide` with an explicit `Math_Context` to limit the numeric precision." - handle_java_exception extra_message=extra_message <| + error_if_from_float self that <| handle_java_exception extra_message=extra_message <| case math_context of Nothing -> Decimal.Value (self.big_decimal.divide that.big_decimal) _ -> Decimal.Value (self.big_decimal.divide that.big_decimal math_context.math_context) @@ -566,7 +709,7 @@ type Decimal # => -2 remainder : Decimal -> Decimal remainder self (that : Decimal) -> Decimal = - handle_java_exception <| + error_if_from_float self that <| handle_java_exception <| Decimal.Value (self.big_decimal.remainder that.big_decimal) ## ALIAS modulo, modulus, remainder @@ -628,7 +771,7 @@ type Decimal # => 3 div : Decimal -> Decimal div self that:Decimal -> Decimal = - handle_java_exception <| + error_if_from_float self that <| handle_java_exception <| Decimal.Value (self.big_decimal.divideToIntegralValue that.big_decimal) ## ALIAS power @@ -732,7 +875,9 @@ type Decimal Decimal.new "12" . min (Decimal.new "13") # => Decimal.new "12" min : Decimal -> Decimal - min self (that : Decimal) -> Decimal = if self < that then self else that + min self (that : Decimal) -> Decimal = + handle_java_exception <| + if self < that then self else that ## GROUP Math ICON transform4 @@ -747,7 +892,9 @@ type Decimal Decimal.new "12" . max (Decimal.new "13") # => Decimal.new "13" max : Decimal -> Decimal - max self (that : Decimal) -> Decimal = if self > that then self else that + max self (that : Decimal) -> Decimal = + handle_java_exception <| + if self > that then self else that ## GROUP Conversions ICON convert @@ -769,7 +916,7 @@ type Decimal back_to_decimal = BigDecimal.new as_biginteger are_equal = (self.big_decimal.compareTo back_to_decimal) == 0 if are_equal then as_biginteger else - Warning.attach (Loss_Of_Numeric_Precision.Warning self as_biginteger) as_biginteger + attach_loss_of_numeric_precision self as_biginteger ## GROUP Conversions ICON convert @@ -806,7 +953,7 @@ type Decimal to_float : Float to_float self -> Float = f = self.big_decimal.doubleValue - if f.is_finite then attach_loss_of_numeric_precision self f else + if f.is_finite then f else message = "Outside representable Float range (approximately (-1.8E308, 1.8E308))" Warning.attach (Out_Of_Range.Error self message) f @@ -1081,7 +1228,11 @@ type Decimal c = dec 12.345 dec : Text | Integer | Float -> Math_Context | Nothing -> Decimal ! Arithmetic_Error | Number_Parse_Error dec (x : Text | Integer | Float) (mc : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error | Number_Parse_Error = - Decimal.new x mc + handle_java_exception <| + case x of + _ : Text -> Decimal.from_text x mc + _ : Integer -> Decimal.from_integer x mc + _ : Float -> Decimal.from_float x mc explicit=True ## PRIVATE handle_number_format_exception ~action = @@ -1092,13 +1243,12 @@ handle_number_format_exception ~action = handle_precision_loss : Any -> ConversionResult -> Any handle_precision_loss original_value conversion_result:ConversionResult -> Any = if conversion_result.hasPrecisionLoss.not then conversion_result.newValue else - new_value = conversion_result.newValue - Warning.attach (Loss_Of_Numeric_Precision.Warning original_value new_value) new_value + attach_loss_of_numeric_precision original_value conversion_result.newValue ## PRIVATE attach_loss_of_numeric_precision : Float -> Any -> Any -attach_loss_of_numeric_precision x value = - Warning.attach (Loss_Of_Numeric_Precision.Warning x value) value +attach_loss_of_numeric_precision orig value = + Warning.attach (Loss_Of_Numeric_Precision.Warning orig value) value ## PRIVATE handle_java_exception ~action (extra_message : Text = "") = @@ -1123,11 +1273,25 @@ Decimal.from (that : Text) = Decimal.from_text that Decimal.from (that : Integer) = Decimal.new that ## PRIVATE -Decimal.from (that : Float) = - is_exceptional = that.is_nan || that.is_infinite - if is_exceptional then Error.throw (Illegal_Argument.Error "Cannot convert "+that.to_text+" to a Decimal") else - handle_java_exception <| attach_loss_of_numeric_precision that <| - Decimal.Value <| Decimal_Utils.fromFloat that +Decimal.from (that : Float) = Decimal.from_float that explicit=False ## PRIVATE -Float.from (that : Decimal) = that.to_float +Float.from (that : Decimal) = + attach_loss_of_numeric_precision that that.to_float + +## PRIVATE + Helper method allowing access to the backing field. +get_big_decimal (that : Decimal) = that.big_decimal + +## PRIVATE + Create a Decimal from the Java backing field. +from_big_decimal that = Decimal.Value that + +## PRIVATE +error_if_from_float left right ~action = + msg = "Cannot perform operation on Float and Decimal; please convert the Float to Decimal using .to_decimal." + case left of + Decimal.From_Float _ _ -> Error.throw (Illegal_Argument.Error msg) + _ -> case right of + Decimal.From_Float _ _ -> Error.throw (Illegal_Argument.Error msg) + _ -> action diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Numeric/Internal/Decimal_Internal.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Numeric/Internal/Decimal_Internal.enso index edd28b76fc..bf84d9ae37 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Numeric/Internal/Decimal_Internal.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Numeric/Internal/Decimal_Internal.enso @@ -3,6 +3,8 @@ private import project.Data.Decimal.Decimal import project.Data.Numbers.Number import project.Data.Text.Text +from project.Errors.Common import Loss_Of_Numeric_Precision +import project.Warning.Warning from project.Data.Ordering import Comparable, Ordering polyglot java import org.enso.base.numeric.Decimal_Utils @@ -11,4 +13,5 @@ polyglot java import org.enso.base.numeric.Decimal_Utils type Decimal_Comparator compare (x : Decimal) (y : Decimal) = Ordering.from_sign (x.big_decimal.compareTo y.big_decimal) + hash x:Decimal = Decimal_Utils.hashCodeOf x.big_decimal diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Storage.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Storage.enso index 98499172ae..99adac591e 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Storage.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Storage.enso @@ -2,6 +2,7 @@ from Standard.Base import all import Standard.Base.Errors.Common.Index_Out_Of_Bounds import Standard.Base.Errors.Illegal_Argument.Illegal_Argument import Standard.Base.Errors.Illegal_State.Illegal_State +from Standard.Base.Data.Decimal import from_big_decimal, get_big_decimal import project.Column.Column import project.Value_Type.Bits @@ -78,7 +79,7 @@ closest_storage_type value_type = case value_type of This step is unnecessary for primitive and builtin values, but necessary for values such as `Decimal`/`BigDecimal`. enso_to_java x = case x of - Decimal.Value big_decimal -> big_decimal + _ : Decimal -> get_big_decimal x _ -> x ## PRIVATE @@ -88,7 +89,7 @@ enso_to_java x = case x of necessary for values such as `Decimal`/`BigDecimal`. java_to_enso x = case x of _ : Nothing -> Nothing - _ : BigDecimal -> Decimal.Value x + _ : BigDecimal -> from_big_decimal x _ -> x ## PRIVATE diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsAtomNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsAtomNode.java index 8bfcdd3703..f335d9fc56 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsAtomNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsAtomNode.java @@ -9,6 +9,7 @@ import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.Node; @@ -27,6 +28,7 @@ import org.enso.interpreter.runtime.data.atom.AtomConstructor; import org.enso.interpreter.runtime.data.atom.StructsLibrary; import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; import org.enso.interpreter.runtime.state.State; +import org.enso.interpreter.runtime.warning.WarningsLibrary; @GenerateUncached public abstract class EqualsAtomNode extends Node { @@ -91,16 +93,24 @@ public abstract class EqualsAtomNode extends Node { @Cached(value = "customComparatorNode.execute(self)") Type cachedComparator, @Cached(value = "findCompareMethod(cachedComparator)", allowUncached = true) Function compareFn, - @Cached(value = "invokeCompareNode(compareFn)") InvokeFunctionNode invokeNode) { - var otherComparator = customComparatorNode.execute(other); - if (cachedComparator != otherComparator) { - return false; + @Cached(value = "invokeCompareNode(compareFn)") InvokeFunctionNode invokeNode, + @Shared @CachedLibrary(limit = "10") WarningsLibrary warnings) { + try { + var otherComparator = customComparatorNode.execute(other); + if (cachedComparator != otherComparator) { + return false; + } + var ctx = EnsoContext.get(this); + var args = new Object[] {cachedComparator, self, other}; + var result = invokeNode.execute(compareFn, null, State.create(ctx), args); + assert orderingOrNullOrError(this, ctx, result, compareFn); + if (warnings.hasWarnings(result)) { + result = warnings.removeWarnings(result); + } + return ctx.getBuiltins().ordering().newEqual() == result; + } catch (UnsupportedMessageException e) { + throw EnsoContext.get(this).raiseAssertionPanic(this, null, e); } - var ctx = EnsoContext.get(this); - var args = new Object[] {cachedComparator, self, other}; - var result = invokeNode.execute(compareFn, null, State.create(ctx), args); - assert orderingOrNullOrError(this, ctx, result, compareFn); - return ctx.getBuiltins().ordering().newEqual() == result; } @TruffleBoundary @@ -123,16 +133,21 @@ public abstract class EqualsAtomNode extends Node { @Specialization( replaces = {"equalsAtomsWithDefaultComparator", "equalsAtomsWithCustomComparator"}) - boolean equalsAtomsUncached(VirtualFrame frame, Atom self, Atom other) { + boolean equalsAtomsUncached( + VirtualFrame frame, + Atom self, + Atom other, + @Shared @CachedLibrary(limit = "10") WarningsLibrary warnings) { if (self.getConstructor() != other.getConstructor()) { return false; } else { - return equalsAtomsUncached(frame == null ? null : frame.materialize(), self, other); + return equalsAtomsUncached(frame == null ? null : frame.materialize(), self, other, warnings); } } @CompilerDirectives.TruffleBoundary - private boolean equalsAtomsUncached(MaterializedFrame frame, Atom self, Atom other) { + private boolean equalsAtomsUncached( + MaterializedFrame frame, Atom self, Atom other, WarningsLibrary warnings) { Type customComparator = CustomComparatorNode.getUncached().execute(self); if (customComparator != null) { Function compareFunc = findCompareMethod(customComparator); @@ -144,7 +159,8 @@ public abstract class EqualsAtomNode extends Node { CustomComparatorNode.getUncached(), customComparator, compareFunc, - invokeFuncNode); + invokeFuncNode, + warnings); } for (int i = 0; i < self.getConstructor().getArity(); i++) { var selfField = StructsLibrary.getUncached().getField(self, i); diff --git a/test/Base_Internal_Tests/src/Comparator_Spec.enso b/test/Base_Internal_Tests/src/Comparator_Spec.enso index 3ca9a40f24..036658ba5c 100644 --- a/test/Base_Internal_Tests/src/Comparator_Spec.enso +++ b/test/Base_Internal_Tests/src/Comparator_Spec.enso @@ -1,5 +1,6 @@ from Standard.Base import all import Standard.Base.Errors.Common.Incomparable_Values +import Standard.Base.Errors.Illegal_Argument.Illegal_Argument from Standard.Test import all @@ -34,6 +35,19 @@ type No_Ord_Comparator Comparable.from (that:No_Ord) = Comparable.new that No_Ord_Comparator +type Attach_Warning + Value (n : Integer) + Value2 (n : Integer) + +type Attach_Warning_Comparator + compare (x : Attach_Warning) (y : Attach_Warning) = + r = Ordering.compare x.n y.n + Warning.attach (Illegal_Argument.Error "warning") r + + hash x:Attach_Warning = Ordering.hash x.n + +Comparable.from (that : Attach_Warning) = Comparable.new that Attach_Warning_Comparator + # Tests add_specs suite_builder = suite_builder.group "Object Comparator" group_builder-> @@ -79,6 +93,11 @@ add_specs suite_builder = suite_builder.group "Object Comparator" group_builder- (default_comparator 1 True) . should_fail_with Incomparable_Values (default_comparator (No_Ord.Value 1) (No_Ord.Value 2)).should_fail_with Incomparable_Values + group_builder.specify "warnings attached to the return value of .compare should not affect the boolean value of the comparison operator" <| + ((Attach_Warning.Value 1) < (Attach_Warning.Value 2)) . should_be_true + ((Attach_Warning.Value 1) == (Attach_Warning.Value 1)) . should_be_true + ((Attach_Warning.Value 1) == (Attach_Warning.Value2 1)) . should_be_true + main filter=Nothing = suite = Test.build suite_builder-> add_specs suite_builder diff --git a/test/Base_Internal_Tests/src/Decimal_Constructor_Spec.enso b/test/Base_Internal_Tests/src/Decimal_Constructor_Spec.enso new file mode 100644 index 0000000000..40ae65de71 --- /dev/null +++ b/test/Base_Internal_Tests/src/Decimal_Constructor_Spec.enso @@ -0,0 +1,27 @@ +from Standard.Base import all + +from Standard.Test import all + +id_decimal (x : Decimal) -> Decimal = x + +is_value_constructor d = case d of + Decimal.Value _ -> True + Decimal.From_Float _ _ -> False + +add_specs suite_builder = + suite_builder.group "(Decimal_Constructor_Spec) conversions" group_builder-> + group_builder.specify "Conversion from float to decimal should use the correct constructor" <| + is_value_constructor (dec 0.1) . should_be_true + is_value_constructor (0.1 . to_decimal) . should_be_true + is_value_constructor (0.1 . to_decimal) . should_be_true + is_value_constructor (Decimal.from_float 0.1) . should_be_true + + is_value_constructor (Decimal.new 0.1) . should_be_false + is_value_constructor (Decimal.from 0.1) . should_be_false + is_value_constructor (Decimal.from_float 0.1 explicit=False) . should_be_false + is_value_constructor (id_decimal 0.1) . should_be_false + +main filter=Nothing = + suite = Test.build suite_builder-> + add_specs suite_builder + suite.run_with_filter filter diff --git a/test/Base_Internal_Tests/src/Main.enso b/test/Base_Internal_Tests/src/Main.enso index 47d57de1c0..a1fc96bf8f 100644 --- a/test/Base_Internal_Tests/src/Main.enso +++ b/test/Base_Internal_Tests/src/Main.enso @@ -4,12 +4,14 @@ from Standard.Test import all import project.Input_Output_Spec import project.Comparator_Spec +import project.Decimal_Constructor_Spec import project.Grapheme_Spec main filter=Nothing = suite = Test.build suite_builder-> - Input_Output_Spec.add_specs suite_builder Comparator_Spec.add_specs suite_builder + Decimal_Constructor_Spec.add_specs suite_builder Grapheme_Spec.add_specs suite_builder + Input_Output_Spec.add_specs suite_builder suite.run_with_filter filter diff --git a/test/Base_Tests/src/Data/Decimal_Spec.enso b/test/Base_Tests/src/Data/Decimal_Spec.enso index 3a5f806853..b40adae748 100644 --- a/test/Base_Tests/src/Data/Decimal_Spec.enso +++ b/test/Base_Tests/src/Data/Decimal_Spec.enso @@ -13,6 +13,8 @@ polyglot java import java.lang.Integer as Java_Integer Decimal.should_have_rep self rep = self.internal_representation . should_equal rep +id_decimal (x : Decimal) -> Decimal = x + add_specs suite_builder = suite_builder.group "(Decimal_Spec) construction" group_builder-> group_builder.specify "should be able to construct a Decimal with dec" <| @@ -211,7 +213,7 @@ add_specs suite_builder = (Ordering.hash x0) . should_equal (Ordering.hash x1) - group_builder.specify "should compare correctly to Integer and Float" pending="https://github.com/enso-org/enso/issues/10163" <| + group_builder.specify "should compare correctly to Integer and Float" <| values = [] + [[0.1, 0.1]] + [["0.1", 0.1]] @@ -373,6 +375,13 @@ add_specs suite_builder = (lesser > d) . should_be_false (lesser >= d) . should_be_false + group_builder.specify "mixed float/decimal comparisons should attach a warning" pending="https://github.com/enso-org/enso/issues/10679" <| + [(_ == _), (_ != _), (_ <= _), (_ >= _), (_ < _), (_ > _), .min, .max].map c-> + a = c (Decimal.new "0.1") 0.1 + b = c 0.1 (Decimal.new "0.1") + Problems.expect_only_warning (Loss_Of_Numeric_Precision.Warning 0.1 (Decimal.new "0.1")) a + Problems.expect_only_warning (Loss_Of_Numeric_Precision.Warning 0.1 (Decimal.new "0.1")) b + group_builder.specify "should not implicitly convert Decimal to Integer in comparison" <| (Decimal.new "12.5" == 12) . should_be_false (12 == Decimal.new "12.5") . should_be_false @@ -383,173 +392,173 @@ add_specs suite_builder = suite_builder.group "(Decimal_Spec) edge cases" group_builder-> group_builder.specify "can support values outside the double range" <| - d = Decimal.new Float.max_value + d = Float.max_value . to_decimal (d == Float.max_value) . should_be_true ((d * d) == Float.max_value) . should_be_false ((-(d * d)) == -Float.max_value) . should_be_false Ordering.hash (d * d) . should_equal 2146435072 Ordering.hash -(d * d) . should_equal -1048576 - Ordering.hash ((d * d) + 0.1) . should_equal 2146435072 - Ordering.hash ((-(d * d)) + 0.1) . should_equal -1048576 + Ordering.hash ((d * d) + 0.1.to_decimal) . should_equal 2146435072 + Ordering.hash ((-(d * d)) + 0.1.to_decimal) . should_equal -1048576 group_builder.specify "from conversion supports values outside the double range" <| - d = Float.max_value.to Decimal + d = Float.max_value . to_decimal (d == Float.max_value) . should_be_true ((d * d) == Float.max_value) . should_be_false ((-(d * d)) == -Float.max_value) . should_be_false Ordering.hash (d * d) . should_equal 2146435072 Ordering.hash -(d * d) . should_equal -1048576 - Ordering.hash ((d * d) + 0.1) . should_equal 2146435072 - Ordering.hash ((-(d * d)) + 0.1) . should_equal -1048576 + Ordering.hash ((d * d) + 0.1.to_decimal) . should_equal 2146435072 + Ordering.hash ((-(d * d)) + 0.1.to_decimal) . should_equal -1048576 suite_builder.group "(Decimal_Spec) arithmetic" group_builder-> group_builder.specify "should allow arithmetic with Decimals, without Math_Context" <| - (Decimal.new 1 + Decimal.new 2) . should_equal (Decimal.new 3) - (Decimal.new 1.1 + Decimal.new 2.2) . should_equal (Decimal.new 3.3) + (Decimal.from_integer 1 + Decimal.from_integer 2) . should_equal (Decimal.from_integer 3) + (Decimal.from_float 1.1 + Decimal.from_float 2.2) . should_equal (Decimal.from_float 3.3) - (Decimal.new 10 - Decimal.new 6) . should_equal (Decimal.new 4) - (Decimal.new 10.1 - Decimal.new 6.6) . should_equal (Decimal.new 3.5) + (Decimal.from_integer 10 - Decimal.from_integer 6) . should_equal (Decimal.from_integer 4) + (Decimal.from_float 10.1 - Decimal.from_float 6.6) . should_equal (Decimal.from_float 3.5) - (Decimal.new 3 * Decimal.new 7) . should_equal (Decimal.new 21) - (Decimal.new 3.12 * Decimal.new 7.97) . should_equal (Decimal.new 24.8664) + (Decimal.from_integer 3 * Decimal.from_integer 7) . should_equal (Decimal.from_integer 21) + (Decimal.from_float 3.12 * Decimal.from_float 7.97) . should_equal (Decimal.from_float 24.8664) - (Decimal.new 50 / Decimal.new 2) . should_equal (Decimal.new 25) - (Decimal.new 50.75 / Decimal.new 2.5) . should_equal (Decimal.new 20.3) + (Decimal.from_integer 50 / Decimal.from_integer 2) . should_equal (Decimal.from_integer 25) + (Decimal.from_float 50.75 / Decimal.from_float 2.5) . should_equal (Decimal.from_float 20.3) - (Decimal.new 1 + Decimal.new -2) . should_equal (Decimal.new -1) - (Decimal.new 1.1 + Decimal.new -2.2) . should_equal (Decimal.new -1.1) + (Decimal.from_integer 1 + Decimal.from_integer -2) . should_equal (Decimal.from_integer -1) + (Decimal.from_float 1.1 + Decimal.from_float -2.2) . should_equal (Decimal.from_float -1.1) - (Decimal.new -10 - Decimal.new 6) . should_equal (Decimal.new -16) - (Decimal.new 10.1 - Decimal.new -6.6) . should_equal (Decimal.new 16.7) + (Decimal.from_integer -10 - Decimal.from_integer 6) . should_equal (Decimal.from_integer -16) + (Decimal.from_float 10.1 - Decimal.from_float -6.6) . should_equal (Decimal.from_float 16.7) - (Decimal.new 3 * Decimal.new -7) . should_equal (Decimal.new -21) - (Decimal.new -3.12 * Decimal.new 7.97) . should_equal (Decimal.new -24.8664) + (Decimal.from_integer 3 * Decimal.from_integer -7) . should_equal (Decimal.from_integer -21) + (Decimal.from_float -3.12 * Decimal.from_float 7.97) . should_equal (Decimal.from_float -24.8664) - (Decimal.new -50 / Decimal.new -2) . should_equal (Decimal.new 25) - (Decimal.new -50.75 / Decimal.new -2.5) . should_equal (Decimal.new 20.3) + (Decimal.from_integer -50 / Decimal.from_integer -2) . should_equal (Decimal.from_integer 25) + (Decimal.from_float -50.75 / Decimal.from_float -2.5) . should_equal (Decimal.from_float 20.3) - (Decimal.new 213427523957 + Decimal.new 93849398884384) . should_equal (Decimal.new 94062826408341) + (Decimal.from_integer 213427523957 + Decimal.from_integer 93849398884384) . should_equal (Decimal.from_integer 94062826408341) (Decimal.new "723579374753.3535345" + Decimal.new "35263659267.23434535") . should_equal (Decimal.new "758843034020.58787985") - (Decimal.new -29388920982834 - Decimal.new 842820) . should_equal (Decimal.new -29388921825654) + (Decimal.from_integer -29388920982834 - Decimal.from_integer 842820) . should_equal (Decimal.from_integer -29388921825654) (Decimal.new "-8273762787.3535345" - Decimal.new "76287273.23434535") . should_equal (Decimal.new "-8350050060.58787985") - (Decimal.new 7297927982888383 * Decimal.new 828737) . should_equal (Decimal.new 6048062942754969862271) + (Decimal.from_integer 7297927982888383 * Decimal.from_integer 828737) . should_equal (Decimal.from_integer 6048062942754969862271) (Decimal.new "893872388.3535345" * Decimal.new "72374727737.23434535") . should_equal (Decimal.new "64693770738918463976.840471466139575") (Decimal.new "909678645268840" / Decimal.new "28029830") . should_equal (Decimal.new "32453948") (Decimal.new "384456406.7860325392609633764" / Decimal.new "24556.125563546") . should_equal (Decimal.new "15656.2323234234") - (Decimal.new 3948539458034580838458034803485 + Decimal.new 237957498573948579387495837459837) . should_equal (Decimal.new 241906038031983160225953872263322) + (Decimal.from_integer 3948539458034580838458034803485 + Decimal.from_integer 237957498573948579387495837459837) . should_equal (Decimal.from_integer 241906038031983160225953872263322) (Decimal.new "927349527347587934.34573495739475938745" + Decimal.new "37593874597239487593745.3457973847574") . should_equal (Decimal.new "37594801946766835181679.69153234215215938745") - (Decimal.new -239485787538745937495873495759598 - Decimal.new 273958739485793475934793745) . should_equal (Decimal.new -239486061497485423289349430553343) + (Decimal.from_integer -239485787538745937495873495759598 - Decimal.from_integer 273958739485793475934793745) . should_equal (Decimal.from_integer -239486061497485423289349430553343) (Decimal.new "-79237492374927979579239292.293749287928373" - Decimal.new "239729379279872947923947.234273947927397239") . should_equal (Decimal.new "-79477221754207852527163239.528023235855770239") - (Decimal.new 3745937948729837923798237 * Decimal.new 273948729872398729387) . should_equal (Decimal.new 1026194943235357770434981633846801087750690719) + (Decimal.from_integer 3745937948729837923798237 * Decimal.from_integer 273948729872398729387) . should_equal (Decimal.from_integer 1026194943235357770434981633846801087750690719) (Decimal.new "38450830483049850394093.48579374987938934" * Decimal.new "297349287397297.2394279379287398729879234") . should_equal (Decimal.new "11433327043969147404767677628296882442.487387236451853113753778864149360386696556") - (Decimal.new 1026194943235357770434981633846801087750690719 / Decimal.new 273948729872398729387) . should_equal (Decimal.new 3745937948729837923798237) + (Decimal.from_integer 1026194943235357770434981633846801087750690719 / Decimal.from_integer 273948729872398729387) . should_equal (Decimal.from_integer 3745937948729837923798237) (Decimal.new "11433327043969147404767677628296882442.487387236451853113753778864149360386696556" / Decimal.new "297349287397297.2394279379287398729879234") . should_equal (Decimal.new "38450830483049850394093.48579374987938934") group_builder.specify "should allow arithmetic with Decimals, with precision setting (examples)" <| - (Decimal.new "10.22") . add (Decimal.new "20.33") (Math_Context.new 3) . should_equal (Decimal.new 30.6) - (Decimal.new "20.33") . subtract (Decimal.new "10.22") (Math_Context.new 3) . should_equal (Decimal.new 10.1) - (Decimal.new "10.22") . multiply (Decimal.new "20.33") (Math_Context.new 4) . should_equal (Decimal.new 207.8) - (Decimal.new "1065.9378") . divide (Decimal.new "23.34") (Math_Context.new 3) . should_equal (Decimal.new 45.7) + (Decimal.new "10.22") . add (Decimal.new "20.33") (Math_Context.new 3) . should_equal (Decimal.new "30.6") + (Decimal.new "20.33") . subtract (Decimal.new "10.22") (Math_Context.new 3) . should_equal (Decimal.new "10.1") + (Decimal.new "10.22") . multiply (Decimal.new "20.33") (Math_Context.new 4) . should_equal (Decimal.new "207.8") + (Decimal.new "1065.9378") . divide (Decimal.new "23.34") (Math_Context.new 3) . should_equal (Decimal.new "45.7") group_builder.specify "should allow arithmetic with Decimals, with precision setting" <| - (Decimal.new 213427523957 . add (Decimal.new 93849398884384) (Math_Context.new 8)) . should_equal (Decimal.new 94062826000000) + (Decimal.from_integer 213427523957 . add (Decimal.from_integer 93849398884384) (Math_Context.new 8)) . should_equal (Decimal.from_integer 94062826000000) (Decimal.new "723579374753.3535345" . add (Decimal.new "35263659267.23434535") (Math_Context.new 12)) . should_equal (Decimal.new "758843034021") - (Decimal.new -29388920982834 . subtract (Decimal.new 842820) (Math_Context.new 7)) . should_equal (Decimal.new -29388920000000) + (Decimal.from_integer -29388920982834 . subtract (Decimal.from_integer 842820) (Math_Context.new 7)) . should_equal (Decimal.from_integer -29388920000000) (Decimal.new "-8273762787.3535345" . subtract (Decimal.new "76287273.23434535") (Math_Context.new 10)) . should_equal (Decimal.new "-8350050061") - (Decimal.new 7297927982888383 . multiply (Decimal.new 828737) (Math_Context.new 6)) . should_equal (Decimal.new 6048060000000000000000 ) + (Decimal.from_integer 7297927982888383 . multiply (Decimal.from_integer 828737) (Math_Context.new 6)) . should_equal (Decimal.from_integer 6048060000000000000000 ) (Decimal.new "893872388.3535345" . multiply (Decimal.new "72374727737.23434535") (Math_Context.new 14)) . should_equal (Decimal.new "64693770738918000000") (Decimal.new "909678645268840" . divide (Decimal.new "28029830") (Math_Context.new 6)) . should_equal (Decimal.new "32453900 ") (Decimal.new "384456406.7860325392609633764" . divide (Decimal.new "24556.125563546") (Math_Context.new 7)) . should_equal (Decimal.new "15656.23") - (Decimal.new 3948539458034580838458034803485 . add (Decimal.new 237957498573948579387495837459837) (Math_Context.new 20)) . should_equal (Decimal.new 241906038031983160230000000000000) + (Decimal.from_integer 3948539458034580838458034803485 . add (Decimal.from_integer 237957498573948579387495837459837) (Math_Context.new 20)) . should_equal (Decimal.from_integer 241906038031983160230000000000000) (Decimal.new "927349527347587934.34573495739475938745" . add (Decimal.new "37593874597239487593745.3457973847574") (Math_Context.new 24)) . should_equal (Decimal.new "37594801946766835181679.7") - (Decimal.new -239485787538745937495873495759598 . subtract (Decimal.new 273958739485793475934793745) (Math_Context.new 12)) . should_equal (Decimal.new -239486061497000000000000000000000) + (Decimal.from_integer -239485787538745937495873495759598 . subtract (Decimal.from_integer 273958739485793475934793745) (Math_Context.new 12)) . should_equal (Decimal.from_integer -239486061497000000000000000000000) (Decimal.new "-79237492374927979579239292.293749287928373" . subtract (Decimal.new "239729379279872947923947.234273947927397239") (Math_Context.new 16)) . should_equal (Decimal.new "-79477221754207850000000000") - (Decimal.new 3745937948729837923798237 . multiply (Decimal.new 273948729872398729387) (Math_Context.new 21)) . should_equal (Decimal.new 1026194943235357770430000000000000000000000000) + (Decimal.from_integer 3745937948729837923798237 . multiply (Decimal.from_integer 273948729872398729387) (Math_Context.new 21)) . should_equal (Decimal.from_integer 1026194943235357770430000000000000000000000000) (Decimal.new "38450830483049850394093.48579374987938934" . multiply (Decimal.new "297349287397297.2394279379287398729879234") (Math_Context.new 35)) . should_equal (Decimal.new "11433327043969147404767677628296882000") - (Decimal.new 1026194943235357770434981633846801087750690719 . divide (Decimal.new 273948729872398729387) (Math_Context.new 10)) . should_equal (Decimal.new 3745937949000000000000000) + (Decimal.from_integer 1026194943235357770434981633846801087750690719 . divide (Decimal.from_integer 273948729872398729387) (Math_Context.new 10)) . should_equal (Decimal.from_integer 3745937949000000000000000) (Decimal.new "11433327043969147404767677628296882442.487387236451853113753778864149360386696556" . divide (Decimal.new "297349287397297.2394279379287398729879234") (Math_Context.new 9)) . should_equal (Decimal.new "38450830500000000000000") group_builder.specify "should allow arithmetic with Decimals, with precision setting and rounding mode" <| - (Decimal.new 3.4 . add (Decimal.new 20.05) (Math_Context.new 3)) . should_equal (Decimal.new 23.5) - (Decimal.new 3.4 . add (Decimal.new 20.05) (Math_Context.new 3 Rounding_Mode.bankers)) . should_equal (Decimal.new 23.4) - (Decimal.new -3.4 . add (Decimal.new -20.05) (Math_Context.new 3)) . should_equal (Decimal.new -23.5) - (Decimal.new -3.4 . add (Decimal.new -20.05) (Math_Context.new 3 Rounding_Mode.bankers)) . should_equal (Decimal.new -23.4) - (Decimal.new -867.625 . subtract (Decimal.new -33.05) (Math_Context.new 5)) . should_equal (Decimal.new -834.58) - (Decimal.new -867.625 . subtract (Decimal.new -33.05) (Math_Context.new 5 Rounding_Mode.bankers)) . should_equal (Decimal.new -834.58) - (Decimal.new 49.31 . multiply (Decimal.new 5) (Math_Context.new 4)) . should_equal (Decimal.new 246.6) - (Decimal.new 49.31 . multiply (Decimal.new 5) (Math_Context.new 4 Rounding_Mode.bankers)) . should_equal (Decimal.new 246.6) - (Decimal.new 49.29 . multiply (Decimal.new 5) (Math_Context.new 4)) . should_equal (Decimal.new 246.5) - (Decimal.new 49.29 . multiply (Decimal.new 5) (Math_Context.new 4 Rounding_Mode.bankers)) . should_equal (Decimal.new 246.4) - (Decimal.new 247.75 . divide (Decimal.new -5) (Math_Context.new 3)) . should_equal (Decimal.new -49.6) - (Decimal.new 247.75 . divide (Decimal.new -5) (Math_Context.new 3 Rounding_Mode.bankers)) . should_equal (Decimal.new -49.6) + (Decimal.from_float 3.4 . add (Decimal.from_float 20.05) (Math_Context.new 3)) . should_equal (Decimal.from_float 23.5) + (Decimal.from_float 3.4 . add (Decimal.from_float 20.05) (Math_Context.new 3 Rounding_Mode.bankers)) . should_equal (Decimal.from_float 23.4) + (Decimal.from_float -3.4 . add (Decimal.from_float -20.05) (Math_Context.new 3)) . should_equal (Decimal.from_float -23.5) + (Decimal.from_float -3.4 . add (Decimal.from_float -20.05) (Math_Context.new 3 Rounding_Mode.bankers)) . should_equal (Decimal.from_float -23.4) + (Decimal.from_float -867.625 . subtract (Decimal.from_float -33.05) (Math_Context.new 5)) . should_equal (Decimal.from_float -834.58) + (Decimal.from_float -867.625 . subtract (Decimal.from_float -33.05) (Math_Context.new 5 Rounding_Mode.bankers)) . should_equal (Decimal.from_float -834.58) + (Decimal.from_float 49.31 . multiply (Decimal.from_integer 5) (Math_Context.new 4)) . should_equal (Decimal.from_float 246.6) + (Decimal.from_float 49.31 . multiply (Decimal.from_integer 5) (Math_Context.new 4 Rounding_Mode.bankers)) . should_equal (Decimal.from_float 246.6) + (Decimal.from_float 49.29 . multiply (Decimal.from_integer 5) (Math_Context.new 4)) . should_equal (Decimal.from_float 246.5) + (Decimal.from_float 49.29 . multiply (Decimal.from_integer 5) (Math_Context.new 4 Rounding_Mode.bankers)) . should_equal (Decimal.from_float 246.4) + (Decimal.from_float 247.75 . divide (Decimal.from_integer -5) (Math_Context.new 3)) . should_equal (Decimal.from_float -49.6) + (Decimal.from_float 247.75 . divide (Decimal.from_integer -5) (Math_Context.new 3 Rounding_Mode.bankers)) . should_equal (Decimal.from_float -49.6) - group_builder.specify "should allow mixed arithmetic" <| - (Decimal.new 1 + 2) . should_equal 3 - (Decimal.new 1 - 2) . should_equal -1 - (Decimal.new 3 * 4) . should_equal 12 - (Decimal.new 3 / 4) . should_equal 0.75 + group_builder.specify "should allow mixed arithmetic (integer)" <| + (Decimal.from_integer 1 + 2) . should_equal 3 + (Decimal.from_integer 1 - 2) . should_equal -1 + (Decimal.from_integer 3 * 4) . should_equal 12 + (Decimal.from_integer 3 / 4) . should_equal 0.75 - (1 + Decimal.new 2) . should_equal 3 - (1 - Decimal.new 2) . should_equal -1 - (3 * Decimal.new 4) . should_equal 12 - (3 / Decimal.new 4) . should_equal 0.75 + (1 + Decimal.from_integer 2) . should_equal 3 + (1 - Decimal.from_integer 2) . should_equal -1 + (3 * Decimal.from_integer 4) . should_equal 12 + (3 / Decimal.from_integer 4) . should_equal 0.75 - (Decimal.new 1 + 2.0) . should_equal 3 - (Decimal.new 1 - 2.0) . should_equal -1 - (Decimal.new 3 * 4.0) . should_equal 12 - (Decimal.new 3 / 4.0) . should_equal 0.75 + group_builder.specify "should not allow mixed arithmetic (float)" <| + (Decimal.from_integer 1 + 2.0) . should_fail_with Illegal_Argument + (Decimal.from_integer 1 - 2.0) . should_fail_with Illegal_Argument + (Decimal.from_integer 3 * 4.0) . should_fail_with Illegal_Argument + (Decimal.from_integer 3 / 4.0) . should_fail_with Illegal_Argument + (Decimal.from_integer 3 % 4.0) . should_fail_with Illegal_Argument + (Decimal.from_integer 3 . div 4.0) . should_fail_with Illegal_Argument - (1 + Decimal.new 2.0) . should_equal 3 - (1 - Decimal.new 2.0) . should_equal -1 - (3 * Decimal.new 4.0) . should_equal 12 - (3 / Decimal.new 4.0) . should_equal 0.75 + (1.0 + Decimal.from_float 2.0) . should_fail_with Illegal_Argument + (1.0 - Decimal.from_float 2.0) . should_fail_with Illegal_Argument + (3.0 * Decimal.from_float 4.0) . should_fail_with Illegal_Argument + (3.0 / Decimal.from_float 4.0) . should_fail_with Illegal_Argument + (3.0 % Decimal.from_float 4.0) . should_fail_with Illegal_Argument group_builder.specify "Decimal mixed arithmetic should result in Decimal" <| - (Decimal.new 1 + 2) . should_be_a Decimal - (Decimal.new 1 - 2) . should_be_a Decimal - (Decimal.new 3 * 4) . should_be_a Decimal - (Decimal.new 3 / 4) . should_be_a Decimal + (Decimal.from_integer 1 + 2) . should_be_a Decimal + (Decimal.from_integer 1 - 2) . should_be_a Decimal + (Decimal.from_integer 3 * 4) . should_be_a Decimal + (Decimal.from_integer 3 / 4) . should_be_a Decimal - (1 + Decimal.new 2) . should_be_a Decimal - (1 - Decimal.new 2) . should_be_a Decimal - (3 * Decimal.new 4) . should_be_a Decimal - (3 / Decimal.new 4) . should_be_a Decimal + (1 + Decimal.from_integer 2) . should_be_a Decimal + (1 - Decimal.from_integer 2) . should_be_a Decimal + (3 * Decimal.from_integer 4) . should_be_a Decimal + (3 / Decimal.from_integer 4) . should_be_a Decimal group_builder.specify "should get an aritmetic error when a result is a nonterminating decimal expansion" <| - (Decimal.new 1 / Decimal.new 3) . should_fail_with Arithmetic_Error + (Decimal.from_integer 1 / Decimal.from_integer 3) . should_fail_with Arithmetic_Error group_builder.specify "should negate values correctly" <| - Decimal.new 5 . negate . should_equal -5 - Decimal.new -5 . negate . should_equal 5 - Decimal.new 5.3 . negate . should_equal -5.3 - Decimal.new -5.3 . negate . should_equal 5.3 - Decimal.new 0 . negate . should_equal 0 - Decimal.new -0 . negate . should_equal 0 - d = Decimal.new 5 + Decimal.from_integer 5 . negate . should_equal -5 + Decimal.from_integer -5 . negate . should_equal 5 + Decimal.from_float 5.3 . negate . should_equal -5.3 + Decimal.from_float -5.3 . negate . should_equal 5.3 + Decimal.from_integer 0 . negate . should_equal 0 + Decimal.from_integer -0 . negate . should_equal 0 + d = Decimal.from_integer 5 nd = -d nd . should_equal -5 - d2 = Decimal.new -5 + d2 = Decimal.from_integer -5 nd2 = -d2 nd2 . should_equal 5 - group_builder.specify "Mixed Decimal/Integer arithmetic should not attach warnings" <| - Problems.assume_no_problems (Decimal.new 1 + 2) - Problems.assume_no_problems (1 + Decimal.new 2) - suite_builder.group "(Decimal_Spec) conversions" group_builder-> group_builder.specify "should convert correctly to and from Integer" <| a = Decimal.new "12000" @@ -600,7 +609,7 @@ add_specs suite_builder = (-huge_b).to_float . should_equal -huge_float (-huge_c).to_float . should_equal -huge_float - group_builder.specify "attaches a warning to the result of .to_float when it is out the representable range" <| + group_builder.specify "attaches a warning to the result of .to_float when it is outside the representable range" <| huge_a = Decimal.new "3.4E320" f = huge_a.to_float f.should_equal Number.positive_infinity @@ -611,14 +620,42 @@ add_specs suite_builder = Problems.expect_only_warning (Out_Of_Range.Error -huge_a "Outside representable Float range (approximately (-1.8E308, 1.8E308))") f2 Warning.get_all f2 . at 0 . value . to_display_text . should_equal "Value out of range: -3.4E+320: Outside representable Float range (approximately (-1.8E308, 1.8E308))" - group_builder.specify "attaches a warning to the result of .to_float when it results in a loss of precision" <| - Problems.expect_only_warning Loss_Of_Numeric_Precision (Decimal.new "0.1" . to_float) - group_builder.specify "should convert correctly to Float using implicit conversions" <| f = Float.from (Decimal.new "56.34") f.should_be_a Float f.should_equal 56.34 + group_builder.specify "Explicit conversions to_decimal do not attach a warning" <| + Problems.assume_no_problems (dec 0.1) + Problems.assume_no_problems (0.1 . to_decimal) + Problems.assume_no_problems (0.1 . to_decimal) + Problems.assume_no_problems (Decimal.from_float 0.1) + + group_builder.specify "Implicit conversions to_decimal attach a warning" <| + Problems.expect_only_warning Loss_Of_Numeric_Precision (Decimal.new 0.1) + Problems.expect_only_warning Loss_Of_Numeric_Precision (Decimal.from 0.1) + Problems.expect_only_warning Loss_Of_Numeric_Precision (Decimal.from_float 0.1 explicit=False) + Problems.expect_only_warning Loss_Of_Numeric_Precision (id_decimal 0.1) + + group_builder.specify "`dec` never attaches a warning" <| + Problems.assume_no_problems (dec "0.1") + Problems.assume_no_problems (dec 10) + Problems.assume_no_problems (dec 0.1) + + group_builder.specify "Explicit conversions to Float do not attach a warning" <| + Problems.assume_no_problems (Decimal.new "0.1" . to_float) + + group_builder.specify "Arithmetic with decimal and explicitly converted float should succeed" <| + ((Decimal.new 1) + (dec 0.1)) . should_equal 1.1 + ((Decimal.new 1) + (0.1 . to_decimal)) . should_equal 1.1 + ((Decimal.new 1) + (0.1 . to_decimal)) . should_equal 1.1 + ((Decimal.new 1) + (Decimal.from_float 0.1)) . should_equal 1.1 + + group_builder.specify "Arithmetic with decimal and implicitly converted float should fail" <| + ((Decimal.new 1) + (Decimal.from 0.1)) . should_fail_with Illegal_Argument + ((Decimal.new 1) + (id_decimal 0.1)) . should_fail_with Illegal_Argument + ((Decimal.new 1) + (Decimal.new 0.1)) . should_fail_with Illegal_Argument + group_builder.specify "should attach a Loss_Of_Numeric_Precision warning when converting decimal to float with .from" <| Problems.expect_only_warning Loss_Of_Numeric_Precision (Float.from (Decimal.new "56.34")) @@ -653,20 +690,18 @@ add_specs suite_builder = + [["9223372036854775807", 10, 7], ["9223372036854775808", 10, 8], ["922337203685477580000000000008", 10, 8]] + [["-9223372036854775808", 10, -8], ["-9223372036854775809", 10, -9], ["-922337203685477580000000000008", 10, -8]] cases.map c-> Test.with_clue ("["+c.to_text+"] ") <| - base = c.at 0 - modulus = c.at 1 - residue = c.at 2 - epsilon = if c.length > 3 then c.at 3 else 0.0 - (Decimal.new base % modulus) . should_equal epsilon=epsilon residue - (Decimal.new base % Decimal.new modulus) . should_equal residue + base = dec (c.at 0) + modulus = dec (c.at 1) + residue = dec (c.at 2) + (base % modulus) . should_equal residue group_builder.specify "should define remainder (example)" <| - (Decimal.new 10 . remainder 3) . should_equal 1 - (Decimal.new 10 % 3) . should_equal 1 + (Decimal.from_integer 10 . remainder 3) . should_equal 1 + (Decimal.from_integer 10 % 3) . should_equal 1 group_builder.specify "remainder 0 should throw" <| - (Decimal.new 3 % 0) . should_fail_with Arithmetic_Error - (Decimal.new 3 % Decimal.new 0) . should_fail_with Arithmetic_Error + (Decimal.from_integer 3 % 0) . should_fail_with Arithmetic_Error + (Decimal.from_integer 3 % Decimal.from_integer 0) . should_fail_with Arithmetic_Error suite_builder.group "(Decimal_Spec) div" group_builder-> group_builder.specify "should define div" <| @@ -1000,9 +1035,6 @@ add_specs suite_builder = 12 . to_decimal . should_be_a Decimal 12.3 . to_decimal . should_be_a Decimal - group_builder.specify "Float.to_decimal should attach a warning" <| - Problems.expect_warning Loss_Of_Numeric_Precision (12.3 . to_decimal) - group_builder.specify "examples" <| 12 . to_decimal . should_equal (Decimal.new "12") 12.3 . to_decimal . should_equal (Decimal.new "12.3")