Skip to content

Commit

Permalink
Extend BigDecimal with few a things
Browse files Browse the repository at this point in the history
  • Loading branch information
Sija committed Dec 18, 2017
1 parent a1e90f0 commit 2dfb2e2
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 28 deletions.
48 changes: 46 additions & 2 deletions spec/std/big/big_decimal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,42 @@ 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"))
(1.3.to_big_d + 2).should eq(BigDecimal.new("3.3"))
(1.3.to_big_d + 2.4).should eq(BigDecimal.new("3.7"))

(2 + 1.to_big_d).should eq(BigDecimal.new("3.0"))
(1.3 + 2.to_big_d).should eq(BigDecimal.new("3.3"))
(1.3 + 2.4.to_big_d).should eq(BigDecimal.new("3.7"))

(2.to_big_d - 1).should eq(BigDecimal.new("1.0"))
(2.to_big_d - 1.3).should eq(BigDecimal.new("0.7"))
(2.4.to_big_d - 1.3).should eq(BigDecimal.new("1.1"))

(2 - 1.to_big_d).should eq(BigDecimal.new("1.0"))
(2 - 1.3.to_big_d).should eq(BigDecimal.new("0.7"))
(2.4 - 1.3.to_big_d).should eq(BigDecimal.new("1.1"))

(1.to_big_d * 2).should eq(BigDecimal.new("2.0"))
(1.3.to_big_d * 2).should eq(BigDecimal.new("2.6"))
(1.3.to_big_d * 2.4).should eq(BigDecimal.new("3.12"))

(1 * 2.to_big_d).should eq(BigDecimal.new("2.0"))
(1.3 * 2.to_big_d).should eq(BigDecimal.new("2.6"))
(1.3 * 2.4.to_big_d).should eq(BigDecimal.new("3.12"))

(3.to_big_d / 2).should eq(BigDecimal.new("1.5"))
(3.3.to_big_d / 3).should eq(BigDecimal.new("1.1"))
(3.3.to_big_d / 1.1).should eq(BigDecimal.new("3"))

(3 / 2.to_big_d).should eq(BigDecimal.new("1.5"))
(3.3 / 3.to_big_d).should eq(BigDecimal.new("1.1"))
(3.3 / 1.1.to_big_d).should eq(BigDecimal.new("3"))
end

it "can be converted from other types" do
Expand All @@ -171,6 +207,8 @@ describe BigDecimal do
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)

Expand All @@ -179,8 +217,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
Expand All @@ -199,6 +235,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
Expand Down
150 changes: 124 additions & 26 deletions src/big/big_decimal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ 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
Expand Down Expand Up @@ -66,19 +67,19 @@ struct BigDecimal < Number
end
end

# Creates an new `BigDecimal` from `Int`.
# Creates a 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.
def initialize(num : Float)
initialize num.to_s
end

# Creates a new `BigDecimal` from `BigInt`/`UInt64`, which matches the internal representation.
# Creates a new `BigDecimal` from `BigInt` / `UInt64`, which matches the internal representation.
def initialize(@value : BigInt, @scale : UInt64)
end

Expand All @@ -87,7 +88,7 @@ struct BigDecimal < Number
end

def initialize(value : BigInt)
initialize(value, 0u64)
initialize(value, 0_u64)
end

def initialize
Expand All @@ -100,6 +101,10 @@ struct BigDecimal < Number
num
end

def - : BigDecimal
BigDecimal.new(-@value, @scale)
end

def +(other : BigDecimal) : BigDecimal
if @scale > other.scale
scaled = other.scale_to(self)
Expand All @@ -112,6 +117,10 @@ struct BigDecimal < Number
end
end

def +(other : Int | Float)
self + BigDecimal.new(other)
end

def -(other : BigDecimal) : BigDecimal
if @scale > other.scale
scaled = other.scale_to(self)
Expand All @@ -124,15 +133,27 @@ struct BigDecimal < Number
end
end

def -(other : Int | Float)
self - BigDecimal.new(other)
end

def *(other : BigDecimal) : BigDecimal
BigDecimal.new(@value * other.value, @scale + other.scale)
end

def *(other : Int | Float)
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 | Float)
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.
#
# ```
Expand Down Expand Up @@ -173,7 +194,7 @@ struct BigDecimal < Number
end
end

def <=>(other : Int)
def <=>(other : Int | Float)
self <=> BigDecimal.new(other)
end

Expand Down Expand Up @@ -238,35 +259,95 @@ struct BigDecimal < Number
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
self
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
Expand Down Expand Up @@ -296,30 +377,47 @@ struct BigDecimal < Number
end
end

struct Int
struct Number
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
end

class String
include Comparable(BigDecimal)
def +(other : BigDecimal)
other + self
end

def -(other : BigDecimal)
to_big_d - other
end

# Convert `String` to `BigDecimal`.
def *(other : BigDecimal)
other * self
end

def /(other : BigDecimal)
to_big_d / other
end
end

struct Float
# 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
Expand Down

0 comments on commit 2dfb2e2

Please sign in to comment.