Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 8 additions & 8 deletions src/big/big_decimal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct BigDecimal < Number
#
# NOTE: Floats are fundamentally less precise than BigDecimals,
# which makes initialization from them risky.
def self.new(num : Float)
def self.new(num : Float) : self
raise ArgumentError.new "Can only construct from a finite number" unless num.finite?
new(num.to_s)
end
Expand All @@ -47,13 +47,13 @@ struct BigDecimal < Number
#
# NOTE: BigRational are fundamentally more precise than BigDecimals,
# which makes initialization from them risky.
def self.new(num : BigRational)
def self.new(num : BigRational) : self
num.numerator.to_big_d / num.denominator.to_big_d
end

# Returns *num*. Useful for generic code that does `T.new(...)` with `T`
# being a `Number`.
def self.new(num : BigDecimal)
def self.new(num : BigDecimal) : self
num
end

Expand Down Expand Up @@ -235,7 +235,7 @@ struct BigDecimal < Number
# BigDecimal.new(1).div(BigDecimal.new(2)) # => BigDecimal(@value=5, @scale=2)
# BigDecimal.new(1).div(BigDecimal.new(3), 5) # => BigDecimal(@value=33333, @scale=5)
# ```
def div(other : BigDecimal, precision = DEFAULT_PRECISION) : BigDecimal
def div(other : BigDecimal, precision : Int = DEFAULT_PRECISION) : BigDecimal
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This comes from way before this PR, but I would have expected precision to be a BigInteger in BigDecimal 🤔

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FWIW, bigdecimal-rs, which this implementation is based on, does the same

check_division_by_zero other
return self if @value.zero?
other.factor_powers_of_ten
Expand Down Expand Up @@ -349,7 +349,7 @@ struct BigDecimal < Number
self <=> other.to_big_r
end

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

Expand Down Expand Up @@ -754,7 +754,7 @@ struct BigDecimal < Number
end

# returns `self * 10 ** exponent`
protected def mul_power_of_ten(exponent : Int)
protected def mul_power_of_ten(exponent : Int) : BigDecimal
if exponent <= scale
BigDecimal.new(@value, @scale - exponent)
else
Expand All @@ -764,7 +764,7 @@ struct BigDecimal < Number

# Factors out any extra powers of ten in the internal representation.
# For instance, value=100 scale=2 => value=1 scale=0
protected def factor_powers_of_ten
protected def factor_powers_of_ten : Nil
if @scale > 0
reduced, exp = value.factor_by(TEN_I)
if exp <= @scale
Expand Down Expand Up @@ -860,7 +860,7 @@ end

# :nodoc:
struct Crystal::Hasher
def self.reduce_num(value : BigDecimal)
def self.reduce_num(value : BigDecimal) : UInt64
v = reduce_num(value.value.abs)

# v = UInt64.mulmod(v, 10_u64.powmod(-scale, HASH_MODULUS), HASH_MODULUS)
Expand Down
12 changes: 6 additions & 6 deletions src/big/big_float.cr
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ struct BigFloat < Float
LibGMP.mpf_get_default_prec
end

def self.default_precision=(prec : Int)
def self.default_precision=(prec : Int) : Nil
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is worrysome: a setter method should always return its argument.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There is the older and abandoned #10083 for this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My own 2-cents: it should be a linting rule rather than a language rule, that the setter return what is handed to it. Seems there are other opinions on what setter methods should return, too (like matching the getter).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, we'd want this to be different. But this change is fine for this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You want it to return what was passed in so that chained assignment is handled. Ruby, for example, ignores returning a value from setters for this reason.

PRECISION = BigFloat.default_precision = 5

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Fryguy Yes, this is tracked in #7707.

LibGMP.mpf_set_default_prec(prec.to_u64)
end

Expand All @@ -105,11 +105,11 @@ struct BigFloat < Float
LibGMP.mpf_cmp_z(self, other)
end

