Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various Decimal usability tweaks #10517

Merged
merged 19 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 79 additions & 29 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Decimal.enso
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ type Decimal
Value (big_decimal : BigDecimal)

## ICON input_number
Construct a `Decimal` from a string or integer.
Construct a `Decimal` from a string, integer or float.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Construct a `Decimal` from a string, integer or float.
Construct a `Decimal` from a Text, Integer or Float.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


Arguments:
- x: The `Text`, `Integer`, or `Float` to construct a `Decimal` from.
Expand All @@ -114,11 +114,21 @@ type Decimal
Create a `Decimal` from a string.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Create a `Decimal` from a string.
Create a `Decimal` from a Text.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


c = Decimal.new "12.345"

^ Example
Create a `Decimal` from an integer.

c = Decimal.new 12345

^ Example
Create a `Decimal` from a float.

c = Decimal.new 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_string x mc
_ : Text -> Decimal.from_text x mc
_ : Integer -> Decimal.from_integer x mc
_ : Float -> Decimal.from_float x mc

Expand Down Expand Up @@ -147,9 +157,9 @@ type Decimal
^ Example
Create a `Decimal` from a string.

d = Decimal.from_string "12.345"
from_string : Text -> Math_Context | Nothing -> Decimal ! Number_Parse_Error
from_string (s : Text) (mc : Math_Context | Nothing = Nothing) -> Decimal ! Number_Parse_Error =
d = Decimal.from_text "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 <|
case mc of
_ : Math_Context -> Decimal.Value <| handle_precision_loss s <| Decimal_Utils.fromString s mc.math_context
Expand Down Expand Up @@ -343,7 +353,7 @@ type Decimal
c = a + b
# => Decimal.new 30.55
+ : Decimal -> Decimal
+ self (that : Decimal) = self.add that
+ self (that : Decimal) -> Decimal ! Arithmetic_Error = self.add that

## ALIAS minus
GROUP Operators
Expand Down Expand Up @@ -402,7 +412,7 @@ type Decimal
c = a - b
# => Decimal.new 10.11
- : Decimal -> Decimal
- self (that : Decimal) = self.subtract that
- self (that : Decimal) -> Decimal ! Arithmetic_Error = self.subtract that

## ALIAS times
GROUP Operators
Expand Down Expand Up @@ -460,7 +470,7 @@ type Decimal
c = a * b
# => Decimal.new 207.7726
* : Decimal -> Decimal
* self (that : Decimal) = self.multiply that
* self (that : Decimal) -> Decimal ! Arithmetic_Error = self.multiply that

## GROUP Operators
ICON math
Expand Down Expand Up @@ -524,7 +534,7 @@ type Decimal
c = a / b
# => Decimal.new 45.67
/ : Decimal -> Decimal
/ self (that : Decimal) = self.divide that
/ self (that : Decimal) -> Decimal ! Arithmetic_Error = self.divide that

## ALIAS modulo, modulus
GROUP Operators
Expand Down Expand Up @@ -554,7 +564,7 @@ type Decimal
remainder = Decimal.new -5 . remainder 3
# => -2
remainder : Decimal -> Decimal
remainder self (that : Decimal) =
remainder self (that : Decimal) -> Decimal =
handle_java_exception <|
Decimal.Value (self.big_decimal.remainder that.big_decimal)

Expand Down Expand Up @@ -586,7 +596,7 @@ type Decimal
remainder = Decimal.new -5 % 3
# => -2
% : Decimal -> Decimal
% self (that : Decimal) = self.remainder that
% self (that : Decimal) -> Decimal = self.remainder that

## GROUP Math
ICON math
Expand Down Expand Up @@ -640,7 +650,7 @@ type Decimal
Decimal.new "2.25" . pow (Decimal.new "5")
# => 57.6650390625
pow : Integer -> Decimal
pow self exp:Integer =
pow self exp:Integer -> Decimal =
## If `exp` is an integer that does not fit in a Java Integer,
UnsuppUnsupported_Argument_Types is raised, so we convert that to an
Arithmetic_Error.
Expand All @@ -667,7 +677,7 @@ type Decimal
Decimal.new "2.25" ^ Decimal.new "5"
# => 57.6650390625
^ : Integer -> Decimal
^ self exp:Integer = self.pow exp
^ self exp:Integer -> Decimal = self.pow exp

## GROUP Operators
ICON operators
Expand All @@ -679,7 +689,7 @@ type Decimal
5.1.negate
# => Decimal.new -5.1
negate : Decimal
negate self = Decimal.Value self.big_decimal.negate
negate self -> Decimal = Decimal.Value self.big_decimal.negate

