# BigFloat number conversion problem

Here is the problem in a nutshell:
julia> BigFloat(3.825)
3.82500000000000017763568394002504646778106689453125
julia>BigFloat(3825//1000)
3.825000000000000000000000000000000000000000000000000000000000000000000000000007

In the first one it looks like a Float64 is taking place before BigFloat gets it and the rest is noise.

Use the big"..." non-standard literal, eg

julia> big"3.825"
3.825000000000000000000000000000000000000000000000000000000000000000000000000007

3 Likes

Thatâ€™s true - as far as I remember, this already happens at parsing level, long before the call converts its argument.

Seems like a problem to me. Any sane way to get around this other than turning the number into a rational

Related to this if you try to convert the 3.825 into a rational you do not get 3825//1000 or reduced 153//40 so the fault is at the string to Float64 conversion since it should be exact in binary. I see 2 problems, a Float64 conversion that is wrong and a Float64 conversion that shouldnâ€™t happen.

Thanks Tamas for big"8.325" that works correctly
julia>big"8.325"
3.825000000000000000000000000000000000000000000000000000000000000000000000000007
In the online manual they show big() which would indicate
big(3.825) or big(â€ś3.825â€ť)
julia> big(3.825)
3.82500000000000017763568394002504646778106689453125
julia> big(â€ś3.825â€ť)
ERROR: MethodError: no method matching big(::String)
Closest candidates are:
big(::Type{Complex{T<:Real}}) where T<:Real at complex.jl:969
big(::Type{#s561} where #s561<:Integer) at gmp.jl:403
big(::Type{#s561} where #s561<:Rational) at gmp.jl:404
â€¦
Stacktrace:
[1] top-level scope at none:0
There is some language inconsistency here that probably needs to be addressed.

Could you please post a link to where you found this? If you want to create a BigFloat from a regular string, a correct way would be this:

julia> BigFloat("3.825")
3.825000000000000000000000000000000000000000000000000000000000000000000000000007


the big() function converts an existing number to its â€śbigâ€ť (i.e. maximum precision) representation, according to its type:

julia> big(1)
1

julia> typeof(big(1))
BigInt

julia> big(1.0)
1.0

julia> typeof(big(1.0))
BigFloat

1 Like

Why do you say that 3.825 should be exact in binary?

I donâ€™t think it is: 3.825 = \frac{153}{40} = \frac1{2^3} \left(30 + \frac35\right), and the binary representation of \frac35 has an infinite cyclic sequence (much like the infamous 1/10):

\frac35 =(0.1001100110011\ldots)_2

So the mantissa cannot be represented exactly with a finite number of bits.

4 Likes

@ffevotte you are certainly correct. I should have expanded it in binary before making such a dumb statement.

That blunder aside, I still think there is an issue using BigFloat but donâ€™t have any good suggestion for a clean solution. However, there might be some way to pass the initial string as well as the converted number and let the function decide which to take and convert. I know that sounds very clumsy but using big"3.825" just seems very wrong and inconsistent language-wise.

big is under base.big in the julialang.org manual and yes, BigFloat(â€ś3.825â€ť) also works and is better than using big"3.825" which is rather hokey syntax.

It is part of the supported API. If anything, the odd one out is BigFloat(::AbstractString), as other float types just have a (try)parse, but it makes sense since one may need to specify the precision.

1 Like

Note that you can get a rational with

rationalize(3.825)
2 Likes

Yes, but in this case, 3.825 gets first parsed as a Float64 (and therefore rounded), and only then is it converted to a rational. Depending on the tolerance given to rationalize, one may not always get \frac{153}{40} as one might expect.

This happens to work, because the round-off error caused by the conversion to Float64 is compensated by the approximation in rationalize:

julia> rationalize(3.825, tol=1e-15)
153//40


When decreasing the tolerance, rationalize produces a better approximation of Float64(3.825), but not of 3.825 itself.

julia> rationalize(3.825, tol=1e-20)
538290589893019//140729565985103


Whereas for big"3.825" and with the same level of tolerance, rationalize produces the â€ścorrectâ€ť result (which, again, is not guaranteed, but happens to be the case because round-off errors compensate)

julia> rationalize(big"3.825", tol=1e-20)
153//40

4 Likes

Iâ€™m not sure I understand what you find inconsistent. Would you rather have a special syntax understood by the parser to define BigFloat literals, like we have 1.0f0 to define Float32 literals instead of Float64? I might be missing something, but I would say that Float32 is the exception here (perhaps because it is both very useful and supported by the hardware).

All other numeric types that I know of (big integers, 32-bit integers on a 64-bit architecture, big floats, half-precision floatsâ€¦) must be built explicitly, either using their constructor (e.g. BigInt, Int32, BigFloatâ€¦) to convert them from another numeric representation, or using parse to read them from a string. So all this seems rather consistent to me.

I see the big"..." string macro merely as syntactic sugar to avoid calling parse (or rather tryparse in this case) for BigInt or BigFloat literals.

2 Likes

At least unsigned integers, hexadecimals, binary and octal also have special syntax: https://docs.julialang.org/en/v1/manual/integers-and-floating-point-numbers/#Integers-1

1 Like

True. I guess I was not explicit enough, then. What I wanted to say is that, if you want to build a numeric value of a specific type, based on its decimal representation* (an important mention that I forgot), I think that you have to explicitly parse a string except for very few specific types: Int (32 or 64 depending on your architecture), Float32 and Float64.

Itâ€™s true that you can build an UInt with special syntax, but then you have to change the radix and use a binary, octal or hexadecimal representation (and BTW I would consider hexadecimal, binary and octal to be number representations, not types). If you want to represent the value 25 as an UInt (as opposed to creating a bitmask), you either have to make the conversion in your head and write 0x19 or be explicit and call the relevant constructor UInt(25) or parse(UInt, "25").

* I make the distinction here because I think most mere humans nowadays think of numeric values in terms of their decimal representations. I would even venture to say that developers (although they are much more than mere humans ) only depart from radix 10 in very specific situations, for example when building bitmasks (in which case the value of the number is of somewhat lesser importanceâ€¦) Of course I might be wrong.

No definitely not. The only thing you need to be explicitly about is the type.

Is by no mean parsing a string and

Donâ€™t use this as literal.