I’m still trying to break my Python if/then habit. I have 3 transformations in this function. I’ve tried several solutions to get the logic I want in a clean way. They all seem kinda messy. I don’t know which direction to go next to get it cleaner. How would you clean this code?
def get_price_precision(price, fuzz_offset) do
price_digits_before_decimal_with_value =
case price >= 1.0 do
true -> get_digits_before_decimal(price)
false -> 0
end
fuzz_offset_dig_past_decimal = get_digits_past_decimal(fuzz_offset)
optimize_precision = price_digits_before_decimal_with_value - (fuzz_offset_dig_past_decimal + 1)
optimize_precision =
case optimize_precision > 0 do
true -> 0
false -> abs(optimize_precision)
end
price_dig_past_decimal = get_digits_past_decimal(price)
case price_dig_past_decimal > optimize_precision do
true -> price_dig_past_decimal
false -> optimize_precision
end
end
Please excuse me for not answering your question just yet but are you using float to represent a price?
Elixir has no built in support for decimal data type or decimal calculations (rounding, cutting off after nth digit etc) but Decimal library is widely used. For example, if you use Ecto, you can store price in Decimal field in database and it will get mapped to Decimal from this library.
Accidentally, using the library may simplify your code snippet
I use floats for the bulk of the module. I had to use Decimals for some numbers formatting. But still unsure when to use floats, when to use Decimals. Advantage of floats over decimals is memory right? Any other disadvantage I’d need to consider using Decimals?
Ogod, never ever use floats ever ever ever with money ever in any case, ever in any language in any system ever, like Ever!
I hope I can sum up why with a single example:
iex> (1.1 + 2.2) === 3.3
false
You will get the same answer in every language (assuming you have precise comparisons enabled).
This is just a small small sampling of the huge iceberg that are floating point. It all stems from the fact that a lot of number are just not representable in binary floating point (see the wikipedia article). Your numbers slowly diverge the more you operate on them, which is fine for games (well, not really there either, I’m sure you’ve seen physics explosions in games, that is usually becomes of floating point inaccuracies, but games need speed, so they give up accuracy for speed), but NOT fine for money. O.o
Never ever ever use floating point numbers for anything where accuracy is important, like ever.
I hope this post is clear enough, there is never ever ever a case to ever use floating point where accuracy is important, like ever ever ever. Feel free to google for more information and examples of cases and cases that have near destroyed companies.
Whops. @Emily as a rule of thumb, when dealing with money do stick to either integers (and operate on cents) or use decimals with appropriate precision. Otherwise you can loose money
@Emily on topic of this offtopic - for prices you can use this lib https://github.com/liuggio/money - it’s pretty cross-language thing to go extra mile to represent money As for the mentioned library - we used it in production, it’s good
Decimal is about as first class as a library can get. It has full support in Ecto and many of the primary devs, it will exist and be updated.
Money is however money-centric, it has a lot of stuff that will make sure things are ‘done right’, it is the traditional 3-rd party lib, but it has such a defined role that if the dev ever stopped updating it then someone else would take it up anyway.
Well you could define it to 25 decimal points or whatever if you want, or 100.
The EVM/BEAM has arbitrary sized integers so representing such values in such libraries actually remains quite efficient.
EDIT: Also, for note, 32-bit floating point with 8-9 decimal places you are losing precision, you do not want floating point especially for that. With 64-bit it goes further, but still you are not able to represent numbers accurately so you still do not want floating point.
To concur, we encountered that doing some 3rd party app for eve online. When you count distance in meter in space in js (for a webgl stuff) from the center if the sun of the system you are in… the smallest mistakes propagate fast and give strange things…
Here’s, another refactored version., minimizing the case clauses, & breaking up the function…
(It might be my python roots, but the first version to me, I can read easier & make out the transformations faster then the second. But consensus seems to be something more like the code below would be cleaner?
def get_price_precision(price, fuzz_offset) do
price_digits_before_decimal_with_value = get_price_digits_before_decimal(price)
fuzz_offset_dig_past_decimal = get_digits_past_decimal(fuzz_offset)
optimize_precision = calc_opt_price_precision(fuzz_offset_dig_past_decimal, price_digits_before_decimal_with_value)
price_dig_past_decimal = get_digits_past_decimal(price)
Enum.max([price_dig_past_decimal, optimize_precision])
end
defp get_price_digits_before_decimal(price) when price >= 1.0, do: get_digits_before_decimal(price)
defp get_price_digits_before_decimal(_), do: 0
defp calc_opt_price_precision(fuzz_offset_dig_past_decimal, price_digits_before_decimal_with_value) do
optimize_precision = price_digits_before_decimal_with_value - (fuzz_offset_dig_past_decimal + 1)
if optimize_precision > 0, do: 0, else: abs(optimize_precision)
end
Use case when you have a different check, or multiple clauses.
As soon as possible, and especially if you would otherwise start nesting if, unless or case, put the logic of it in its own function. The main advantage of this is that the logical steps you are performing now has a name.
Returns the price precision to compare against order book sample price precision. It’s used to optimize the final price precision & ending price digits to increase fill probability.
At the end of the day, “is the source code readable” is probably more important than “is this perfectly idiomatic”. So I wouldn’t go overboard with multi-headed functions just because the language supports it as an alternative way for decision making.
And indeed, if you don’t want an external library, convert everything you get in into integer µBTC (or some smaller size depending on the needed precision) and stay with integer math. Floats are for science and physics, never for money. It takes some getting used at (I once worked on a system where money was in m€ (tenths of a Eurocent), which was funny at first but really helpful in the long run).