Skip to content
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

Make / be float division #2968

Closed
asterite opened this issue Jul 7, 2016 · 70 comments · Fixed by #8120
Closed

Make / be float division #2968

asterite opened this issue Jul 7, 2016 · 70 comments · Fixed by #8120

Comments

@asterite
Copy link
Member

asterite commented Jul 7, 2016

Similar to Python 3, we could have / return a float, and add another operator, maybe // (though that means an empty regex right now, but we could alternatively use %r() for that).

So:

# Now
1 / 2 # => 0

# With this change
1 / 2  # => 0.5
1 // 2 # => 0

I'm not 100% sure about this, mainly because:

  • what type should the result be? Float64?
  • x /= 2 changes the type of x, while previously it doesn't, but one could use x //= 2 now

I'm cc-ing @pbrusco because he originally proposed this :-)

Thoughts?

@Nephos
Copy link
Contributor

Nephos commented Jul 7, 2016

Hello,
Usually I take care of my number's type, so I don't think adding a new operator like this one is useful.
Furthermore, Ruby does not provide such operator. I hope Crystal will stay close to Ruby and away from python ;)

Is a new operator very efficient against a cast (to_f64) ?
If yes, maybe adding a new operator can be useful, but why changing "/" ?

Plus, php do that. Do you really want to do something like php ?

@pbrusco
Copy link

pbrusco commented Jul 7, 2016

Of course I agree on changing, keeping an ambiguous division causes lots of headaches. I've seen people struggling with results and then finding out that in that language. I'm sure that division related bugs are everywhere and I think this is the way to avoid them.

If performance is needed => //
If precision is needed => something else (a method)

@Nephos, I think crystal will be better than ruby, but it needs the "right" division :P

Here are the reasons (in the context of python): https://www.python.org/dev/peps/pep-0238/.

@asterite, Thanks you for opening the discussion

@bcardiff
Copy link
Member

bcardiff commented Jul 8, 2016

In crystal the user can have fine control of the numerical types. But when receiving just a number, might not be clear.

Having / to be floating point division, and // been the floor of the division would mean to have a unified semantic of the operation despite the numerical types. This favor more concistency IMO.

As for the type, Float64 by default sounds reasonable, unless Float32 is involved and no Float64. I think that will honor f32 operations for when the user cares.

@ozra
Copy link
Contributor

ozra commented Jul 8, 2016

You mean something like:?

F32#/(Number) -> F32
Number#/(Number) -> F64

@asterite
Copy link
Member Author

asterite commented Jul 8, 2016

I think the PEP has one very good reason to do this:

def average(elements)
  elements.sum / elements.size
end

The average should be a float, but this might give an integer back. We can solve this using fdiv:

def average(elements)
  elements.sum.fdiv(elements.size)
end