## GROUP Math
ICON math
Expand Down Expand Up @@ -754,7 +764,7 @@ type Decimal
d.to_integer
# => 2345
to_integer : Integer
to_integer self =
to_integer self -> Integer =
i = self.big_decimal.toBigInteger
if self == i then i else
Warning.attach (Loss_Of_Numeric_Precision.Warning self i) i
Expand Down Expand Up @@ -791,8 +801,8 @@ type Decimal
d = Decimal.new "23.45"
d.to_float
# => 23.45
to_float : Integer
to_float self =
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
message = "Outside representable Float range (approximately (-1.8E308, 1.8E308))"
Expand Down Expand Up @@ -942,7 +952,7 @@ type Decimal
@format make_number_format_selector
@locale Locale.default_widget
format : Text -> Locale -> Text
format self format:Text="" locale:Locale=Locale.default =
format self format:Text="" locale:Locale=Locale.default -> Text =
symbols = DecimalFormatSymbols.new locale.java_locale
formatter = DecimalFormat.new format symbols
formatter.format self.big_decimal
Expand Down Expand Up @@ -986,50 +996,90 @@ type Decimal
Decimal.parse "123.456.789,87654" locale=Locale.italy
# => 123456789.87654
parse : Text -> Locale | Nothing -> Decimal ! Number_Parse_Error
parse text locale:(Locale | Nothing)=Nothing = case locale of
Nothing -> Decimal.from_string text
parse text locale:(Locale | Nothing)=Nothing -> Decimal ! Number_Parse_Error = case locale of
Nothing -> Decimal.from_text text
Locale.Value java_locale -> Panic.catch ParseException ((NumberFormat.getInstance java_locale).parse text) _->
Error.throw (Number_Parse_Error.Error text)

## PRIVATE
precision : Integer
precision self = self.big_decimal.precision
precision self -> Integer = self.big_decimal.precision

## PRIVATE
scale : Integer
scale self = self.big_decimal.scale
scale self -> Integer = self.big_decimal.scale

## PRIVATE
with_scale : Integer -> Decimal
private with_scale self new_scale:Integer =
private with_scale self new_scale:Integer -> Decimal =
if self.scale == new_scale then self else
Decimal.Value (self.big_decimal.setScale new_scale)

## PRIVATE
unscaled_value : Integer
unscaled_value self = self.big_decimal.unscaledValue
unscaled_value self -> Integer = self.big_decimal.unscaledValue

## PRIVATE
internal_representation : [Integer]
internal_representation self = [self.unscaled_value, self.precision, self.scale]
internal_representation self -> [Integer] = [self.unscaled_value, self.precision, self.scale]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is still valid. Probably accepted by the compiler, but I have no idea what it will do.

I think we should use

Suggested change
internal_representation self -> [Integer] = [self.unscaled_value, self.precision, self.scale]
internal_representation self -> Vector Integer = [self.unscaled_value, self.precision, self.scale]

instead, as that's what is used most of the time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what happens when you write in other languages on the weekend.


## PRIVATE
Note: the underlying Java `BigDecimal` implementation is not affected by
locale.
to_text : Text
to_text self = self.big_decimal.toString
to_text self -> Text = self.big_decimal.toString

## PRIVATE
Note: the underlying Java `BigDecimal` implementation is not affected by
locale.
to_display_text : Text
to_display_text self = self.big_decimal.toString
to_display_text self -> Text = self.big_decimal.toString

## PRIVATE
Note: the underlying Java `BigDecimal` implementation is not affected by
locale.
to_text_without_scientific_notation : Text
to_text_without_scientific_notation self = self.big_decimal.toPlainString
to_text_without_scientific_notation self -> Text = self.big_decimal.toPlainString

## ICON input_number
Construct a `Decimal` from a string, integer or float.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume as @jdunkerley suggested we probably also want to replace string with Text here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


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 = dec "12.345"

^ Example
Create a `Decimal` from an integer.

c = dec 12345

^ Example
Create a `Decimal` from a float.

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 =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what we should do with Math_Context | Nothing - won't play nicely with the widgets.
One to think about in the decimal refinement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we just add 2 constructors to Math_Context? e.g. Custom and Default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decimal.new x mc

## PRIVATE
handle_number_format_exception ~action =
Expand Down Expand Up @@ -1065,7 +1115,7 @@ Comparable.from (_ : Decimal) = Decimal_Comparator
Comparable.from (_ : Number) = Decimal_Comparator

## PRIVATE
Decimal.from (that : Text) = Decimal.from_string that
Decimal.from (that : Text) = Decimal.from_text that

## PRIVATE
Decimal.from (that : Integer) = Decimal.new that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Decimal.from (that:JS_Object) =
case that.get "type" == "Decimal" && ["value", "scale", "precision"].all that.contains_key of
True ->
math_context = Math_Context.new (that.at "precision")
raw_value = Decimal.from_string (that.at "value") math_context
raw_value = Decimal.from_text (that.at "value") math_context
raw_value.with_scale (that.at "scale")
False -> Error.throw (Illegal_Argument.Error "Invalid JS_Object for Decimal.")

Expand Down
1 change: 1 addition & 0 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export project.System.Process
export project.System.Process.Exit_Code.Exit_Code
export project.Warning.Warning
from project.Data.Boolean export Boolean, False, True
from project.Data.Decimal export dec
from project.Data.Json.Extensions export all
from project.Data.Numbers export Float, Integer, Number
from project.Data.Range.Extensions export all
Expand Down
Loading