def <=>(other : Float::Primitive)
def <=>(other : Float::Primitive) : Int32?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All the <=> methods should normally just return Int32

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, and the Nil return is a bit confusing here as well (though not incorrect when considering you're comparing with a NaN).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All the <=> methods should normally just return Int32

The contract in general is to return nil when not comparable. That is relevant for some types, but not all.

Copy link
Copy Markdown
Contributor

@HertzDevil HertzDevil Apr 23, 2025

Choose a reason for hiding this comment

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

This Float::Primitive overload was indeed added as a breaking change in 1.9 because it needs to return nil. Note that BigFloat itself doesn't have NaN, since we are still using GMP, not MPFR

LibGMP.mpf_cmp_d(self, other) unless other.nan?
end

def <=>(other : Int)
def <=>(other : Int) : Int32
Int.primitive_si_ui_check(other) do |si, ui, big_i|
{
si: LibGMP.mpf_cmp_si(self, {{ si }}),
Expand Down Expand Up @@ -369,7 +369,7 @@ struct BigFloat < Float
LibGMP.mpf_get_ui(self).to_u64!
end

def to_unsafe
def to_unsafe : Pointer(LibGMP::MPF)
mpf
end

Expand Down Expand Up @@ -473,7 +473,7 @@ struct BigFloat < Float
format_impl(io, is_negative, integer, decimals, separator, delimiter, decimal_places, group, only_significant)
end

def clone
def clone : BigFloat
self
end

Expand Down Expand Up @@ -545,7 +545,7 @@ struct Number
end

struct Int
def <=>(other : BigFloat)
def <=>(other : BigFloat) : Int32
-(other <=> self)
end

Expand Down
28 changes: 14 additions & 14 deletions src/big/big_int.cr
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct BigInt < Int
# BigInt.new("123_456_789_123_456_789_123_456_789") # => 123456789123456789123456789
# BigInt.new("1234567890ABCDEF", base: 16) # => 1311768467294899695
# ```
def initialize(str : String, base = 10)
def initialize(str : String, base : Int32 = 10)
# Strip leading '+' char to smooth out cases with strings like "+123"
str = str.lchop('+')
# Strip '_' to make it compatible with int literals like "1_000_000"
Expand All @@ -47,7 +47,7 @@ struct BigInt < Int
end

# Creates a `BigInt` from the given *num*.
def self.new(num : Int::Primitive)
def self.new(num : Int::Primitive) : self
Int.primitive_si_ui_check(num) do |si, ui, _|
{
si: begin
Expand Down Expand Up @@ -110,23 +110,23 @@ struct BigInt < Int
end

# :ditto:
def self.new(num : BigFloat)
def self.new(num : BigFloat) : self
num.to_big_i
end

# :ditto:
def self.new(num : BigDecimal)
def self.new(num : BigDecimal) : self
num.to_big_i
end

# :ditto:
def self.new(num : BigRational)
def self.new(num : BigRational) : self
num.to_big_i
end

# Returns *num*. Useful for generic code that does `T.new(...)` with `T`
# being a `Number`.
def self.new(num : BigInt)
def self.new(num : BigInt) : self
num
end

Expand Down Expand Up @@ -155,7 +155,7 @@ struct BigInt < Int
end
end

def <=>(other : Float::Primitive)
def <=>(other : Float::Primitive) : Int32?
LibGMP.cmp_d(mpz, other) unless other.nan?
end

Expand Down Expand Up @@ -338,7 +338,7 @@ struct BigInt < Int
{the_q, the_r}
end

def unsafe_truncated_divmod(number : BigInt)
def unsafe_truncated_divmod(number : BigInt) : Tuple(BigInt, BigInt)
the_q = BigInt.new
the_r = BigInt.new { |r| LibGMP.tdiv_qr(the_q, r, self, number) }
{the_q, the_r}
Expand Down Expand Up @@ -385,7 +385,7 @@ struct BigInt < Int
BigInt.new { |mpz| LibGMP.com(mpz, self) }
end

def bit(bit : Int)
def bit(bit : Int) : Int32
return 0 if bit < 0
return self < 0 ? 1 : 0 if bit > LibGMP::BitcntT::MAX
LibGMP.tstbit(self, LibGMP::BitcntT.new!(bit))
Expand Down Expand Up @@ -792,7 +792,7 @@ struct BigInt < Int
BigRational.new(self)
end

def clone
def clone : BigInt
self
end

Expand All @@ -806,7 +806,7 @@ struct BigInt < Int
pointerof(@mpz)
end

def to_unsafe
def to_unsafe : Pointer(LibGMP::MPZ)
mpz
end
end
Expand Down Expand Up @@ -901,7 +901,7 @@ class String
#
# "3a060dbf8d1a5ac3e67bc8f18843fc48".to_big_i(16)
# ```
def to_big_i(base = 10) : BigInt
def to_big_i(base : Int32 = 10) : BigInt
BigInt.new(self, base)
end
end
Expand All @@ -919,7 +919,7 @@ module Math
end

# Calculates the integer square root of *value*.
def isqrt(value : BigInt)
def isqrt(value : BigInt) : BigInt
BigInt.new { |mpz| LibGMP.sqrt(mpz, value) }
end

Expand Down Expand Up @@ -984,7 +984,7 @@ end
struct Crystal::Hasher
private HASH_MODULUS_INT_P = BigInt.new(HASH_MODULUS)

def self.reduce_num(value : BigInt)
def self.reduce_num(value : BigInt) : UInt64
{% if LibGMP::UI == UInt64 %}
v = LibGMP.tdiv_ui(value, HASH_MODULUS)
value < 0 ? &-v : v
Expand Down
26 changes: 13 additions & 13 deletions src/big/big_rational.cr
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,23 @@ struct BigRational < Number
# Creates an exact representation of float as rational.
#
# Raises `ArgumentError` if *num* is not finite.
def self.new(num : Float::Primitive)
def self.new(num : Float::Primitive) : self
raise ArgumentError.new "Can only construct from a finite number" unless num.finite?
new { |mpq| LibGMP.mpq_set_d(mpq, num) }
end

# Creates an exact representation of float as rational.
def self.new(num : BigFloat)
def self.new(num : BigFloat) : self
new { |mpq| LibGMP.mpq_set_f(mpq, num) }
end

# Creates a `BigRational` from the given *num*.
def self.new(num : BigRational)
def self.new(num : BigRational) : self
num
end

# :ditto:
def self.new(num : BigDecimal)
def self.new(num : BigDecimal) : self
num.to_big_r
end

Expand Down Expand Up @@ -90,19 +90,19 @@ struct BigRational < Number
BigInt.new(@mpq._mp_den)
end

def <=>(other : BigRational)
def <=>(other : BigRational) : Int32
LibGMP.mpq_cmp(mpq, other)
end

def <=>(other : Float::Primitive)
def <=>(other : Float::Primitive) : Int32?
self <=> BigRational.new(other) unless other.nan?
end

def <=>(other : BigFloat)
def <=>(other : BigFloat) : Int32
self <=> other.to_big_r
end

def <=>(other : Int)
def <=>(other : Int) : Int32
Int.primitive_si_ui_check(other) do |si, ui, big_i|
{
si: LibGMP.mpq_cmp_si(self, {{ si }}, 1),
Expand All @@ -112,7 +112,7 @@ struct BigRational < Number
end
end

def <=>(other : BigInt)
def <=>(other : BigInt) : Int32
LibGMP.mpq_cmp_z(self, other)
end

Expand Down Expand Up @@ -371,15 +371,15 @@ struct BigRational < Number
denominator.format(io, separator, delimiter, decimal_places, group: group, only_significant: only_significant)
end

def clone
def clone : BigRational
self
end

private def mpq
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are some of these functions not typed at all?

Should be : LibGMP::MPQ*

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The cr-source-typer has a new flag to ignore private defs, as well as type restrictions that are resolved to 3+ union types.

I realize my PR description doesn't show that with the single line command 😬

pointerof(@mpq)
end

def to_unsafe
def to_unsafe : Pointer(LibGMP::MPQ)
mpq
end

Expand All @@ -406,7 +406,7 @@ struct Int
BigRational.new(self, 1)
end

def <=>(other : BigRational)
def <=>(other : BigRational) : Int32
-(other <=> self)
end

Expand Down Expand Up @@ -467,7 +467,7 @@ end

# :nodoc:
struct Crystal::Hasher
def self.reduce_num(value : BigRational)
def self.reduce_num(value : BigRational) : UInt64
inverse = BigInt.new do |mpz|
if LibGMP.invert(mpz, value.denominator, HASH_MODULUS_INT_P) == 0
# inverse doesn't exist, i.e. denominator is a multiple of HASH_MODULUS
Expand Down
6 changes: 3 additions & 3 deletions src/big/json.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class JSON::Builder
end

struct BigInt
def self.new(pull : JSON::PullParser)
def self.new(pull : JSON::PullParser) : self
case pull.kind
when .int?
value = pull.raw_value
Expand All @@ -38,7 +38,7 @@ struct BigInt
end

struct BigFloat
def self.new(pull : JSON::PullParser)
def self.new(pull : JSON::PullParser) : self
case pull.kind
when .int?, .float?
value = pull.raw_value
Expand All @@ -65,7 +65,7 @@ struct BigFloat
end

struct BigDecimal
def self.new(pull : JSON::PullParser)
def self.new(pull : JSON::PullParser) : self
case pull.kind
when .int?, .float?
value = pull.raw_value
Expand Down
6 changes: 3 additions & 3 deletions src/big/yaml.cr
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
require "yaml"
require "big"

def BigInt.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
def BigInt.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : self
unless node.is_a?(YAML::Nodes::Scalar)
node.raise "Expected scalar, not #{node.class}"
end

BigInt.new(node.value)
end

def BigFloat.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
def BigFloat.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : self
unless node.is_a?(YAML::Nodes::Scalar)
node.raise "Expected scalar, not #{node.class}"
end

BigFloat.new(node.value)
end

def BigDecimal.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
def BigDecimal.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : self
unless node.is_a?(YAML::Nodes::Scalar)
node.raise "Expected scalar, not #{node.class}"
end
Expand Down