6 ms·
> "Isn't it obvious that the output type should be float?" To you that might be obvious. The compiler can't infer what your intentions were from your code. >
by ajxs 4y ago
> "Isn't it obvious that the output type should be float?"
To you that might be obvious. The compiler can't infer what your intentions were from your code.
> "So why not just cast the int to a float and multiply them and output a float, like other languages do?"
What if the result is being stored in an integer type, how should this be handled? In order to compile the code in the ways your post specifies the compiler has to make many assumptions. Choosing to minimise the amount of assumptions made by the compiler about types is part of 'strong typing'. Being a systems programming language, it needs to be concerned with the underlying representation of the data. What if the result variable only has 8-bits, and can't reasonably represent a floating point type at all?
I do lots of coding in Ada, which has a strong type system. It allows you to specify arbitrary ranged numeric types. This allows you to better semantically describe your problem domain. You could have a specific numeric type representing the number of slices in a pizza (an integer), and one specifying the number of kilometres between two points on a map (a float). Multiplying one by the other might not make sense in your problem domain. The compiler would prevent you making what it assumes would be an erroneous calculation.
- dasil003 4y agoWait hold on, why are you jumping straight to "what the compiler can infer"? Compilers (and type systems) are written by and for humans, so first tell me the use case for multiplying a float by an int that does not lead to a float and justify why we should optimize for that case.
- fennecfoxen 4y agoSure. Multiplying a float by an int that leads to AN ERROR because you mixed up your types and you are now losing precision somewhere or other. Avoiding that sort of an error at compile-time is right there at the heart of the core premise of Rust, so Rust should optimize for it.
- Turing_Machine 4y agoExcept that in the real world people want to multiply floats by ints all the time, and they don't want to go through a lot of type-wankery to do it.
- RHSeeger 4y agoIn order to make _some_ things safer, every type system must make _other_ things impossible. There is, as far as I'm aware, no real way around that.
- fennecfoxen 4y agoIt’s like one cast, yo
- Turing_Machine 4y agoIt's "one cast" times the total number of times you need to multiple ints by floats for the rest of your career.
- civilized 4y agoYou can also make errors when you multiply two floats together. Why then should Rust allow any operations at all?
- lang_agnostic 4y agoYou simultaneously make a great and stupid point. The stupid part is obvious: operations are allowed because writing programs is useful. Now for the great, nuanced and complicated point: How do we detect errors when manipulating floats while allowing us to write the useful programs? This boils down to two things: 1. What do we think is correct at any given time for any given program? The compiler can't know so we have to tell it, which brings us to 2 2. How do we write down in our programming language what is supposed to be correct? The problem with floats is that sometimes you care about precision, sometimes you don't sometimes you care about overflows, sometimes you don't. Sometimes you care about inverse operations, sometimes you don't. Commercial programming language which only expose a "float" types usually are unable to deal with the greater complexity of _ensuring_ that some property you care about isn't broken. That is why compilers let you operate on floats and shoot yourself in the foot when you divide and then multiply back. On the other hand, one could imagine a future programming language (and some academic experiments already exist) where you can tell the computer "in this part of the code, it's important that I never overflow" or "in this function, I expect multiplication and division to be inverses of each other". In which case the compiler will display diagnostic information if you do something that break those properties. It's not clear yet what is the most convenient user interface to write down and check those properties. But many believe it by using even more advanced type systems than Rust. Many other believe that we can add static analysis atop existing programming languages to obtain the same result. TLDR: Rust allows it for now because 50-100years in the future we'll have to tools to tell when it's ok and when it's not ok to multiply floats together. Right now we're still smacking rocks together to make fire.
- ajxs 4y agoMost of the work I do in systems programming languages is bare-metal code where semantics for representing and manipulating data at the bit-level are required. I need to have very explicit control over the shape of the data in memory. For starters, many of the systems I work with don't even have the ability to do floating-point calculations at all. > "Compilers (and type systems) are written by and for humans..." The abstraction that is the type system might be designed for humans, but the underlying physical layers are totally non-abstract, tangible things with their own constraints. If I'm sending data to a DAC over the i2c protocol, the protocol has very real constraints at the electrical level. In order to interact with such protocols in a meaningful way we need low-level semantics to control the shape of data at the level of individual bits. Consider the following: uint32_t a = 1234567; float b = 0.34; uint8_t c = a * b; fwrite(&c, 1, 1, stream); What value gets written to the stream here? > "first tell me the use case for multiplying a float by an int that does not lead to a float" Should the Rust compiler forbid all multiplication between the two types that doesn't get stored in a float?
- pfdietz 4y agoOne can manipulate arbitrary precision integers at the bit level. Nonnegative integers correspond to infinite sequences of bits all but a finite number of which are 0; negative numbers to infinite sequences in which all but a finite number are 1. There's no need in most programs to insist that variables have some finite (and fairly small) range beyond which integer operations stop acting like their mathematical definition.
- nybble41 4y agoSure, if you want to use arbitrary-precision integers everywhere. At a significant loss in performance.
- ajxs 4y agoThere are very real use cases where integer types with specific fixed widths are necessary, on account of very real constraints. A serial interface for instance, which is a very common peripheral outside of PCs, often sends information out over the wire 8 bits at a time. 'Systems programming languages' need to specify how arithmetic using these fixed-width integers should work.
- Strilanc 4y agoI would say the issue is which float you want. If the int is larger than 2^24 it can't be represented exactly as a float32 so you may have intended to widen to float64 which covers int32 and float32. Or maybe not; maybe you wanted performance instead. That being said, multiplying by 2 not working is pretty funny because that's always exact until you overflow the float. It's pretty annoying the compiler didn't just figure out there was no precision loss anywhere here so just do it instead of complaining.
- Gibbon1 4y ago> I would say the issue is which float you want. You're getting warmer. The reality is compilers default floating point is wrong for most uses. You should be able to represent things like pi and 1/3 exactly by default. That that's not the case in JS is criminal.
- aw1621107 4y agoSo symbolic math? That sounds like an interesting choice of details. Is it something that would produce tangible better results in a significant number of cases?
- quickthrower2 4y agoIn mathematics integers are a subset of the reals (which floats try to immitate) and when using multiplication of an int and a real you would expect the range to be real numbers. So in a static typing context float * int has to be float. Otherwise it is not * it is some other operations (e.g. * composed with floor). In dynamic typing you could decide the type at runtime. A JS runtime could in theory do that under the hood although semantically it should behave as a float.
- RHSeeger 4y agoBut in software, integers are _not_ a subset of floats. There are numbers that can be represented using an n-bit integer that cannot be represented using an n-bit float, and vice versa. Converting between the two automatically can lose information.
- throwawaymaths 4y agoThat's a bad argument because almost every floating point operation loses information of some sort of another. Should we ban all floating point operations on those grounds? Moreover in many cases int * float is an intrinsic, so on the hardware level there is no conversion operation interposed into the operator.
- aw1621107 4y ago> Moreover in many cases int * float is an intrinsic, so on the hardware level there is no conversion operation interposed into the operator. Out of curiosity, what instructions support mixed integer/floating-point operands? I can think of integer-/floating-point-only instructions off the top of my head, and I know I've seen conversion instructions, but not mixed ones. I'm hardly an assembly expert, though, so I wouldn't be surprised if I just wasn't aware of such a thing.
- throwawaymaths 4y agoFIMUL
- civilized 4y ago> To you that might be obvious. The compiler can't infer what your intentions were from your code. How does the compiler know what my intentions were when I multiplied two floats? Maybe I intended to receive a partridge in a pear tree. Intentions are not relevant here. What's relevant is what makes sense for a multiplication operator to do.
- throwawaymaths 4y agoYes and it makes sense for a multiplication operator to support float * int or float / int. Suppose you take the average height of 10 people. Converting the 10 to a float before multiplying seems wrong because the compiler is implicitly suggesting "I am not letting you take this average until you consider the possibility of non integral human count.
- throwawaymaths 4y agoGenerally speaking your argument holds true for addition and subtraction but for multiplication and division there are almost no cases where integer units * (or /) float units are unreasonable, for example "man-hours" (discrete * float) or "pizza slices/minute" or averaging people's heights (centimeters / #raw number) e.g.
- ajxs 4y agoIt's not that I don't think there's a valid case for this kind of arithmetic, it's just that the compiler doesn't really know what you're trying to accomplish, and in what context the result will be used. All of this is a consequence of a non-perfect world where the compiler can't contextually evaluate what the programmer is doing. All type systems are just abstractions designed to make it easier to express your intent while minimising mistakes. Storing the result of dividing an integer by a float, or vice-versa, in an integer is totally possible in C, but very likely not the behaviour the programmer intended. Should the compiler just forbid this? I guess what Rust has done is just one possible tradeoff. For what it's worth, Ada has actually introduced semantics for dealing with arithmetic using units of measurement[1]. 1: https://www.adacore.com/gems/gem-136-how-tall-is-a-kilogram https://www.adacore.com/gems/gem-136-how-tall-is-a-kilogram