Skip to content

Commit

Permalink
Extend BigDecimal with a few things
Browse files Browse the repository at this point in the history
  • Loading branch information
Sija committed Jan 5, 2018
1 parent 50b563c commit 59871f2
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 54 deletions.
26 changes: 24 additions & 2 deletions spec/std/big/big_decimal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,34 @@ 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
1.to_big_d.should eq (BigDecimal.new(1))
"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)

Expand All @@ -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
Expand All @@ -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
Expand Down
196 changes: 144 additions & 52 deletions src/big/big_decimal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
initialize(num.to_s)
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)
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
Expand All @@ -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)
Expand All @@ -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.
#
# ```
Expand All @@ -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)
Expand All @@ -173,7 +183,7 @@ struct BigDecimal < Number
end
end

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

Expand Down Expand Up @@ -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
Expand All @@ -234,39 +244,104 @@ 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
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 +371,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

def *(other : BigDecimal)
other * self
end

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

# Convert `String` to `BigDecimal`.
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 59871f2

Please sign in to comment.