Open albertorestifo opened 4 years ago
Meanwhile it's probably possible to encode a small enough Float32
as Float16
into a UInt16
with casting it into a UInt32
first and then doing the right dance of masks and shifts.
I tried, but it was very difficult to handle all cases properly (like NaN and Infinity). Most likely I was also doing something wrong as I'm not very familiar with bit operations, I based it off this gist.
This would need compiler support, we definitely won't find time for this before 1.0.
Actually, taking for example rust, there's no Float16
type, but there's a crate which can convert back and forth from Float32. I think a shard to do that would be the best option, and all the arithmetic is performed on Float32
. I'd like to try that approach first before adding anything to the compiler or stdlib.
That's a very good idea!
Looking at the rust crate, I have absolutely not idea what is going on here, however this function might be a very good starting point for a shard. I'll have a look and see if I can pull something good off it.
@albertorestifo the first function is a function which uses the VCVTPH2PS
X86_64 instruction to speed up the conversion from f16 to f32. That would be done in crystal with some custom assembly, but getting something working first is good, before you make it fast :)
Here it is a first basic implementation as part of the CBOR library I'm working on.
All the CBOR Float16 tests provided in the RFC are now passing, giving me a reasonable certainty that the conversion from the Rust code was correct.
I'll now focus on finishing the CBOR library before properly extracting this into a Float16 library.
LLVM exposes intrinsics for f16 conversion from/to f32 and f64 (f16 is actually an u16): https://llvm.org/docs/LangRef.html#half-precision-floating-point-intrinsics
Also see:
I guess we can add it to the standard library, given that it's an LLVM intrinsic.
I was thinking of an implementation like this:
lib LibInstrinsics
fun f16tof32 = "llvm.convert.from.fp16.f32"(Int16) : Float32
fun f16tof64 = "llvm.convert.from.fp16.f64"(Int16) : Float64
fun f32tof16 = "llvm.convert.to.fp16.f32"(Float32) : Int16
fun f64tof16 = "llvm.convert.to.fp16.f64"(Float64) : Int16
end
@[Extern]
struct Float16
@value : Int16
def self.new(value : Float32)
new LibIntrinsics.f32tof16(value)
end
def self.new(value : Float64)
new LibIntrinsics.f64tof16(value)
end
private def initialize(@value : Int16)
end
def to_f32
LibIntrinsics.f16tof32(@value)
end
def to_f64
LibIntrinsics.f16tof64(@value)
end
def to_f
to_f64
end
end
The idea is that Float16
only provides conversions to and from Float32 and Float64. We could provide all of the arithmetic methods, but I think that would be very inefficient, to convert all the time from and to Float16 and Float32/Float64.
With this API, if you have a C function that returns a Float16, because it's marked as @[Extern]
, you simply use it:
lib LibSome
fun give_me_an_f16 : Float16
fun accept_f16(value : Float16)
end
# Ask a Float16 and immediately go to safe ground: Float64
f = LibSome.give_me_an_f16.to_f64
# You do the math you need with f as a Float64
# Then you convert it to Float16 at the end:
LibSome.accept_f16(Float16.new(f))
We could also add to_f16
to Float32
and Float64
to it's a bit more convenient than doing Float16.new(...)
.
I also tried this code and it worked well:
i1 = 0b0_00000_0000000001_i16
p! LibIntrinsics.f16tof64(i1)
p! i1.unsafe_as(Float16).to_f64
i2 = 0b0_00000_1111111111_i16
p LibIntrinsics.f16tof64(i2)
p! i2.unsafe_as(Float16).to_f64
i3 = 0b0_11110_1111111111_i16
p LibIntrinsics.f16tof64(i3)
p! i3.unsafe_as(Float16).to_f64
The above are some examples found in Wikipedia.
Let me know if you think this is fine, I can send a PR.
While working on an implementation of a CBOR encoder/decoder I hit a roadblock, as the protocol allows for the transmission of half-precision floating point numbers, but crystal is lacking a
Float16
type.It would be nice to have a
Float16
type in Crystal.