From f7a931c595100c6c192c0e478ef5443422c307d2 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 8 Jan 2018 01:19:37 +0100 Subject: [PATCH] Extend BigDecimal with a few things (#5390) --- spec/std/big/big_decimal_spec.cr | 26 +++- src/big/big_decimal.cr | 196 +++++++++++++++++++++++-------- 2 files changed, 171 insertions(+), 51 deletions(-) diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr index b5202fe59d29..9ead1fc33453 100644 --- a/spec/std/big/big_decimal_spec.cr +++ b/spec/std/big/big_decimal_spec.cr @@ -159,6 +159,19 @@ describe BigDecimal do BigDecimal.new(3333333.to_big_i, 7_u64).should eq(BigDecimal.new(1).div(BigDecimal.new(3), 7)) BigDecimal.new(3333.to_big_i, 7_u64).should eq(BigDecimal.new(1).div(BigDecimal.new(3000), 7)) + + (-BigDecimal.new(3)).should eq(BigDecimal.new(-3)) + end + + it "performs arithmetic with other number types" do + (1.to_big_d + 2).should eq(BigDecimal.new("3.0")) + (2 + 1.to_big_d).should eq(BigDecimal.new("3.0")) + (2.to_big_d - 1).should eq(BigDecimal.new("1.0")) + (2 - 1.to_big_d).should eq(BigDecimal.new("1.0")) + (1.to_big_d * 2).should eq(BigDecimal.new("2.0")) + (1 * 2.to_big_d).should eq(BigDecimal.new("2.0")) + (3.to_big_d / 2).should eq(BigDecimal.new("1.5")) + (3 / 2.to_big_d).should eq(BigDecimal.new("1.5")) end it "can be converted from other types" do @@ -166,11 +179,14 @@ describe BigDecimal do "1.5".to_big_d.should eq (BigDecimal.new(15, 1)) BigInt.new(15).to_big_d.should eq (BigDecimal.new(15, 0)) 1.5.to_big_d.should eq (BigDecimal.new(15, 1)) + 1.5.to_big_f.to_big_d.should eq (BigDecimal.new(15, 1)) end it "is comparable with other types" do BigDecimal.new("1.0").should eq BigDecimal.new("1") BigDecimal.new("1").should eq BigDecimal.new("1.0") + 1.should_not eq BigDecimal.new("-1.0") + 1.should_not eq BigDecimal.new("0.1") BigDecimal.new(1, 10).should eq BigDecimal.new(10, 11) BigDecimal.new(10, 11).should eq BigDecimal.new(1, 10) @@ -179,8 +195,6 @@ describe BigDecimal do (BigDecimal.new("0.99999999999999999999999999999999999999") > BigDecimal.new(1)).should be_false BigDecimal.new("1.00000000000000000000000000000000000000").should eq BigDecimal.new(1) - (1 < BigDecimal.new(1)).should be_false - (1 < BigDecimal.new(1)).should be_false (BigDecimal.new(1) < 1).should be_false (2 < BigDecimal.new(1)).should be_false @@ -199,6 +213,14 @@ describe BigDecimal do (1 <= BigDecimal.new(1)).should be_true (0 <= BigDecimal.new(1)).should be_true + (BigDecimal.new("6.5") < 6.6).should be_true + (6.6 > BigDecimal.new("6.5")).should be_true + (BigDecimal.new("7.5") > 6.6).should be_true + (6.6 < BigDecimal.new("7.5")).should be_true + + BigDecimal.new("6.6").should eq(6.6) + 6.6.should eq(BigDecimal.new("6.6")) + (BigDecimal.new("6.5") > 7).should be_false (BigDecimal.new("7.5") > 6).should be_true end diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index 172bffba9739..837d6934c217 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -18,12 +18,29 @@ struct BigDecimal < Number TEN = BigInt.new(10) DEFAULT_MAX_DIV_ITERATIONS = 100_u64 - include Comparable(Number) + include Comparable(Int) include Comparable(BigDecimal) + include Comparable(Float) getter value : BigInt getter scale : UInt64 + # Returns *num*. Useful for generic code that does `T.new(...)` with `T` + # being a `Number`. + def self.new(num : BigDecimal) + num + end + + # Creates a new `BigDecimal` from `BigInt` *value* and `UInt64` *scale*, + # which matches the internal representation. + def initialize(@value : BigInt, @scale : UInt64) + end + + # Creates a new `BigDecimal` from `Int`. + def initialize(num : Int = 0, scale : Int = 0) + initialize(num.to_big_i, scale.to_u64) + end + # Creates a new `BigDecimal` from a `String`. # # Allows only valid number strings with an optional negative sign. @@ -66,38 +83,16 @@ struct BigDecimal < Number end end - # Creates an new `BigDecimal` from `Int`. - def initialize(num : Int) - initialize(num.to_big_i, 0) - end - - # Creating a `BigDecimal` from `Float`. + # Creates a new `BigDecimal` from `Float`. # - # NOTE: Floats are fundamentally less precise than BigDecimals, which makes initialization from them risky. + # NOTE: Floats are fundamentally less precise than BigDecimals, + # which makes initialization from them risky. def initialize(num : Float) - initialize num.to_s - end - - # Creates a new `BigDecimal` from `BigInt`/`UInt64`, which matches the internal representation. - def initialize(@value : BigInt, @scale : UInt64) - end - - def initialize(value : Int, scale : Int) - initialize(value.to_big_i, scale.to_u64) - end - - def initialize(value : BigInt) - initialize(value, 0u64) - end - - def initialize - initialize(0) + initialize(num.to_s) end - # Returns *num*. Useful for generic code that does `T.new(...)` with `T` - # being a `Number`. - def self.new(num : BigDecimal) - num + def - : BigDecimal + BigDecimal.new(-@value, @scale) end def +(other : BigDecimal) : BigDecimal @@ -112,6 +107,10 @@ struct BigDecimal < Number end end + def +(other : Int) + self + BigDecimal.new(other) + end + def -(other : BigDecimal) : BigDecimal if @scale > other.scale scaled = other.scale_to(self) @@ -124,15 +123,27 @@ struct BigDecimal < Number end end + def -(other : Int) + self - BigDecimal.new(other) + end + def *(other : BigDecimal) : BigDecimal BigDecimal.new(@value * other.value, @scale + other.scale) end + def *(other : Int) + self * BigDecimal.new(other) + end + def /(other : BigDecimal) : BigDecimal div other end - # Divides self with another `BigDecimal`, with a optionally configurable *max_div_iterations*, which + def /(other : Int) + self / BigDecimal.new(other) + end + + # Divides `self` with another `BigDecimal`, with a optionally configurable *max_div_iterations*, which # defines a maximum number of iterations in case the division is not exact. # # ``` @@ -151,7 +162,6 @@ struct BigDecimal < Number end remainder = remainder * TEN - i = 0 while remainder != ZERO && i < max_div_iterations inner_quotient, inner_remainder = remainder.divmod(denominator) @@ -173,7 +183,7 @@ struct BigDecimal < Number end end - def <=>(other : Int) + def <=>(other : Int | Float) self <=> BigDecimal.new(other) end @@ -209,8 +219,8 @@ struct BigDecimal < Number def to_s(io : IO) factor_powers_of_ten - s = @value.to_s + s = @value.to_s if @scale == 0 io << s return @@ -234,27 +244,92 @@ struct BigDecimal < Number end end + def inspect(io) + to_s(io) + io << "_big_d" + end + def to_big_d self end - # Converts to integer. Truncates anything on the right side of the decimal point. - def to_i + # Converts to `Int64`. Truncates anything on the right side of the decimal point. + def to_i64 if @value >= 0 - (@value / TEN ** @scale).to_i + (@value / TEN ** @scale).to_i64 else - -(@value.abs / TEN ** @scale).to_i + -(@value.abs / TEN ** @scale).to_i64 end end - # Converts to unsigned integer. Truncates anything on the right side of the decimal point, + # Converts to `Int32`. Truncates anything on the right side of the decimal point. + def to_i32 + to_i64.to_i32 + end + + # Converts to `Int16`. Truncates anything on the right side of the decimal point. + def to_i16 + to_i64.to_i16 + end + + # Converts to `Int8`. Truncates anything on the right side of the decimal point. + def to_i8 + to_i64.to_i8 + end + + # Converts to `Int32`. Truncates anything on the right side of the decimal point. + def to_i + to_i32 + end + + # Converts to `UInt64`. Truncates anything on the right side of the decimal point, + # converting negative to positive. + def to_u64 + (@value.abs / TEN ** @scale).to_u64 + end + + # Converts to `UInt32`. Truncates anything on the right side of the decimal point, + # converting negative to positive. + def to_u32 + to_u64.to_u32 + end + + # Converts to `UInt16`. Truncates anything on the right side of the decimal point, + # converting negative to positive. + def to_u16 + to_u64.to_u16 + end + + # Converts to `UInt8`. Truncates anything on the right side of the decimal point, + # converting negative to positive. + def to_u8 + to_u64.to_u8 + end + + # Converts to `UInt32`. Truncates anything on the right side of the decimal point, # converting negative to positive. def to_u - (@value.abs / TEN ** @scale).to_u + to_u32 + end + + # Converts to `Float64`. + def to_f64 + to_s.to_f64 end + # Converts to `Float32`. + def to_f32 + to_f64.to_f32 + end + + # Converts to `Float64`. def to_f - to_s.to_f + to_f64 + end + + # Converts to `BigFloat`. + def to_big_f + BigFloat.new(to_s) end def clone @@ -262,11 +337,11 @@ struct BigDecimal < Number end def hash(hasher) - hasher.string(self.to_s) + hasher.string(to_s) end - # Returns the *quotient* as absolutely negative if self and other have different signs, - # otherwise returns the *quotient*. + # Returns the *quotient* as absolutely negative if `self` and *other* have + # different signs, otherwise returns the *quotient*. def normalize_quotient(other : BigDecimal, quotient : BigInt) : BigInt if (@value < 0 && other.value > 0) || (other.value < 0 && @value > 0) -quotient.abs @@ -299,27 +374,50 @@ end struct Int include Comparable(BigDecimal) - # Convert `Int` to `BigDecimal`. + # Converts `self` to `BigDecimal`. def to_big_d BigDecimal.new(self) end def <=>(other : BigDecimal) - self <=> other.value + to_big_d <=> other + end + + def +(other : BigDecimal) + other + self + end + + def -(other : BigDecimal) + to_big_d - other + end + + def *(other : BigDecimal) + other * self + end + + def /(other : BigDecimal) + to_big_d / other end end -class String +struct Float include Comparable(BigDecimal) - # Convert `String` to `BigDecimal`. + def <=>(other : BigDecimal) + to_big_d <=> other + end + + # Converts `self` to `BigDecimal`. + # + # NOTE: Floats are fundamentally less precise than BigDecimals, + # which makes conversion to them risky. def to_big_d BigDecimal.new(self) end end -struct Float - # NOTE: Floats are fundamentally less precise than BigDecimals, which makes initialization from them risky. +class String + # Converts `self` to `BigDecimal`. def to_big_d BigDecimal.new(self) end