So right now we have:

  • / is "floor division" or "float division", depending on the type of the first argument
  • fdiv is always `"float division"

I think that's the main problem: that / has two meanings, depending on the type of the first argument. We could make it so that:

  • / always means "float division"
  • // always means "floor division"
  • fdiv is removed from the language

I was initially against this change, but @pbrusco insisted so much I decided to open an issue. After considering it for a while I'm starting to like it. But then I thought "Hm, I'm sure this will break a lot of code, as / is probably used in the standard library for "floor division" in many places". Well, I searched and it turns out that floor division is needed in very few places, and in most places a .0 is added to the divisor, or an explicit to_f is used on either operands. It also happened to me that when seeing x / 3 in some places I wondered "Hm, is this intention here to do a "float division" or a "floor division"? One then has to trace back the type of x to know.

In summary, "floor division" is needed, but there's no reason to have / mean two things, we can have two operators with very clear semantics.

So now I'm very open to make this change :-)

@ysbaddaden
Copy link
Contributor

I never heard of // ... to me that's a line comment, whatever the language :-/

@asterite
Copy link
Member Author

asterite commented Jul 8, 2016

Python, Elm and Lua have this operator.

Additionally, Visual Basic has it as a backslash.

We could choose another character combination, maybe /., but I think // is the most common and looks best, at least in non-C languages.

@ozra
Copy link
Contributor

ozra commented Jul 8, 2016

As I remember (it's been decades...) pascal used / : R, div : Z, and can't-remember-which-language-back-then used / : R, \ : Z. Anyway, it's not an entirely uncommon feature. And I must say the Python // does make some sense

@ozra
Copy link
Contributor

ozra commented Jul 8, 2016

Haha, there it was, VB.

@Nephos
Copy link
Contributor

Nephos commented Jul 8, 2016

Python, Elm and Lua have this operator.

This is not a good thing to me.

In Ruby, someone making the mistake when using / instead of fdiv ou / + to_f just uses the wrong language.
When you do use a language like Python, you write / or //.
When you use C++, you write a cast.
If not, you must go back to the documentation. This is not an error of the language, just yours.

However, adding a method /. seems more expressive (to me) than force the fdiv with /.

@refi64
Copy link
Contributor

refi64 commented Jul 8, 2016

I'm +100 on this. Implicit integer division should die a slow, painful death. I cannot count how many times bugs related to this have screwed me over in C++.

@bcardiff
Copy link
Member

bcardiff commented Jul 8, 2016

@ozra
I mean

F32#/(F64) -> F64
F32#/(Number*) -> F32

F64#/(F32) -> F64
Number*#/(F32) -> F32

Number#/(Number) -> F64

Number* will be any number but F64 due to order of the rules.

@oprypin
Copy link
Member

oprypin commented Jul 8, 2016

For dynamically typed languages it's just silly to have integer division as /, but I didn't feel as strongly about it in Crystal because implementation needs to be opinionated (the problems have been brought up already, nothing to add). Still, I support the change for sure.

@ozra
Copy link
Contributor

ozra commented Jul 10, 2016

@bcardiff - OK: so F32 return also for F32 on RHS, when not F64 on LHS.

@david50407
Copy link
Contributor

Vote for not to change.

Now we have / for "floor division" or "float division", depending on the type of the number and the argument, I think this is the auto casting sugar, the result will be the larger (or more detail) number type, so we can get:

Int / Int => Int
Int / Float => Float
Float / Int => Float
Float / Float => Float

/ always only have one meaning that cast both number to the same type and divide.
(So it would be floor on both Int or float on both Float)

And if we do this change, / still have two meanings "divide to Float32" or "divide to Float64" depends on the type of numbers (as @bcardiff said).

IMO, If we want to divide and get the float, we can just do fdiv or #to_f32/#to_f64 first on the number or the argument.

@jhass
Copy link
Member

jhass commented Jul 11, 2016

There's a third possibility to remove the inconsistency that's not been mentioned yet and I'm not sure I like it too much either, but it should be on the table for completeness.

As it has been said fdiv always means float division, while / is context specific. We could simply make / always floor.

3.2 / 3 #=> 1
4.6 / 2 #=> 2
10.2 / 3.3 #=> 3
Int / Int #=> Int
Int / Float #=> Int
Float / Float # => Int

Now it has also been pointed out that while / is the more common operator, float division is the more common operation in high level programs. Giving the shorter and more expressive operator to the less common operation is not ideal. Following that logic, and given there would be agreement about the above point, / should be renamed to div and fdiv be renamed to /.

@ozra
Copy link
Contributor

ozra commented Jul 11, 2016

@jhass, yes, the Pascal way :-)

@bcardiff
Copy link
Member

@david50407 I don't see "divide to Float32" and "divide to Float64" as semantically different operations. There is a precision difference for sure, but is the same as other maths operators. #+ and #*.

Due to polymorphism and overloads we can support different methods for the same message, but whenever we do this it should be semantically painless for the user.

I don't mind if / + // or / + /. are chosen, although I slightly prefer the former.

I think that differentiating this operation will bring less errors in the code.

The other maths operators are more safe because (unless overflow/underflow) you end up in the same type of numbers. That is what I try to honor with the overload I propose.

@david50407
Copy link
Contributor

@bcardiff @asterite How about / for floor division and /. for float division?
That would be more compatible with old codes (old codes used #to_f and .0 for float division) and the dot in /. can also be explained to "This return value of the operator is a float."

@asterite
Copy link
Member Author

@david50407 The most common division is float division, not floor division. / is also the symbol used in math, and it means float division, not floor division. Floor division is outside regular math, so it should probably have another symbol.

@david50407
Copy link
Contributor

david50407 commented Jul 12, 2016

@asterite will the new symbol for floor division be a method (can be defined manually by def) or just a syntax sugar?

BTW, how about /_? We cannot read value from _ now, so this won't be confused and _ can also mean floor operation. (just suggest the symbol for it, // is also good and some languages are already using.)

@jhass
Copy link
Member

jhass commented Jul 12, 2016

Do we really need a new operator? I think 1.div 2 is just fine.

@asterite
Copy link
Member Author

@david50407 It'll be a new method, similar to other operators, so you could define it in BigInt, etc. I don't see a reason to choose anything other than //, mostly because it's already present in other languages.

@jhass And although we don't necessarily need a new operator for this, integer division is still very common and it would be tedious to write x.div y (this is also a conclusion of Python's PEP)

@b00401062
Copy link

b00401062 commented Aug 19, 2016

I vote for the change. This change would help those playing scientific computing. For simplicity, I always prefer to write 1 / 2 / 3 instead of 1.fdiv(2).fdiv(3).

About the second concern of @asterite , x /= 2.0 still change the type of x. So what I mean is that this should not be the reason to stop / from being a float division. / itself is never used as a mean to maintain type safety even in current Crystal.

In the world where / and // are not context specific, the rule is simpler and users can decide whether the keep the remainder or not based on their purpose.

(x : Any) /= Any # => x : Float
(x : Any) //= Any # => x : Int

@silverweed
Copy link

+1, in my experience I hardly ever required a floor division, and if I did I'd probably take extra care on the method used to obtain it; imho the pros outweigh the cons.

@ozra
Copy link
Contributor

ozra commented Sep 16, 2016

I hope the choice of // for floor goes through! :-)

@asterite
Copy link
Member Author

I actually implemented this in a branch and tried it out and kind of disliked it... it's like, I'm using integers, I want to divide it by two and I'm forced to use // instead of /. It felt annoying.

I think it makes more sense in languages like Haskell and Elm where they use Hindley-Minler type inference that deduces things from functions and operators (so in a / b, a and b must be floats while in a // b, a and b must be integers), but for other languages it's maybe not good.

I'll still leave this issue open because the change might happen, I'm just not as convinced as before...

@straight-shoota
Copy link
Member

I'm not sure about this. It seems nice but there are valid concerns. Is the result really worth it?

However, I don't see a problem with supporting multiple versions. This requires only a small compiler change and partial adaptation could be split across two or three releases. It should be possible to ensure the code always works with two different compiler versions.

@j8r
Copy link
Contributor

j8r commented Jun 27, 2018

There is also the option of returing a BigRational, like Perl6 does.
But personally I like the current behavior of divisions in Crystal.

@rdp
Copy link
Contributor

rdp commented Jun 27, 2018 via email

@asterite
Copy link
Member Author

These above are all just guesses. If we change / to always mean float division, then:

  • if it's already used with floats, it will continue working
  • if it's used in integers, then the result will now be a float, and that will be incompatible with the type the result is assigned to (the left hand must currently be an integer, becaue there's no implicit cast between integer and float after a division). So you will get a compile error.

Believe, I already implemented this feature but never pushed the code. When I did it, you would get compile errors on incorrect usage, and it would work on existing float divisions. There will not be silent errors introduced by this change.

Relax :-)

@AlexWayfer
Copy link
Contributor

Oh no… what's next? The /// operator for BigDecimal result, or [div, mod], or something else?

I love Ruby language for a small amount of special characters — it's closer to natural language.

I could guess about #div,#modulo and #divmod methods.

But // is non-intuitive. And it's dirtier.

I saw the comment from @asterite :

I think the decision about introducing this // operator is already taken. Now it's time to review the code.

But I just don't want to leave it without my opinion (and not only mine, judging by this thread).

@rdp
Copy link
Contributor

rdp commented Aug 3, 2018

FWIW I noticed that elixir uses "/" for floating point divide and "#div" for integer divide... :)

@fabon-f
Copy link

fabon-f commented Aug 22, 2019

The release note of 0.28.0(https://crystal-lang.org/2019/04/17/crystal-0.28.0-released.html) says that a breaking change about Int#/ will happen in the 0.29.0. Now Crystal is 0.30.1, but Int#/ still returns Int. Are there any obstacles to this breaking change?

@bcardiff
Copy link
Member

Ref #8120

@girng
Copy link
Contributor

girng commented Oct 6, 2019

After reading this thread, one thing that was not mentioned was ivars for classes. After this change, a developer cannot statically type their ivars and rely on / division. Because the / operator will convert the result to a Float64, thus, will give you a compile error. The developer will be like wtf, and use a union, or wrap their math expressions with parentheses and invoke .to_i all over their code. After all this, they will probably think they need to use .as as well, LOL!

Real world use case (if you don't believe me):

Before #8120:

class Client
  property min_dmg = 0
end

player = Client.new

player.min_dmg += (10) / 2

test = 0

test += player.min_dmg

pp typeof(test)
pp test

Works perfect. Output:

Int32
5

After:

class Client
  property min_dmg : Int32 | Float64
  
  def initialize
    @min_dmg = 0
  end
end

player = Client.new

player.min_dmg += (10) / 2

test = 0

test += player.min_dmg

pp typeof(test)
pp test

Output:

(Float64 | Int32)
5

Now, after creating these examples a new problem arose. Local/temp variables will be converted into unions just to match the ivar's union. This is a very bad decision IMO.

A lot of potential problems will come to fruition over this change, because / is a convention used by so many developers already.

@j8r
Copy link
Contributor

j8r commented Oct 6, 2019

Just use //? Problem solved :)
Lots of methods returns a different type from the root object, nothing specific to divisions.
If Float64 is wanted, setting @min_dmg = 0_f64 prevent to have an union.
If Int32 is wanted, the developer will either figure how to divide the old way, or will ask in the chat/forum.

@asterite
Copy link
Member Author

asterite commented Oct 6, 2019

@Gring just use // where you previously used /. It's a really simple change. In fact you can blindly solve compile error from upgrading the crystal version by replacing / with // where the compiler complains.

@girng
Copy link
Contributor

girng commented Oct 6, 2019

Crystal is a statically typed language. In my example, the ivars/variables are explicitly set to a type. Why is that type changing? It just doesn't make sense to me. Something is not right

Crystal’s syntax is heavily inspired by Ruby’s, so it feels natural to read and easy to write, and has the added benefit of a lower learning curve for experienced Ruby devs.

This does not happen with Ruby.

If this stays, I urge the developers to please, PLEASE mention this somewhere in GitBook/docs/wherever so new developers know in advance. That's all I ask, thank you

@refi64
Copy link
Contributor

refi64 commented Oct 6, 2019 via email

@girng
Copy link
Contributor

girng commented Oct 6, 2019

min_dmg is explicitly set to an Int32, and wants to become a Float64. That's trying to change the type (when the developer has not introduced a Float64 anywhere in their code).

@asterite
Copy link
Member Author

asterite commented Oct 6, 2019

Could you please answer why can't you use // in your example?

@girng
Copy link
Contributor

girng commented Oct 6, 2019

"Why are you spelling robot without a w? It's spelled rowbot".

Conventions and history of an operator are super important, especially in this case because I bet a lot of new devs coming to Crystal have a Ruby background (and expect / to work the same).

Why Crystal?

  • "We love C's efficiency"
  • "Oh, and we don't want to write C code to make the code run faster."
  • ..oh, and our / operator returns a Float64.

lol see what I mean?

@atik7
Copy link

atik7 commented Oct 6, 2019

I vote favor of //

Crystal is not replacement of ruby or c

@j8r
Copy link
Contributor

j8r commented Oct 6, 2019

Note:

p 3.tdiv 2 #=> 1
p 3.fdiv 2 #=> 1.5

The / and // decision is already made.
This is a matter of taste, / returning a float is quite natural when coming from mathematics/school.
If the only argument is: "I'm familiar with / returning Int", it isn't enough. Others can say the opposite, too.

@girng, have you a proposal for integer, division, I guess #div?

In fact, the question is either if Crystal is more newbie-friendly or pro-friendly.

@girng
Copy link
Contributor

girng commented Oct 6, 2019

My argument is, the language should not convert our types in the background. If a developer wants a Float64, they can divide a number by a float. Just like in C, Ruby, and Go. This has been a convention for decades AFAIK.

Even if you don't agree, objectively speaking, changing of types should ALWAYS be initiated by the developer. There should not be any "magic" that converts types in the background. See my example above, this is just a recipe for disaster

@j8r
Copy link
Contributor

j8r commented Oct 6, 2019

But it doesn't convert any types, as I said / and // are methods. In your case, you are using an assignment of the result of a method, with += .
Exactly like

str = 'a'
str += "b" if str
p str         #=> "ab"
p typeof(str) #=> (Char | String)

@girng
Copy link
Contributor

girng commented Oct 6, 2019

I don't want to argue because when I do, I become really rude and you guys are very friendly and helpful so it would be out of line. I will leave it at that and stand by my previous posts

@asterite
Copy link
Member Author

asterite commented Oct 6, 2019

@girng when you do 1.fdiv(2) you get a Float64. That's not different than / doing now the same thing. In fact in Ruby you have to use fdiv for float division. In Crystal we chose to have two different operators.

@girng
Copy link
Contributor

girng commented Oct 6, 2019

In ruby:

pp (5/2).class

pp (5/2.0).class

Output:

Integer
Float

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.