- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 1.7k
 
          Add Int.from_digits as inverse of Int#digits
          #16237
        
          New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
  
    Add Int.from_digits as inverse of Int#digits
  
  #16237
              Conversation
        
          
                src/int.cr
              
                Outdated
          
        
      | num = 0 | ||
| multiplier = 1 | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these should not be hardcoded to Int32
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
multiplier also needs to be in the target type, otherwise it would overflow for something like 10_000_000_000
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
        
          
                src/int.cr
              
                Outdated
          
        
      | end | ||
| 
               | 
          ||
| num += digit * multiplier | ||
| multiplier *= base | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
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.  | 
    
        
          
                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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| 
           Just a note: OP description is outdated.  | 
    
Changes:
Int.from_digitsthat returns a number from digitsExample:
Closes #14954