How can I get the fractional part of a float only?

Convert the float to a really large fixed decimal int, then zero the integer part and convert the fractional part to string :wink: This yields no rounding errors.

This will probably come across as a bit superficial from me but I prefer to go for whatever’s most performant, and to me just doing one trunc and one floating-point subtraction is quicker than multiplying a float by 1_000_000 and doing what you described.

It probably won’t ever matter but it’s how I do things. :blush:

The Decimal solution by @dimitarvp is a good one, but please be aware that some of the other techniques suggested in this thread are misguided in general. It is not true that subtracting, truncating and multiplying by 1_000_000 leads to a small error. In many cases it will, but in some it will be completely off.

Let me make a simple example, let’s say our initial number is simply x = 1.001:

x = 1.001

# WRONG! Do not do this!
trunc((x - trunc(x)) * 1_000_000)
#=> 999

Here’s the problem:

trunc(x)
#=> 1

So far so good…

x - trunc(x)
#=> 9.999999999998899e-4

Loosing precision, as the result cannot be exactly represented as a float,

(x - trunc(x)) * 1_000_000
#=> 999.9999999998898

Here we are amplifying the error by a million, definitely not a good idea.

trunc((x - trunc(x)) * 1_000_000)
#=> 999

And here we basically ended up throwing away more precision. The end result is off by 1 part in 1000, which is huge in many applications. To see an even more extreme case, perform the same calculation with x = 1.000001 and you will get 0 instead of the expected result of 1.

Floating point arithmetic is tricky, and the idea that it’s precise enough is a common fallacy. Be aware of this and try to understand the implications of using floats if your code needs to perform calculations!

5 Likes

yep, it is–outer trunc should have been round…

1 Like

Just to be sure, I did not mean to sound too extreme. Pretty much everyone in this thread is correctly stating that floats will lead to a loss of precision. My concern was that someone reading this thread might incorrectly get the idea that these losses would be small enough to ignore in practical cases.

Performing operations as simple as subtractions and divisions on floating point numbers will lead to loss of precision and surprising results (e.g. 0.9 - 0.8 results in 0.09999999999999998). Multiplications and truncations will make these errors larger. In some applications, that is totally fine. In many others, it is not. In general, we must be aware of the issue.

1 Like

Hello two years later. Since you have the time in fractional seconds, can you multiply it by 1M to get the time in microseconds, the create the DateTime from that? No need for Decimal in this case.

iex(1)> round(1485425805.49934 * 1_000_000)
1485425805499340
iex(2)> |> DateTime.from_unix!(:microsecond)
~U[2017-01-26 10:16:45.499340Z]
iex(3)> |> DateTime.to_naive()
~N[2017-01-26 10:16:45.499340]

This does what you mean: Get the time specified by the float in microseconds, rounded to the nearest one, then convert that to a DateTime. Artificially converting the float to a decimal in order to truncate off the fractional part is a bit of a diversion, because the fractional part is not a precise value anyway.

1 Like

I would do that if I trusted floats not to introduce a tiny inaccuracy – but I don’t trust them.

It’s the same as with money: don’t use 32/64 bit floats. Use dedicated libraries that are always precise.

So I have a float

I think this thread was doomed from the first sentence, in that case :slight_smile:

1 Like

I probably should have clarified in OP that these floats come from well-formed decimals in a database.

So the main objective back then was not to lose precision while doing the conversion. Hence – not using 32/64 bit floats.

Note that the suggested solutions do not work correctly for negative values :wink:

(microseconds element of tuple must be positive integer…)

Oh yeah, definitely, but that’s trivially fixed.

I’m late to this party but I had to work out a reasonable solution to this for ex_cldr_numbers localized number formatting. You can see the (quite complex) implementation here which in turn leverages float decoding from this erlang library. Its not built for speed but tries to be as accurate as can be.

Using your example above:

iex> Cldr.Digits.fraction_as_integer 1485425805.49934
49934

There are some other functions for working with floats (and decimals) in cldr_utils that I’ve found useful:

iex> Cldr.Digits.number_of_digits(1234.56789098765)
15
iex> Cldr.Digits.number_of_integer_digits(1234)
4
iex> Cldr.Digits.number_of_integer_digits(1234.456)
4
iex> Cldr.Digits.number_of_trailing_zeros(123000)
3
iex> Cldr.Digits.remove_trailing_zeros(1234000)
1234
2 Likes

i-should-buy-a-boat-cat

2 Likes