Skip to content

Commit df9eb7d

Browse files
committed
Fix rounding of Float32->Float16 near eps(Float16(0.0))
The originally described algorithm worked only for round-to-zero, and our adjustment to make it round-to-nearest was incorrect for values that result in Float16 subnormals. Fix this at the expense of an extra `|` and `&`.
1 parent 7fa1332 commit df9eb7d

File tree

3 files changed

+33
-12
lines changed

3 files changed

+33
-12
lines changed

base/float.jl

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,21 @@ end
140140
# Float32 -> Float16 algorithm from:
141141
# "Fast Half Float Conversion" by Jeroen van der Zijp
142142
# ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf
143-
143+
#
144+
# With adjustments for round-to-nearest, ties to even.
145+
#
144146
let _basetable = Vector{UInt16}(undef, 512),
145147
_shifttable = Vector{UInt8}(undef, 512)
146148
for i = 0:255
147149
e = i - 127
148-
if e < -24 # Very small numbers map to zero
150+
if e < -25 # Very small numbers map to zero
149151
_basetable[i|0x000+1] = 0x0000
150152
_basetable[i|0x100+1] = 0x8000
151-
_shifttable[i|0x000+1] = 24
152-
_shifttable[i|0x100+1] = 24
153+
_shifttable[i|0x000+1] = 25
154+
_shifttable[i|0x100+1] = 25
153155
elseif e < -14 # Small numbers map to denorms
154-
_basetable[i|0x000+1] = (0x0400>>(-e-14))
155-
_basetable[i|0x100+1] = (0x0400>>(-e-14)) | 0x8000
156+
_basetable[i|0x000+1] = 0x0000
157+
_basetable[i|0x100+1] = 0x8000
156158
_shifttable[i|0x000+1] = -e-1
157159
_shifttable[i|0x100+1] = -e-1
158160
elseif e <= 15 # Normal numbers just lose precision
@@ -182,10 +184,14 @@ function Float16(val::Float32)
182184
t = 0x8000 (0x8000 & ((f >> 0x10) % UInt16))
183185
return reinterpret(Float16, t ((f >> 0xd) % UInt16))
184186
end
185-
i = (f >> 23) & 0x1ff + 1
187+
i = ((f & ~significand_mask(Float32)) >> significand_bits(Float32)) + 1
186188
@inbounds sh = shifttable[i]
187-
f &= 0x007fffff
188-
@inbounds h = (basetable[i] + (f >> sh)) % UInt16
189+
f &= significand_mask(Float32)
190+
# If `val` is subnormal, the tables are set up to force the
191+
# result to 0, so the significand has an implicit `1` in the
192+
# cases we care about.
193+
f |= significand_mask(Float32) + 0x1
194+
@inbounds h = (basetable[i] + (f >> sh) & significand_mask(Float16)) % UInt16
189195
# round
190196
# NOTE: we maybe should ignore NaNs here, but the payload is
191197
# getting truncated anyway so "rounding" it might not matter
@@ -867,6 +873,11 @@ exponent_one(::Type{Float16}) = 0x3c00
867873
exponent_half(::Type{Float16}) = 0x3800
868874
significand_mask(::Type{Float16}) = 0x03ff
869875

876+
for T in (Float16, Float32, Float64)
877+
@eval significand_bits(::Type{$T}) = $(trailing_ones(significand_mask(T)))
878+
@eval exponent_bits(::Type{$T}) = $(sizeof(T)*8 - significand_bits(T) - 1)
879+
end
880+
870881
# integer size of float
871882
uinttype(::Type{Float64}) = UInt64
872883
uinttype(::Type{Float32}) = UInt32

base/math.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import .Base: log, exp, sin, cos, tan, sinh, cosh, tanh, asin,
2121
exp10, expm1, log1p
2222

2323
using .Base: sign_mask, exponent_mask, exponent_one,
24-
exponent_half, uinttype, significand_mask
24+
exponent_half, uinttype, significand_mask,
25+
significand_bits, exponent_bits
2526

2627
using Core.Intrinsics: sqrt_llvm
2728

@@ -38,8 +39,6 @@ end
3839
end
3940

4041
for T in (Float16, Float32, Float64)
41-
@eval significand_bits(::Type{$T}) = $(trailing_ones(significand_mask(T)))
42-
@eval exponent_bits(::Type{$T}) = $(sizeof(T)*8 - significand_bits(T) - 1)
4342
@eval exponent_bias(::Type{$T}) = $(Int(exponent_one(T) >> significand_bits(T)))
4443
# maximum float exponent
4544
@eval exponent_max(::Type{$T}) = $(Int(exponent_mask(T) >> significand_bits(T)) - exponent_bias(T))

test/float16.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,14 @@ end
166166

167167
# issue #17148
168168
@test rem(Float16(1.2), Float16(one(1.2))) == 0.20019531f0
169+
170+
# issue #32441
171+
const f16eps2 = Float32(eps(Float16(0.0)))/2
172+
const minsubf16 = nextfloat(Float16(0.0))
173+
const minsubf16_32 = Float32(minsubf16)
174+
@test Float16(f16eps2) == Float16(0.0)
175+
@test Float16(nextfloat(f16eps2)) == minsubf16
176+
@test Float16(prevfloat(minsubf16_32)) == minsubf16
177+
# Ties to even, in this case up
178+
@test Float16(minsubf16_32 + f16eps2) == nextfloat(minsubf16)
179+
@test Float16(prevfloat(minsubf16_32 + f16eps2)) == minsubf16

0 commit comments

Comments
 (0)