Crystal: Make `/` be float division

Created on 7 Jul 2016  Â·  70Comments  Â·  Source: crystal-lang/crystal

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?

draft compiler

Most helpful comment

@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)

All 70 comments

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 ?

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

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.

You mean something like:?

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

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 :-)

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

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.

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

Haha, there it was, VB.

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 /.

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++.

@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.

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.

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

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.

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 /.

@jhass, yes, the Pascal way :-)

@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.

@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."

@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.

@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.)

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

@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)

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

+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.

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

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...

I'll admit this is one of the warts I actually dislike about ruby

1/3 => 0

and even more confusing:

a/b => ???

you have to basically memorize what your types are "is one of these a floating point?" every time you do a divide to avoid the integer "discard remainder". Sooo painful. Maybe the default should be to return a rational or something that doesn't lose precision...my $0.02 FWIW...

I'm fine w/ using a different syntax as long as we have a way to individually force num, denom, and/or result to be int or to be float.

Either way, you have to keep in mind your program's desired chain of events. For example, do I want to floor(a)/floor(b) or floor(a/b)? (i.e.: when a = 11.10 and b = 1.11, then floor(11.10)/floor(1.11) == 11.0, but floor(11.10/1.11) == 10.0).

Personally, I prefer (or am just used to) the more, 'old-school' style of:

  a / b                    #=> allow int div (aka floor) if both int, otherwise float div; returns int if both ints, otherwise returns float

... and then forcing what I want via code like 1.0 * scale * a / b, a.to_i / b.to_i, (a.to_f/b.to_f).to_i, etc.

I think it is mostly a holywar question, some people like one way, some - another. Imho "pascal-way" with different operators for float division and int division is safer - if you are dividing it wrong you get compile error (e.g. because the result is float and not int), not just wrong result. But i understand that most people prefer C, so i didn't have lot of expectations about this change.

I use vb, so / and \ is better , haha

@sevk \ is for escaping now lol

I'm still hoping for the //, / -distinction, some additional notes:

  • _args required to be integer_ for //.
  • for / - it's not as clear cut, other than definitely producing an appropriate kind of float.

    • the case against allowing Integer+ as dividend (LHS) is of course: x /= y

    • the case against forbidding it being "scientific" calcing: 1 / 2 / 3

_This leads me back to the same horse (not dead though!!) I'm always beating_: improving the type inference to recognise "weakly suggested types" _for literals_. (#2995) But that's still a Pandora's box problem, with discussions running away into coercion a la C++ and auto-conversion a la Julia, etc. I still think it should be solvable in a cleanly defined, yet limited, way.

In such a case _requiring dividend to be float is the best choice_ - with the literals in 1 / 2 / 3 being "reverse inferred" as appropriate kind of floats.

Please do not use // as integer division operator. div looks more correct.

Firstly, // is comment for every C like language.
Secondly, // is Logical Defined-Or operator in Perl.

@akzhan this has been closed for a month or so, I don't think it will be implemented or accepted anyway.

I am still in favor of having different operators for different divisions. Or at least, make all / return floats for consistency.

Integer division could be another operator or method. I still prefer // in this topic, yet there is an overlap with empty regex that could be solved by context probably but is usually not a good idea to go that path. Yet, empty regex are hardly needed IMO.

Other alternative operator would be %/ that is related to módulo and to division (R uses %/% and %% for example). But // stills seems nicer, even at the cost of dropping the syntax for empty regex.

Yeah, I like // too.

I think it's actually far too late in the language to change how division works. I'd be surprised if the diff from applying this change to the stdlib was reviewable. At a certain point we do have to start saying no to even good changes.

We can't just continue breaking everything right up until the moment that we release 1.0, stability is a culture change and it has to happen sooner rather than later.

And how would we even implement this in terms of supporting the previous release? Its just impossible to do something like this at this stage of development. Even if we wanted to, which I'm not sure we do.

It's not impossible, I did it already in the past and it worked well (but back then I didn't like it).

Let's try it. Anything can break before 1.0. Even division.

I like english name div , more human readable .

I don't think that we can keep making breaking changes to large things in the language up until 1.0.0rc7 or something, then overnight release 1.0.0 and be perfectly stable. The size and scope of breaking changes must decrease towards 1.0.0 otherwise we can't reasonably call it stable without any period of relative stability to point at. Breaking changes is a habit that we're going to have to train ourselves out of, this change is not beneficial to that.

Further to that - although I haven't seen the diff - I believe that the diff to the stdlib and compiler will be very large and hard to review. And I don't see many newcomers tripping up on the current division behaviour either. Yes, this way of doing division might be better (apart from /=, that's pretty weird), but is it that much better that it's worth putting so much time into this change. And it's not just of the crystal core team, but the community and every shard that has to port too.

So for me, it's too much of a change, too late, and for too little benefit. Strongly against this change.

But it'll be a simple breaking change. int / int will give a float result now, and that will probably result in a compiler error.

Also, where's the fun if there are no breaking changes between releases? If there's nothing to do when upgrading to a new version it's kind of boring...

It's simple to describe the change, that doesn't mean it's simple to implement and review the diff.

How about leave / as-is to avoid breaking changes, and add some operator for force float division and some operator for force floor int division? Then, people can specify which 'flavor' of division that they want as applicable.

I kind of feel like that's a lose-lose scenario, though. The latter would almost never be used (usually when working with float data, you'll round it instead of indiscriminately flooring it), and everyone's going to use / anyway, so the former will only be used when someone releases floor division broke their code.

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.

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

FWIW, from my perspective, when I use it, I almost never "want" "/" to be
an integer division, since that "loses information" (the remainder). I
always have to remember "I have to cooerce one side or the other to a float
like 1/3.0" or "a/b.to_f" which is painful. Defaulting to float would be
different than Ruby (in a good way IMO) but of course the transition would
be painful (but possibly worth it). I'd be interested in trying it.
Another possibility: maybe could introduce // as an "always to rational"
operation? ... just my $0.02

On Wed, Jun 27, 2018 at 4:50 AM, Chris Hobbs notifications@github.com
wrote:

@straight-shoota https://github.com/straight-shoota I don't see how
it'd be possible to make it work unless we migrated everyone away from
using / and then back to / again once it changed

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/crystal-lang/crystal/issues/2968#issuecomment-400630435,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAAw0OEqKzyVlJzOaoXgCXEqoP1meoSIks5uA2OGgaJpZM4JHczg
.

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 :-)

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).

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

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?

Ref #8120

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:

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.

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.

@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.

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

The type of your variable isn't changing, when you do a / b you're
basically calling a function and using the output value.

FWIW Python's done this same thing with division for years, it's not nearly
as bizarre or surprising as you might think.

On Sun, Oct 6, 2019, 9:21 AM girng notifications@github.com wrote:

Crystal is a statically typed language. Our 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

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/crystal-lang/crystal/issues/2968?email_source=notifications&email_token=AAM4YSK2PP5EKVSFMRVHVZ3QNHX7JA5CNFSM4CI5ZTQKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOLGLY#issuecomment-538751791,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAM4YSPR6C66GY23YBV2XNDQNHX7JANCNFSM4CI5ZTQA
.

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).

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

"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?

I vote favor of //

Crystal is not replacement of ruby or c

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.

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

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)

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

@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.

In ruby:

pp (5/2).class

pp (5/2.0).class

Output:

Float
Was this page helpful?
0 / 5 - 0 ratings