Safely calculating floats on the stack

I'm extremely new to Rust - still working my way through the book - but my intended use case will need to calculate floats. I'm aware of the rounding issues (e.g. 0.1 + 0.2 != 0.3) yet my application will need to perform calculations on floating point numbers and speed optimisation will be essential.

2 questions on this:

  1. Am I right in assuming that the rust_decimal crate would come at the penalty of performing calculations on the heap?

  2. If I know beforehand that my (all positive) floats will definitely fit within a u8 once multiplied by, say, 1,000,000, and, if I understand correctly, a u8 can live and be calculated on the stack as its size is predetermined, would the workflow float -> u8 -> [calculations] -> float be likely to give me a speed advantage over using rust_decimal?

For context, the app will be processing thousands of floats per second.

Many thanks

No, Decimal is just a regular 128 bit struct that goes on your stack frame if you don't explicitly put it on the heap, i.e. by boxing it.

That sounds like you should benchmark it for your concrete use-case to be sure which one will be faster. Maybe correctness (rounding errors) must also be considered?

1 Like

Hi @peedrr!

What makes you think rust_decimal would perform calculations on the heap? A quick look at the source code tells me that its Decimal structure does not allocate, and common operations do not need heap allocations either. I believe, unless you need some fancy numeric operation, you're fine :slight_smile:

That being said, if your calculations can be done on u8s (as your second point seems to suggest), that will almost definitely be much faster than using rust_decimal. However, I find it hard to imagine that you would run into rounding issues with f64s for a calculation that can be performed using only u8s. Also note that rust_decimal::Decimal is not arbitrary precision, so operations on these numbers will involve rounding as well. (Just in an different way than for floating-point numbers.)

1 Like

Thank you both!

I did take a quick peek at the code for Decimal but my knowledge of Rust is not yet good enough to make sense of what I'm seeing :upside_down_face:

Hmm... back to maths class for me!

Two wrong assumptions in one post. Impressive! :sweat_smile:

1 Like

Actually, not a bad first assumption, if you didn't look at the docs/source for rust_decimal, and especially if you've used something like BigDecimal/BigInteger in Java (which would generally get allocated on the heap, as it is backed by a variable-length byte array), then you might assume Decimal would have to be unsized and thus stored on the heap.

1 Like

This sounds like you might be wanting to work with fixed point numbers (non-integer numbers with a known number of digits of precision, whether in base 2 or base 10 or anything else), not floating point numbers.

Can you tell us more about the actual input data and what computations you want to perform? We can likely recommend a suitable number type with much more accuracy then.

4 Likes

@kpreid has the right of it: work out the desired numerical properties of your algorithm before you start worrying about the minutiae of where those numbers are stored. Floats, decimals, and integers are not interchangeable - similar algorithms on different numeric types produce different results with different characteristics. Switching between those types may turn a correct program into an incorrect one, depending on your specific needs, and it barely matters how fast an incorrect program is.

For context, the app will be processing thousands of floats per second.

Then the purely numerical performance barely matters. Modern CPUs are capable of floating point arithmetic at speeds in the giga-to-teraflop rate; a few thousand numbers is nothing. Other parts of your algorithm, such as scheduling IO to avoid unnecessary waits, are going to dominate.

5 Likes

Thank you all for your responses. This is my first interaction with the Rust community and so far I'm very impressed!

This is exactly correct. It is financial data where the fractional part is known and fixed. The integer part might be somewhat variable, although I guess a sensible upper limit could be established.

Any recommendations for number types would greatly be appreciated. I'm not sure if it's relevant - what I've learned so far suggests it could be: the raw data comes from various sources which each work to their own level of (fixed) precision. So recommendations will either assume adding trailing zeros to the fractional part to create a common number type, or being able to perform calculations on types with different levels of precision. Given a choice between these two, speed will be the deciding factor.

Thanks again

I 100% get this. And funnily enough this is etched into my brain for my day job, where you ALWAYS complete the what before even considering the how. It's funny how some working practices don't automatically transfer! Your statement is a very good reminder, thank you!

Use a crate that gives you an abstraction over monetary values. Not floating point and not fixed point.

Monetary values have a strictly fixed decimal which makes fixed-point numbers seem like a good fit. But with financial calculations, rounding modes really matter. And sometimes i18n concerns do, too. Using . and , appropriately, choosing and placing the currency symbol before/after the value, and so on.

rusty-money is decent. From firsthand experience.

4 Likes