Add Int.from_digits as inverse of Int#digits#16237
Add Int.from_digits as inverse of Int#digits#16237straight-shoota merged 7 commits intocrystal-lang:masterfrom
Int.from_digits as inverse of Int#digits#16237Conversation
src/int.cr
Outdated
| num = 0 | ||
| multiplier = 1 |
There was a problem hiding this comment.
I think these should not be hardcoded to Int32
There was a problem hiding this comment.
Yes, I think this method should be defined on the concrete types (Int32, UInt8 etc.). Then self defines the return type.
There was a problem hiding this comment.
Is it OK to use macros in this case?
{% for type in %w(Int8 Int16 Int32 Int64 Int128 UInt8 UInt16 UInt32 UInt64 UInt128) %}
def {{type.id}}.from_digits(digits : Enumerable(Int), base : Int = 10) : self
if base < 2
raise ArgumentError.new("Invalid base #{base}")
end
num = 0
multiplier = 1
# ...
num
end
{% end %}
There was a problem hiding this comment.
It might be okay to define it on Int. It will only work on its concrete subclasses though.
Int.from_io works that way as well. Calling it directly doesn't compile, but Int32.from_io etc. work fine.
Obviously, it's confusing to have the method defined on Int when it doesn't work there. So maybe the macro solution is better.
There was a problem hiding this comment.
We could use a macro inherited hook instead of declaring on each child type directly. But for some reason, that doesn't seem to work on Int. Maybe because the inheritance is defined in the compiler? 🤔
struct Int
macro inherited
def self.from_digits(digits : Enumerable(Int), base : Int = 10) : self
# ...
ZERO
end
end
end
Int32.from_digits([1, 2, 3, 4]) # Error: undefined method 'from_digits' for Int32.classThere was a problem hiding this comment.
The inheritance is done in the compiler, so no hooks are called. See #13836
|
|
aa6a7c2 to
6b42516
Compare
src/int.cr
Outdated
| end | ||
|
|
||
| num : {{type.id}} = 0 | ||
| multiplier = 1 |
There was a problem hiding this comment.
multiplier also needs to be in the target type, otherwise it would overflow for something like 10_000_000_000
src/int.cr
Outdated
| end | ||
|
|
||
| num += digit * multiplier | ||
| multiplier *= base |
There was a problem hiding this comment.
Multiplication should not be done for the last digit, otherwise this would overflow for numbers near type's maximum, e.g. Int32.from_digits([0xFF, 0xFF, 0xFF, 0x7F], base: 256) would set multiplier to 0x100000000 for the last loop. Ditto if multiplier is no longer hardcoded to Int32
There was a problem hiding this comment.
Nice catch. Done
5eeeaf7 to
0ff5326
Compare
src/int.cr
Outdated
| multiplier : {{type.id}} = 1 | ||
| first_element = true | ||
|
|
||
| digits.each_with_index do |digit, i| |
There was a problem hiding this comment.
this i is unused; you only need either this or first_element to check for the last (or first) element
There was a problem hiding this comment.
Ah, right.
I tried first to skip the last element. But in that case we need to call Enumerable#size that means iterating the whole collection in general case. So the current approach with first_element seems to me slightly better from the performance point of view (in general case). Does it make sense?
Int.from_digits as inverse of Int#digits
|
Is it OK to squash commits into a single one providing that the PR is approved? |
|
Please leave the commit history unchanged. That makes it easier to review. See https://github.com/crystal-lang/crystal/blob/master/CONTRIBUTING.md#making-good-pull-requests |
src/int.cr
Outdated
| # Int32.from_digits([1], base: -2) # => ArgumentError | ||
| # Int32.from_digits([-1]) # => ArgumentError | ||
| # Int32.from_digits([3], base: 2) # => ArgumentError |
There was a problem hiding this comment.
Suggestion: use already established convention (# => ... to return values, # raises ... for exceptions):
| # Int32.from_digits([1], base: -2) # => ArgumentError | |
| # Int32.from_digits([-1]) # => ArgumentError | |
| # Int32.from_digits([3], base: 2) # => ArgumentError | |
| # Int32.from_digits([1], base: -2) # raises ArgumentError | |
| # Int32.from_digits([-1]) # raises ArgumentError | |
| # Int32.from_digits([3], base: 2) # raises ArgumentError |
|
Just a note: OP description is outdated. |
Changes:
Int.from_digitsthat returns a number from digitsExample:
Closes #14954