Decimal type: constuctors, comparisons, and arithmetic (#9272)

This commit is contained in:
GregoryTravis 2024-03-15 17:13:41 -04:00 committed by GitHub
parent 4332eab45a
commit 9a9eff1aa6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1356 additions and 4 deletions

View File

@ -633,6 +633,8 @@
- [Added Google_Analytics.Read][9239]
- [Added `Table.from_union` to allow expanding a vector of tables in one
step][9343]
- [Implemented constructors, comparisons, and arithmetic for a `Decimal`
type.][9272]
[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
@ -911,14 +913,15 @@
[9215]: https://github.com/enso-org/enso/pull/9215
[9225]: https://github.com/enso-org/enso/pull/9225
[9233]: https://github.com/enso-org/enso/pull/9233
[9239]: https://github.com/enso-org/enso/pull/9239
[9249]: https://github.com/enso-org/enso/pull/9249
[9269]: https://github.com/enso-org/enso/pull/9269
[9272]: https://github.com/enso-org/enso/pull/9272
[9299]: https://github.com/enso-org/enso/pull/9299
[9330]: https://github.com/enso-org/enso/pull/9330
[9334]: https://github.com/enso-org/enso/pull/9334
[9346]: https://github.com/enso-org/enso/pull/9346
[9382]: https://github.com/enso-org/enso/pull/9382
[9239]: https://github.com/enso-org/enso/pull/9239
[9343]: https://github.com/enso-org/enso/pull/9343
#### Enso Compiler

View File

@ -0,0 +1,584 @@
import project.Any.Any
import project.Data.Numeric.Internal.Decimal_Internal
import project.Data.Numeric.Math_Context.Math_Context
import project.Data.Numeric.Rounding_Mode.Rounding_Mode
import project.Data.Text.Text
import project.Error.Error
import project.Errors.Common.Arithmetic_Error
import project.Errors.Illegal_Argument.Illegal_Argument
import project.Nothing.Nothing
import project.Panic.Panic
import project.Warning.Warning
from project.Data.Boolean import Boolean, False, True
from project.Data.Numbers import Integer, Float, Number, Number_Parse_Error
from project.Data.Numeric.Internal.Decimal_Internal import Decimal_Comparator
from project.Data.Ordering import Comparable, Ordering
from project.Errors.Common import Loss_Of_Numeric_Precision
polyglot java import java.lang.ArithmeticException
polyglot java import java.lang.NumberFormatException
polyglot java import java.math.BigDecimal
polyglot java import org.enso.base.numeric.ConversionResult
polyglot java import org.enso.base.numeric.Decimal_Utils
## Decimal is the type of decimal (base-10-scaled) numbers in Enso. An Enso
`Decimal` is a wrapper around a Java `BigDecimal`.
`Decimal` values have more overhead than standard `Float` values, but they
have the advantage of being able to precisely specify values such as `0.1`,
which is important for certain kinds of calculations, such as currency.
A `Decimal` value is represented internally by a Java `BigInteger` "unscaled
value" and a "scale value". The numerical value of the `Decimal` is
`(unscaledValue * 10^(-scale))`. Scale values are maintained automatically by
the constructors and numerical operations.
Scale values can allow distinctions between values that would be identical as
`Float`s. For example, the following values have different internal
representations:
a = Decimal.new "2.0"
b = Decimal.new "2.00"
a == b
# => True
These two values have different internal representations, but they are still
considered the same value by `==`.
All of the constructors, and many of the operations, can take an additional
`Math_Context` value, which can specify two things:
- precision: the number of decimal digits to use to represent a value or
result; results are rounded to this precision.
- rounding_mode: the method to use for rounding. See `Rounding_Mode` for
details about each rounding method.
When a `Math_Context` value is used in an operation, and causes a loss of
precision, a `Loss_Of_Numeric_Precision` warning is attached to the result.
If no `Math_Context` value is supplied, the default is to make all operations
exact. A `Math_Context` with a precision of `0` has the same effect.
A `Decimal` can represent any `Float` precisely. However, some `Float`
literals, such as `0.1`, cannot be represented exactly to infinite
precision by a `Float`. For this reason, constructing a `Decimal` from
a `Float` always attaches a `Loss_Of_Numeric_Precision` warning to the
result. To avoid this problem, it is recommended to create `Decimal`s from
fractional values by passing the value in as a `Text`, where possible and
convenient.
The `Decimal` class provides arithmetic operations `add`, `subtract`,
`multiply`, and `divide`, which can take a `Math_Context` argument. You can
also use the usual operators `+`, `-`, `*`, `/`, which are the same as the
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
`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
an explicit precision using a `Math_Context`.
type Decimal
Value (big_decimal : BigDecimal)
## Construct a `Decimal` from a string or integer.
Arguments:
- x: The `Text`, `Integer`, or `Float` to construct a `Decimal` from.
- mc: The `Math_Context` to use to specify precision and `Rounding_Mode`.
If a `Math_Context` is used, there is a possibility of a loss of
precision.
? Number Format
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
- If the `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.
^ Example
Create a `Decimal` from a string.
c = Decimal.new "12.345"
new : Text | Integer | Float -> Math_Context -> Decimal ! Arithmetic_Error | Number_Parse_Error
new (x : Text | Integer | Float) (mc : Math_Context | Nothing = Nothing) -> Decimal | Number_Parse_Error =
handle_java_exception <|
case x of
_ : Text -> Decimal.parse x mc
_ : Integer -> Decimal.from_integer x mc
_ : Float -> Decimal.from_float x mc
## GROUP conversions
Construct a `Decimal` from a `Text`.
Arguments:
- s: The `Text` to construct a `Decimal` from.
- mc: The `Math_Context` to use to specify precision and `Rounding_Mode`.
If a `Math_Context` is used, there is a possibility of a loss of
precision.
? Number Format
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
- If `s` 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.
^ Example
Create a `Decimal` from a string.
d = Decimal.parse "12.345"
parse : Text -> Math_Context -> Decimal ! Arithmetic_Error | Number_Parse_Error
parse (s : Text) (mc : Math_Context | Nothing = Nothing) -> Decimal | Number_Parse_Error =
handle_java_exception <| handle_number_format_exception <|
case mc of
_ : Math_Context -> Decimal.Value <| handle_precision_loss s <| Decimal_Utils.fromString s mc.math_context
_ : Nothing -> Decimal.Value (Decimal_Utils.fromString s)
## GROUP conversions
Construct a `Decimal` from an `Integer`.
Arguments:
- i: The `Integer` to construct a `Decimal` from.
- mc: The `Math_Context` to use to specify precision and `Rounding_Mode`.
If a `Math_Context` is used, there is a possibility of a loss of
precision.
! Error Conditions
- 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.
^ Example
Create a `Decimal` from an integer.
d = Decimal.from_integer 12
from_integer : Integer -> Math_Context -> Decimal ! Arithmetic_Error
from_integer (i : Integer) (mc : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error =
handle_java_exception <|
case mc of
_ : Math_Context -> Decimal.Value <| handle_precision_loss i <| Decimal_Utils.fromInteger i mc.math_context
_ : Nothing -> Decimal.Value (Decimal_Utils.fromInteger i)
## GROUP conversions
Construct a `Decimal` from a `Float`.
Arguments:
- f: The `Float` to construct a `Decimal` from.
- mc: The `Math_Context` to use to specify precision and `Rounding_Mode`.
If a `Math_Context` is used, there is a possibility of a loss of
precision.
? Precision Loss
A `Decimal` can represent any `Float` precisely. However, some `Float`
literals, such as `0.1`, cannot be represented exactly to infinite
precision by a `Float`. For this reason, constructing a `Decimal` from
a `Float` always attaches a `Loss_Of_Numeric_Precision` warning to the
result.
! Error Conditions
- 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.
^ Example
Create a `Decimal` from a float.
d = Decimal.from_integer 12.345
from_float : Float -> Math_Context -> Decimal ! Arithmetic_Error ! Illegal_Argument
from_float (f : Float) (mc : Math_Context | Nothing = Nothing) -> 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)
## ALIAS greater than
GROUP Operators
ICON operators
Checks if this is greater than that.
Arguments:
- that: The number to compare this against.
> Example
Checking if 10 is greater than 7.
Decimal.new 10 > 7
# => True
> : Decimal -> Boolean
> self (that : Decimal) -> Boolean = Decimal_Comparator.compare self that == Ordering.Greater
## ALIAS greater than or equal
GROUP Operators
ICON operators
Checks if this is greater than or equal to that.
Arguments:
- that: The number to compare this against.
> Example
Checking if 10 is greater than or equal to 7.
Decimal.new 10 >= 7
# => True
>= : Decimal -> Boolean
>= self (that : Decimal) -> Boolean =
ordering = Decimal_Comparator.compare self that
ordering == Ordering.Greater || ordering == Ordering.Equal
## ALIAS less than
GROUP Operators
ICON operators
Checks if this is less than that.
Arguments:
- that: The number to compare this against.
> Example
Checking if 10 is less than 7.
Decimal.new 10 < 7
# => False
< : Decimal -> Boolean
< self (that : Decimal) -> Boolean = Decimal_Comparator.compare self that == Ordering.Less
## ALIAS less than or equal
GROUP Operators
ICON operators
Checks if this is less than or equal to that.
Arguments:
- that: The number to compare this against.
> Example
Checking if 10 is less than or equal to 7.
Decimal.new 10 <= 7
# => False
<= : Decimal -> Boolean
<= self (that : Decimal) -> Boolean =
ordering = Decimal_Comparator.compare self that
ordering == Ordering.Less || ordering == Ordering.Equal
## ALIAS plus
GROUP Operators
ICON math
Adds a `Decimal` to another `Decimal` or other kind of number. A
`Math_Context` value can be specified to set the precision and
`Rounding_Mode`.
Arguments:
- that: The number to add to this.
- math_context: Used to optionally specify precision and `Rounding_Mode`.
Addition in Enso will undergo automatic conversions such that you need
not convert other numeric types to `Decimal` manually.
> Example
Adding 10.22 and 20.33.
a = Decimal.new "10.22"
b = Decimal.new "20.33"
c = a.add b
# => Decimal.new 30.55
> Example
Adding 10.22 and 20.33, rounding to 3 digits of precision (1 decimal
place).
a = Decimal.new "10.22"
b = Decimal.new "20.33"
a.add b (Math_Context.new 3)
# => Decimal.new 30.5
add : Decimal -> Decimal -> Math_Context | Nothing -> Decimal ! Arithmetic_Error
add self (that : Decimal) (math_context : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error =
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)
## ALIAS plus
GROUP Operators
ICON math
Adds a `Decimal` to another `Decimal` or other kind of number.
Arguments:
- that: The number to add to this.
Addition in Enso will undergo automatic conversions such that you need
not convert other numeric types to `Decimal` manually.
> Example
Adding 10.22 and 20.33.
a = Decimal.new "10.22"
b = Decimal.new "20.33"
c = a + b
# => Decimal.new 30.55
+ : Decimal -> Decimal -> Decimal
+ self (that : Decimal) = self.add that
## ALIAS minus
GROUP Operators
ICON math
Subtract a `Decimal` or other kind of number from another `Decimal`, or
subtract a `Decimal` from another kind of number. A `Math_Context` value
can be specified to set the precision and `Rounding_Mode`.
Arguments:
- that: The number to subtract from this.
- math_context: Used to optionally specify precision and `Rounding_Mode`.
Subtraction in Enso will undergo automatic conversions such that you need
not convert other numeric types to `Decimal` manually.
> Example
Subtracting 10.22 from 20.33.
a = Decimal.new "20.33"
b = Decimal.new "10.22"
c = a.subtract b
# => Decimal.new 10.11
> Example
Subtracting 10.22 from 20.33, rounding to 3 digits of precision (1
decimal place).
a = Decimal.new "20.33"
b = Decimal.new "10.22"
c = a.subtract b (Math_Context.new 3)
# => Decimal.new 10.1
subtract : Decimal -> Decimal -> Math_Context | Nothing -> Decimal ! Arithmetic_Error
subtract self (that : Decimal) (math_context : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error =
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)
## ALIAS minus
GROUP Operators
ICON math
Subtract a `Decimal` or other kind of number from another `Decimal`, or
subtract a `Decimal` from another kind of number.
Arguments:
- that: The number to subtract from this.
Subtraction in Enso will undergo automatic conversions such that you need
not convert other numeric types to `Decimal` manually.
> Example
Subtracting 10.22 from 20.33.
a = Decimal.new "20.33"
b = Decimal.new "10.22"
c = a - b
# => Decimal.new 10.11
- : Decimal -> Decimal -> Decimal
- self (that : Decimal) = self.subtract that
## ALIAS times
GROUP Operators
ICON math
Multiplies a `Decimal` by another `Decimal` or other kind of number. A
`Math_Context` value can be specified to set the precision and
`Rounding_Mode`.
Arguments:
- that: The number to multiply by this.
- math_context: Used to optionally specify precision and `Rounding_Mode`.
Multiplication in Enso will undergo automatic conversions such that you need
not convert other numeric types to `Decimal` manually.
> Example
Multiplying 10.22 and 20.33.
a = Decimal.new "10.22"
b = Decimal.new "20.33"
c = a.multiply b
# => Decimal.new 207.7726
> Example
Multiplying 10.22 and 20.33, rounding to 4 digits of precision (1
decimal place).
a = Decimal.new "10.22"
b = Decimal.new "20.33"
c = a.multiply b (Math_Context.new 4)
# => Decimal.new 207.8
multiply : Decimal -> Decimal -> Math_Context | Nothing -> Decimal ! Arithmetic_Error
multiply self (that : Decimal) (math_context : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error =
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)
## ALIAS times
GROUP Operators
ICON math
Multiplies a `Decimal` by another `Decimal` or other kind of number.
Arguments:
- that: The number to multiply by this.
Multiplication in Enso will undergo automatic conversions such that you need
not convert other numeric types to `Decimal` manually.
> Example
Multiplying 10.22 and 20.33.
a = Decimal.new "10.22"
b = Decimal.new "20.33"
c = a * b
# => Decimal.new 207.7726
* : Decimal -> Decimal -> Decimal
* self (that : Decimal) = self.multiply that
## GROUP Operators
ICON math
Divide a `Decimal` by another `Decimal` or other kind of number, or
divide another kind of number by a `Decimal`. A `Math_Context` value can
be specified to set the precision and `Rounding_Mode`.
Arguments:
- that: The number to divide by this.
- math_context: Used to optionally specify precision and `Rounding_Mode`.
Division in Enso will undergo automatic conversions such that you need
not convert other numeric types to `Decimal` manually.
! Error Conditions
- If the precision specified in `math_context` is 0, or if
`match_context` is unspecified, and the quotient has a non-terminating
decimal expansion, an `Arithmetic_Error` is thrown.
> Example
Dividing 1065.9378 by 23.34.
a = Decimal.new "1065.9378"
b = Decimal.new "23.34"
c = a.divide b
# => Decimal.new 45.67
> Example
Dividing 1065.9378 by 23.34, rounding to 3 digits of precision (1
decimal place).
a = Decimal.new "1065.9378"
b = Decimal.new "23.34"
c = a.divide b (Math_Context.new 3)
# => Decimal.new 45.7
divide : Decimal -> Decimal -> Math_Context | Nothing -> Decimal | Arithmetic_Error
divide self (that : Decimal) (math_context : Math_Context | Nothing = Nothing) -> Decimal ! Arithmetic_Error =
handle_java_exception <|
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)
## GROUP Operators
ICON math
Divides a `Decimal` by another `Decimal` or other kind of number, or
divides another kind of number by a `Decimal`.
Arguments:
- that: The number to divide by this.
Division in Enso will undergo automatic conversions such that you need
not convert other numeric types to `Decimal` manually.
> Example
Dividing 1065.9378 by 23.34.
a = Decimal.new "1065.9378"
b = Decimal.new "23.34"
c = a / b
# => Decimal.new 45.67
/ : Decimal -> Decimal -> Decimal
/ self (that : Decimal) = self.divide that
## GROUP Operators
Compute the negation of this.
> Example
Negate 5.1 to get -5.1.
5.1.negate
# => Decimal.new -5.1
negate : Decimal
negate self = Decimal.Value self.big_decimal.negate
## PRIVATE
precision : Integer
precision self = self.big_decimal.precision
## PRIVATE
scale : Integer
scale self = self.big_decimal.scale
## PRIVATE
TODO: This +0 is needed to allow internal representations to be compared.
unscaled_value : Integer
unscaled_value self = self.big_decimal.unscaledValue + 0
## PRIVATE
internal_representation : [Integer]
internal_representation self = [self.unscaled_value, self.precision, self.scale]
## PRIVATE
to_text : Text
to_text self = self.big_decimal.toString
## PRIVATE
to_text_without_scientific_notation : Text
to_text_without_scientific_notation self = self.big_decimal.toPlainString
## PRIVATE
handle_number_format_exception ~action =
Panic.catch NumberFormatException action caught_panic->
Error.throw (Number_Parse_Error.Error caught_panic.payload.getMessage)
## PRIVATE
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
## PRIVATE
attach_loss_of_numeric_precision : Float -> Any -> Any
attach_loss_of_numeric_precision f value =
Warning.attach (Loss_Of_Numeric_Precision.Warning f f) value
handle_java_exception ~action =
Panic.catch ArithmeticException action caught_panic->
Error.throw (Arithmetic_Error.Error caught_panic.payload.getMessage)
## PRIVATE
Comparable.from (_ : Decimal) = Decimal_Comparator
## PRIVATE
Comparable.from (_ : Number) = Decimal_Comparator
## PRIVATE
Decimal.from (that : Text) = Decimal.parse that
## PRIVATE
Decimal.from (that : Number) = Decimal.new that

View File

@ -0,0 +1,14 @@
private
import project.Data.Decimal.Decimal
import project.Data.Numbers.Number
import project.Data.Text.Text
from project.Data.Ordering import Comparable, Ordering
polyglot java import org.enso.base.numeric.Decimal_Utils
## PRIVATE
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

View File

@ -0,0 +1,13 @@
private
from project.Data.Numbers import Float
polyglot java import java.lang.Double
## PRIVATE
Maximum representable positive value, from the underlying Java double type.
Float.max_value = Double.MAX_VALUE
## PRIVATE
Minimum representable positive value, from the underlying Java double type.
Float.min_value = Double.MIN_VALUE

View File

@ -0,0 +1,47 @@
import project.Data.Numeric.Rounding_Mode.Rounding_Mode
from project.Data.Numbers import Integer
polyglot java import java.math.MathContext
## Specifies infinite precision in a `Math_Context`.
> Example
Specify infinite precision in constructing a Decimal.
d1 = Decimal.new "1"
d3 = Decimal.new "3"
d4 = Decimal.new "4"
d1.divide d3 (Math_Context.new 12)
# => 0.333333333333
d1.divide d3 (Math_Context.new Unlimited)
# => Arithmetic_Error.Error 'Non-terminating decimal expansion; no exact representable decimal result.'
d1.divide d4 (Math_Context.new Unlimited)
# => 0.25
type Unlimited
## A wrapper around the Java
[`MathContext`](https://docs.oracle.com/javase/8/docs/api/java/math/MathContext.html)
class.
`Math_Context` is used to specify finite or infinite precision requirements
to operations that construct `Decimal` values, or that compute them from
other values. It can also specify the `Rounding_Mode` to use when rounding is
used to meet precision requirements. See `Decimal` for more details.
type Math_Context
## PRIVATE
Value (math_context:MathContext)
## Construct a `Math_Context` value.
Arguments:
- precision: The non-negative int precision setting. A value of `0`
specifies exact operations.
- rounding_mode: The rounding mode to use.
new : Integer -> Rounding_Mode -> Math_Context
new (precision : Integer | Unlimited) (rounding_mode : Rounding_Mode = Rounding_Mode.half_up) =
case precision of
_ : Integer -> Math_Context.Value (MathContext.new precision rounding_mode.rounding_mode)
_ : Unlimited -> Math_Context.Value (MathContext.new 0 rounding_mode.rounding_mode)

View File

@ -0,0 +1,20 @@
polyglot java import java.math.RoundingMode
## A wrapper around the Java
[`RoundingMode`](https://docs.oracle.com/javase/8/docs/api/java/math/RoundingMode.html)
class.
`Rounding_Mode` is used to specify the method to use for rounding a value in
operations on `Decimal`s.` See `Decimal` for more details.
type Rounding_Mode
Value (rounding_mode : RoundingMode)
# Round towards positive infinity for positive numbers, and negative
# infinity for negative numbers. (This is the default.)
half_up : Rounding_Mode
half_up = Rounding_Mode.Value RoundingMode.HALF_UP
## Rownd towards the nearest neighbor, with ties broken by rounding towards
the nearest even neighbor.
bankers : Rounding_Mode
bankers = Rounding_Mode.Value RoundingMode.HALF_EVEN

View File

@ -478,3 +478,19 @@ type Additional_Warnings
to_display_text : Text
to_display_text self =
"There were "+self.count.to_text+" additional issues."
type Loss_Of_Numeric_Precision
## PRIVATE
Indicates that a numeric conversion of a value has lost precision.
Warning original_value new_value
## PRIVATE
Create a human-readable version of the error.
to_display_text : Text
to_display_text self =
"Loss of precision in converting "+self.original_value.to_text+" to "+self.new_value.to_text
## PRIVATE
to_text : Text
to_text self =
"(Loss_Of_Numeric_Precision.Warning (original_value = "+self.original_value.to_text+") (new_value = " + self.new_value.to_text+"))"

View File

@ -2,6 +2,7 @@ import project.Any.Any
import project.Data
import project.Data.Array.Array
import project.Data.Boolean
import project.Data.Decimal.Decimal
import project.Data.Filter_Condition.Filter_Action
import project.Data.Filter_Condition.Filter_Condition
import project.Data.Index_Sub_Range.Index_Sub_Range
@ -15,6 +16,9 @@ import project.Data.Locale.Locale
import project.Data.Map.Map
import project.Data.Maybe.Maybe
import project.Data.Numbers
import project.Data.Numeric.Math_Context.Math_Context
import project.Data.Numeric.Math_Context.Unlimited
import project.Data.Numeric.Rounding_Mode.Rounding_Mode
import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Comparator
import project.Data.Ordering.Natural_Order
@ -101,6 +105,7 @@ from project.System.File_Format.Plain_Text_Format import Plain_Text
export project.Any.Any
export project.Data
export project.Data.Array.Array
export project.Data.Decimal.Decimal
export project.Data.Filter_Condition.Filter_Action
export project.Data.Filter_Condition.Filter_Condition
export project.Data.Index_Sub_Range.Index_Sub_Range
@ -113,6 +118,9 @@ export project.Data.List.List
export project.Data.Locale.Locale
export project.Data.Map.Map
export project.Data.Maybe.Maybe
export project.Data.Numeric.Math_Context.Math_Context
export project.Data.Numeric.Math_Context.Unlimited
export project.Data.Numeric.Rounding_Mode.Rounding_Mode
export project.Data.Ordering.Comparable
export project.Data.Ordering.Default_Comparator
export project.Data.Ordering.Natural_Order

View File

@ -541,7 +541,7 @@ class IrToTruffle(
true
)
val operators = ".!$%&*+-/<>?^~\\"
val operators = ".!$%&*+-/<>?^~\\="
def isOperator(n: Name): Boolean = {
n.name
.chars()

View File

@ -0,0 +1,6 @@
package org.enso.base.numeric;
import java.math.BigDecimal;
// Wraps the result of a conversion to BigDecimal with a precision loss flag.
public record ConversionResult(BigDecimal newValue, boolean hasPrecisionLoss) {}

View File

@ -0,0 +1,94 @@
package org.enso.base.numeric;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
/** Utils for the Enso Decmial type. */
public class Decimal_Utils {
private static final BigDecimal MIN_LONG_BIGDECIMAL = BigDecimal.valueOf(Long.MIN_VALUE);
private static final BigDecimal MAX_LONG_BIGDECIMAL = BigDecimal.valueOf(Long.MAX_VALUE);
public static BigDecimal fromString(String s) {
return new BigDecimal(s);
}
public static ConversionResult fromString(String s, MathContext mc) {
BigDecimal bd = new BigDecimal(s, mc);
BigDecimal withoutMC = new BigDecimal(s);
return new ConversionResult(bd, bd.compareTo(withoutMC) != 0);
}
public static BigDecimal fromInteger(Object o) {
if (o instanceof Long l) {
// According to the BigInteger Javadocs, valueOf is preferred "because it
// allows for reuse of frequently used BigDecimal values".
return BigDecimal.valueOf(l);
} else if (o instanceof BigInteger bi) {
return new BigDecimal(bi);
} else {
throw new IllegalArgumentException("Input must be Long or BigInteger");
}
}
public static ConversionResult fromInteger(Object o, MathContext mc) {
switch (o) {
case Long l -> {
BigDecimal bd = new BigDecimal(l, mc);
BigDecimal withoutMC = new BigDecimal(l);
long backToLong = bd.longValue();
return new ConversionResult(bd, bd.compareTo(withoutMC) != 0);
}
case BigInteger bi -> {
BigDecimal bd = new BigDecimal(bi, mc);
BigDecimal withoutMC = new BigDecimal(bi);
BigInteger backToBigInteger = bd.toBigInteger();
return new ConversionResult(bd, bd.compareTo(withoutMC) != 0);
}
case null, default -> {
throw new IllegalArgumentException("Input must be Long or BigInteger");
}
}
}
public static BigDecimal fromFloat(Double d) {
// According to the BigInteger Javadocs, valueOf is preferred because "the
// value returned is equal to that resulting from constructing a BigDecimal
// from the result of using Double.toString(double)."
return BigDecimal.valueOf(d);
}
public static ConversionResult fromFloat(Double d, MathContext mc) {
// We do not check for precision loss here because we always attach a
// warning when converting from float.
return new ConversionResult(new BigDecimal(d, mc), true);
}
public static boolean fitsInLong(BigDecimal bigDecimal) {
return bigDecimal.compareTo(MIN_LONG_BIGDECIMAL) >= 0
&& bigDecimal.compareTo(MAX_LONG_BIGDECIMAL) <= 0;
}
public static int hashCodeOf(BigDecimal bd) {
boolean isFractional = bd.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) != 0;
boolean fitsInLong = fitsInLong(bd);
if (isFractional || fitsInLong) {
double d = bd.doubleValue();
var fitsInDouble = Double.isFinite(d);
if (fitsInDouble) {
return Double.hashCode(d);
} else {
// Infinite values here just means finite values outside the double
// range. In this path, the values must be fractional, and so cannot
// coincide with a value of any other type (including BigInteger), so we
// can hash it however we want.
assert isFractional;
return bd.hashCode();
}
} else {
// Will not throw ArithmeticException since the value has a 0 fractional part.
assert !isFractional;
return bd.toBigIntegerExact().hashCode();
}
}
}

View File

@ -0,0 +1,498 @@
from Standard.Base import all
import Standard.Base.Data.Numeric.Internal.Numbers_Constants
import Standard.Base.Errors.Common.Arithmetic_Error
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
from Standard.Base.Data.Numbers import Number_Parse_Error
from Standard.Base.Errors.Common import Loss_Of_Numeric_Precision
from Standard.Test import all
polyglot java import org.enso.base.numeric.Decimal_Utils
Decimal.should_have_rep self rep = self.internal_representation . should_equal rep
add_specs suite_builder =
suite_builder.group "construction" group_builder->
group_builder.specify "should be able to construct a Decimal from a string" <|
Decimal.new "123.45" . should_have_rep [12345, 5, 2]
Decimal.parse "123.45" . should_have_rep [12345, 5, 2]
group_builder.specify "should throw Number_Parse_Error on a badly-formatted string" <|
Decimal.new "ee" . should_fail_with Number_Parse_Error
Decimal.new "--123.3.3" . should_fail_with Number_Parse_Error
group_builder.specify "should be able to construct a Decimal from a string, with an explicit precision, using the default rounding" <|
mc = Math_Context.new 4
Decimal.new "123.45" mc . should_have_rep [1235, 4, 1]
Decimal.new "123.45" mc . should_equal (Decimal.new "123.5")
group_builder.specify "should be able to construct a Decimal from a string, with an explicit precision, using a non-default rounding" <|
mc = Math_Context.new 4 Rounding_Mode.bankers
Decimal.new "123.45" mc . should_have_rep [1234, 4, 1]
Decimal.new "123.45" mc . should_equal (Decimal.new "123.4")
group_builder.specify "should be able to construct a Decimal from a long string" <|
Decimal.new "495782984723948723947239938732974241.2345" . should_have_rep [4957829847239487239472399387329742412345, 40, 4]
Decimal.parse "495782984723948723947239938732974241.2345" . should_have_rep [4957829847239487239472399387329742412345, 40, 4]
group_builder.specify "should be able to construct a Decimal from a small integer" <|
Decimal.new 1234500 . should_have_rep [1234500, 7, 0]
group_builder.specify "should be able to construct a Decimal from a small integer, with an explicit precision, using the default rounding" <|
mc = Math_Context.new 4
Decimal.new 1234500 mc . should_have_rep [1235, 4, -3]
Decimal.new 1234500 mc . should_equal (Decimal.new 1235000)
group_builder.specify "should be able to construct a Decimal from a small integer, with an explicit precision, using the non-default rounding" <|
mc = Math_Context.new 4 Rounding_Mode.bankers
Decimal.new 1234500 mc . should_have_rep [1234, 4, -3]
Decimal.new 1234500 mc . should_equal (Decimal.new 1234000)
group_builder.specify "should be able to construct a Decimal from a large integer" <|
Decimal.new 495782984723948723947239938732974241234500 . should_have_rep [495782984723948723947239938732974241234500 , 42, 0]
group_builder.specify "should be able to construct a Decimal from a large integer, with an explicit precision, using the default rounding" <|
mc = Math_Context.new 39
Decimal.new 495782984723948723947239938732974241234500 mc . should_have_rep [495782984723948723947239938732974241235 , 39, -3]
Decimal.new 495782984723948723947239938732974241234500 mc . should_equal (Decimal.new 495782984723948723947239938732974241235000 )
group_builder.specify "should be able to construct a Decimal from a large integer, with an explicit precision, using the non-default rounding" <|
mc = Math_Context.new 39 Rounding_Mode.bankers
Decimal.new 495782984723948723947239938732974241234500 mc . should_have_rep [495782984723948723947239938732974241234 , 39, -3]
Decimal.new 495782984723948723947239938732974241234500 mc . should_equal (Decimal.new 495782984723948723947239938732974241234000 )
group_builder.specify "should be able to construct a Decimal from a float" <|
d = Decimal.from_float 123.45
d.should_have_rep [12345, 5, 2]
group_builder.specify "should be able to construct a Decimal from a float, with an explicit precision, using the default rounding" <|
mc = Math_Context.new 5
d = Decimal.from_float 123.125 mc
d.should_have_rep [12313, 5, 2]
d.should_equal (Decimal.new "123.13")
group_builder.specify "should be able to construct a Decimal from a float, with an explicit precision, using a non-default rounding" <|
mc = Math_Context.new 5 Rounding_Mode.bankers
d = Decimal.from_float 123.125 mc
d.should_have_rep [12312, 5, 2]
d.should_equal (Decimal.new "123.12")
group_builder.specify "should be able to explicity and implicitly specify infinite precision" <|
Decimal.new "123.125" (Math_Context.new 5) . should_equal 123.13
Decimal.new "123.125" (Math_Context.new Unlimited) . should_equal 123.125
Decimal.new "123.125" (Math_Context.new 0) . should_equal 123.125
d1 = Decimal.new "1"
d3 = Decimal.new "3"
d4 = Decimal.new "4"
d1.divide d4 (Math_Context.new Unlimited) . should_equal 0.25
d1.divide d3 (Math_Context.new 12) . internal_representation . should_equal [333333333333, 12, 12]
d1.divide d3 . should_fail_with Arithmetic_Error
d1.divide d3 (Math_Context.new Unlimited) . should_fail_with Arithmetic_Error
group_builder.specify "should report precision loss accurately" <|
mc4 = Math_Context.new 4
mc5 = Math_Context.new 5
Problems.not_expect_warning (Decimal.new "123.25")
Problems.expect_warning Loss_Of_Numeric_Precision (Decimal.new "123.25" mc4)
Problems.not_expect_warning Loss_Of_Numeric_Precision (Decimal.new "123.25" mc5)
Problems.expect_warning Loss_Of_Numeric_Precision (Decimal.new 123.25)
Problems.expect_warning Loss_Of_Numeric_Precision (Decimal.new 123.25 mc4)
Problems.expect_warning Loss_Of_Numeric_Precision (Decimal.new 123.25 mc5)
Problems.not_expect_warning (Decimal.new 12325)
Problems.expect_warning Loss_Of_Numeric_Precision (Decimal.new 12325 mc4)
Problems.not_expect_warning Loss_Of_Numeric_Precision (Decimal.new 12325 mc5)
group_builder.specify "should throw Illegal_Argument for NaN/Inf" <|
Decimal.new Number.nan . should_fail_with Illegal_Argument
Decimal.new Number.positive_infinity . should_fail_with Illegal_Argument
Decimal.new Number.negative_infinity . should_fail_with Illegal_Argument
group_builder.specify "should be convertible via .from" <|
Decimal.from "123.45" . should_equal (Decimal.new "123.45")
Decimal.from "123.45" . should_equal (Decimal.parse "123.45")
Decimal.from 123.45 . should_equal (Decimal.new 123.45)
Decimal.from 123.45 . should_equal (Decimal.from_float 123.45)
Decimal.from 12345 . should_equal (Decimal.new 12345)
Decimal.from 12345 . should_equal (Decimal.from_integer 12345)
group_builder.specify "constructor should respect Math_Context " <|
Decimal.new 12000 (Math_Context.new 0) . should_equal 12000
Decimal.new 12000 (Math_Context.new 1) . should_equal 10000
Decimal.new 12000 (Math_Context.new 2) . should_equal 12000
Decimal.new 12000 (Math_Context.new 3) . should_equal 12000
suite_builder.group "comparison" group_builder->
group_builder.specify "should compare correctly" <|
nums = [["-45.23", "124.872"], [-45.23, 124.872], [-45, 124]]
nums.map pr->
a = Decimal.new (pr.at 0)
b = Decimal.new (pr.at 1)
(a == a) . should_be_true
(b == b) . should_be_true
(a == b) . should_be_false
(b == a) . should_be_false
(a != a) . should_be_false
(b != b) . should_be_false
(a != b) . should_be_true
(b != a) . should_be_true
(a <= a) . should_be_true
(b <= b) . should_be_true
(a <= b) . should_be_true
(b <= a) . should_be_false
(a >= a) . should_be_true
(b >= b) . should_be_true
(a >= b) . should_be_false
(b >= a) . should_be_true
(a < b) . should_be_true
(b > a) . should_be_true
(a > b) . should_be_false
(b < a) . should_be_false
(a <= b) . should_be_true
(b >= a) . should_be_true
(a >= b) . should_be_false
(b <= a) . should_be_false
(a < a) . should_be_false
(b < b) . should_be_false
(a > a) . should_be_false
(b > b) . should_be_false
a . should_equal a
b . should_equal b
a . should_not_equal b
b . should_not_equal a
group_builder.specify "should compare correctly, even with different internal representations" <|
a = Decimal.new "12000"
b = Decimal.new "12000" (Math_Context.new 2)
c = Decimal.new "12000" (Math_Context.new 3)
[[a, b], [a, c], [b, c]].map pr->
x0 = pr.at 0
x1 = pr.at 1
ir0 = x0.internal_representation
ir1 = x1.internal_representation
ir0 . should_not_equal ir1
(x0 == x1) . should_be_true
(x1 == x0) . should_be_true
(x0 != x1) . should_be_false
(x1 != x0) . should_be_false
group_builder.specify "should compare correctly to Integer and Float" <|
ok_values = []
+ [[0.1, 0.1]]
+ [["0.1", 0.1]]
+ [["0", 0]]
+ [["1", 1]]
+ [["-1", -1]]
+ [["2", 2]]
+ [["-2", -2]]
+ [["0", 0.0]]
+ [["1", 1.0]]
+ [["-1", -1.0]]
+ [["2", 2.0]]
+ [["-2", -2.0]]
+ [[0, 0]]
+ [[1, 1]]
+ [[-1, -1]]
+ [[2, 2]]
+ [[-2, -2]]
+ [[0, 0.0]]
+ [[1, 1.0]]
+ [[-1, -1.0]]
+ [[2, 2.0]]
+ [[-2, -2.0]]
+ [["12.34", 12.34]]
+ [["-34.56", -34.56]]
+ [["72775434512.34", 72775434512.34]]
+ [["-347757349.56784374", -347757349.56784374]]
+ [[12.34, 12.34]]
+ [[-34.56, -34.56]]
+ [[72775434512.34, 72775434512.34]]
+ [[-347757349.56784374, -347757349.56784374]]
+ [["2.0", 2.0]]
+ [["2.00", 2.0]]
+ [["2.000", 2.0]]
+ [[9223372036854770000.0, 9223372036854770000.0]]
+ [[9223372036854770000000.0, 9223372036854770000000.0]]
+ [[92233720368547700000000000000000000000.0, 92233720368547700000000000000000000000.0]]
+ [[9223372036854775805, 9223372036854775805]]
+ [[9223372036854775806, 9223372036854775806]]
+ [[9223372036854775807, 9223372036854775807]]
+ [[9223372036854775808, 9223372036854775808]]
+ [[9223372036854775809, 9223372036854775809]]
+ [[9223372036854775999, 9223372036854775999]]
+ [[9223372036854776000, 9223372036854776000]]
+ [[9223372036854776001, 9223372036854776001]]
+ [[9223372036854777000, 9223372036854777000]]
+ [[-9223372036854775810, -9223372036854775810]]
+ [[-9223372036854775809, -9223372036854775809]]
+ [[-9223372036854775808, -9223372036854775808]]
+ [[-9223372036854775807, -9223372036854775807]]
+ [[-9223372036854775807, -9223372036854775807]]
+ [[-9223372036854775806, -9223372036854775806]]
+ [[3946372036854775806000, 3946372036854775806000]]
+ [[3946372036854775807000, 3946372036854775807000]]
+ [[3946372036854775808000, 3946372036854775808000]]
+ [[-3946372036854775809000, -3946372036854775809000]]
+ [[-3946372036854775808000, -3946372036854775808000]]
+ [[-3946372036854775807000, -3946372036854775807000]]
+ [[39463720368547758060000000, 39463720368547758060000000]]
+ [[39463720368547758070000000, 39463720368547758070000000]]
+ [[39463720368547758080000000, 39463720368547758080000000]]
+ [[-39463720368547758090000000, -39463720368547758090000000]]
+ [[-39463720368547758080000000, -39463720368547758080000000]]
+ [[-39463720368547758070000000, -39463720368547758070000000]]
+ [["9223372036854775805", 9223372036854775805]]
+ [["9223372036854775806", 9223372036854775806]]
+ [["9223372036854775807", 9223372036854775807]]
+ [["9223372036854775808", 9223372036854775808]]
+ [["9223372036854775809", 9223372036854775809]]
+ [["9223372036854775999", 9223372036854775999]]
+ [["9223372036854776000", 9223372036854776000]]
+ [["9223372036854776001", 9223372036854776001]]
+ [["9223372036854777000", 9223372036854777000]]
+ [["-9223372036854775810", -9223372036854775810]]
+ [["-9223372036854775809", -9223372036854775809]]
+ [["-9223372036854775808", -9223372036854775808]]
+ [["-9223372036854775807", -9223372036854775807]]
+ [["-9223372036854775807", -9223372036854775807]]
+ [["-9223372036854775806", -9223372036854775806]]
+ [["3946372036854775806000", 3946372036854775806000]]
+ [["3946372036854775807000", 3946372036854775807000]]
+ [["3946372036854775808000", 3946372036854775808000]]
+ [["-3946372036854775809000", -3946372036854775809000]]
+ [["-3946372036854775808000", -3946372036854775808000]]
+ [["-3946372036854775807000", -3946372036854775807000]]
+ [["39463720368547758060000000", 39463720368547758060000000]]
+ [["39463720368547758070000000", 39463720368547758070000000]]
+ [["39463720368547758080000000", 39463720368547758080000000]]
+ [["-39463720368547758090000000", -39463720368547758090000000]]
+ [["-39463720368547758080000000", -39463720368547758080000000]]
+ [["-39463720368547758070000000", -39463720368547758070000000]]
+ [[Float.max_value, Float.max_value]]
+ [[-Float.max_value, -Float.max_value]]
+ [[Float.min_value, Float.min_value]]
+ [[-Float.min_value, -Float.min_value]]
+ [[Float.max_value-1.0, Float.max_value-1.0]]
+ [[-Float.max_value+1.0, -Float.max_value+1.0]]
problematic_values = []
+ [[9223372036854776000.0, 9223372036854776000.0]]
+ [[-9223372036854776000.0, -9223372036854776000.0]]
+ [["9223372036854776000.0", 9223372036854776000.0]]
+ [["-9223372036854776000.0", -9223372036854776000.0]]
# TODO: Include problematic values pending https://github.com/enso-org/enso/issues/9296.
values = ok_values # + problematic_values
_ = [problematic_values]
values.map pr->
v = pr.at 0
d = Decimal.new v
expected = pr.at 1
d . should_equal expected
expected . should_equal d
(d == expected) . should_be_true
(expected == d) . should_be_true
(d != expected) . should_be_false
(expected != d) . should_be_false
if v.is_a Text . not then
lesser = if v > 10000 then v/2 else if v < -10000 then v*2 else v-1
greater = if v > 10000 then v*2 else if v < -10000 then v/2 else v+1
(v > lesser) . should_be_true
(v < greater) . should_be_true
(d <= v) . should_be_true
(d >= v) . should_be_true
(v <= d) . should_be_true
(v >= d) . should_be_true
(d < d) . should_be_false
(d > d) . should_be_false
(d <= d) . should_be_true
(d >= d) . should_be_true
if greater.is_infinite.not then
(d < greater) . should_be_true
(d <= greater) . should_be_true
(greater > d) . should_be_true
(greater >= d) . should_be_true
(d > greater) . should_be_false
(d >= greater) . should_be_false
(greater < d) . should_be_false
(greater <= d) . should_be_false
if lesser.is_infinite.not then
(d > lesser) . should_be_true
(d >= lesser) . should_be_true
(lesser < d) . should_be_true
(lesser <= d) . should_be_true
(d < lesser) . should_be_false
(d <= lesser) . should_be_false
(lesser > d) . should_be_false
(lesser >= d) . should_be_false
suite_builder.group "edge cases" group_builder->
group_builder.specify "can support values outside the double range" <|
d = Decimal.new Float.max_value
(d == Float.max_value) . should_be_true
((d * d) == Float.max_value) . should_be_false
((-(d * d)) == -Float.max_value) . should_be_false
Comparable.hash_builtin (d * d) . should_equal -1851772048
Comparable.hash_builtin -(d * d) . should_equal 1851772048
Comparable.hash_builtin ((d * d) + 0.1) . should_equal -1176480326
Comparable.hash_builtin ((-(d * d)) + 0.1) . should_equal -534461530
suite_builder.group "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.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.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.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.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.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.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.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.new 213427523957 + Decimal.new 93849398884384) . should_equal (Decimal.new 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.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.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.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.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.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.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)
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.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.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.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.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.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.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.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)
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
(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
(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
(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
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
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
nd = -d
nd . should_equal -5
d2 = Decimal.new -5
nd2 = -d2
nd2 . should_equal 5
main =
suite = Test.build suite_builder->
add_specs suite_builder
suite.run_with_filter

View File

@ -27,6 +27,12 @@ type Complex
< self (that:Complex) = Complex_Comparator.compare self that == Ordering.Less
> self (that:Complex) = Complex_Comparator.compare self that == Ordering.Greater
>= self (that:Complex) =
ordering = Complex_Comparator.compare self that
ordering == Ordering.Greater || ordering == Ordering.Equal
<= self (that:Complex) =
ordering = Complex_Comparator.compare self that
ordering == Ordering.Less || ordering == Ordering.Equal
Complex.from (that:Number) = Complex.new that
@ -570,6 +576,16 @@ add_specs suite_builder =
3.truncate . should_equal 3
-3.truncate . should_equal -3
suite_builder.group "Hashing" group_builder->
# TODO: Pending https://github.com/enso-org/enso/issues/9296
group_builder.specify "Hash consistency for values near Double.MIN/MAX_VALUE" pending="hash consistency" <|
ok_values = [9223372036854775000, -9223372036854775000]
bad_values = [9223372036854776000, -9223372036854776000]
values = ok_values + bad_values
values.map i->
f = i.to_float
(Comparable.hash_builtin f) . should_equal (Comparable.hash_builtin i)
suite_builder.group "Number Conversions" group_builder->
group_builder.specify "Complex plus Integer" <|
v1 = (Complex.new 1 2) + (Complex.new 3)
@ -623,7 +639,13 @@ add_specs suite_builder =
v1>v2 . should_be_false
v2>v1 . should_be_true
group_builder.specify "Greater or equal of complex and number" <|
v1<=v2 . should_be_true
v2<=v1 . should_be_false
v1>=v2 . should_be_false
v2>=v1 . should_be_true
group_builder.specify "Greater or equal of complex < number" <|
v1 = (Complex.new 3)
v2 = 4
v1<v2 . should_be_true
@ -632,7 +654,13 @@ add_specs suite_builder =
v1>v2 . should_be_false
v2>v1 . should_be_true
group_builder.specify "Greater or equal of number and complex" <|
v1<=v2 . should_be_true
v2<=v1 . should_be_false
v1>=v2 . should_be_false
v2>=v1 . should_be_true
group_builder.specify "Greater or equal of complex > number" <|
v1 = 3
v2 = (Complex.new 4)
v1<v2 . should_be_true
@ -641,6 +669,27 @@ add_specs suite_builder =
v1>v2 . should_be_false
v2>v1 . should_be_true
v1<=v2 . should_be_true
v2<=v1 . should_be_false
v1>=v2 . should_be_false
v2>=v1 . should_be_true
group_builder.specify "Greater or equal of complex == number" <|
v1 = 4
v2 = (Complex.new 4)
v1<v2 . should_be_false
v2<v1 . should_be_false
v1>v2 . should_be_false
v2>v1 . should_be_false
v1<=v2 . should_be_true
v2<=v1 . should_be_true
v1>=v2 . should_be_true
v2>=v1 . should_be_true
suite_builder.group "BigInts" group_builder->
expected_value = 2002115494039257055317447151023464523838443110452722331466645440244415760562579268801894716412