mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-25 06:21:35 +00:00
fixes float to 128-bit int/uint conversion
This commit is contained in:
@@ -91,22 +91,107 @@ floattidf_unsigned :: proc "c" (a: u128) -> f64 {
|
||||
}
|
||||
|
||||
|
||||
// f64 -> unsigned integer conversion (truncating toward zero)
|
||||
// port of clang/compiler-rt's fp_fixuint_impl.inc;
|
||||
// decompose the f64 into significand and exponent, shift the significand into place,
|
||||
// saturate out of range values
|
||||
@(private="file")
|
||||
fixuint :: proc "contextless" ($U: typeid, a: f64) -> U where intrinsics.type_is_unsigned(U) {
|
||||
BITS :: 8 * size_of(U)
|
||||
SIGNIFICAND_BITS :: 52
|
||||
EXPONENT_BIAS :: 1023
|
||||
|
||||
IMPLICIT_BIT :: (u64(1) << SIGNIFICAND_BITS)
|
||||
SIGNIFICAND_MASK :: (IMPLICIT_BIT - 1)
|
||||
|
||||
// Break a into sign, exponent, significand parts.
|
||||
a_rep := transmute(u64)a
|
||||
negative := (a_rep >> 63) != 0
|
||||
exponent := i32((a_rep >> SIGNIFICAND_BITS) & 0x7ff) - EXPONENT_BIAS
|
||||
significand := (a_rep & SIGNIFICAND_MASK) | IMPLICIT_BIT
|
||||
|
||||
// If either the value or the exponent is negative, the result is zero.
|
||||
if negative || exponent < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// If the value is too large for the integer type, saturate.
|
||||
if exponent >= BITS {
|
||||
return max(U)
|
||||
}
|
||||
|
||||
// If 0 <= exponent < SIGNIFICAND_BITS, right shift to get the result.
|
||||
// Otherwise, shift left.
|
||||
if exponent < SIGNIFICAND_BITS {
|
||||
return U(significand >> u32(SIGNIFICAND_BITS - exponent))
|
||||
}
|
||||
return U(significand) << u32(exponent - SIGNIFICAND_BITS)
|
||||
}
|
||||
|
||||
// f64 -> signed integer conversion (truncating toward zero)
|
||||
// port of clang/compiler-rt's fp_fixint_impl.inc;
|
||||
// decompose the f64 into significand and exponent, shift the significand into place,
|
||||
// saturate out of range values
|
||||
@(private="file")
|
||||
fixint :: proc "contextless" ($T: typeid, a: f64) -> T where intrinsics.type_is_integer(T) && !intrinsics.type_is_unsigned(T) {
|
||||
BITS :: 8 * size_of(T)
|
||||
SIGNIFICAND_BITS :: 52
|
||||
EXPONENT_BIAS :: 1023
|
||||
|
||||
IMPLICIT_BIT :: (u64(1) << SIGNIFICAND_BITS)
|
||||
SIGNIFICAND_MASK :: (IMPLICIT_BIT - 1)
|
||||
|
||||
// Break a into sign, exponent, significand parts.
|
||||
a_rep := transmute(u64)a
|
||||
negative := (a_rep >> 63) != 0
|
||||
exponent := i32((a_rep >> SIGNIFICAND_BITS) & 0x7ff) - EXPONENT_BIAS
|
||||
significand := (a_rep & SIGNIFICAND_MASK) | IMPLICIT_BIT
|
||||
|
||||
// If exponent is negative, the result is zero.
|
||||
if exponent < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// If the value is too large for the integer type, saturate;
|
||||
// (the only exactly representable value with exponent BITS-1 is min(T))
|
||||
// this deviates from clang/compiler-rt (it instead wraps from BITS-1 to BITS,
|
||||
// that is, f64(1 << 127) would wrap to min(i128) (for T=i128)),
|
||||
// while this saturates consistently
|
||||
if exponent >= BITS - 1 {
|
||||
return min(T) if negative else max(T)
|
||||
}
|
||||
|
||||
// If 0 <= exponent < SIGNIFICAND_BITS, right shift to get the result.
|
||||
// Otherwise, shift left. After saturation the magnitude is below
|
||||
// 1 << (BITS-1), so it fits in T and negation can't overflow
|
||||
r: T
|
||||
if exponent < SIGNIFICAND_BITS {
|
||||
r = T(significand >> u32(SIGNIFICAND_BITS - exponent))
|
||||
} else {
|
||||
r = T(significand) << u32(exponent - SIGNIFICAND_BITS)
|
||||
}
|
||||
return -r if negative else r
|
||||
}
|
||||
|
||||
@(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 {
|
||||
// TODO(bill): implement `fixunsdfti` correctly
|
||||
x := u64(a)
|
||||
return u128(x)
|
||||
fixunsdfti :: proc "c" (a: f64) -> u128 {
|
||||
return fixuint(u128, a)
|
||||
}
|
||||
|
||||
@(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 {
|
||||
// TODO(bill): implement `fixunsdfdi` correctly
|
||||
x := i64(a)
|
||||
return i128(x)
|
||||
fixunsdfdi :: proc "c" (a: f64) -> u64 {
|
||||
return fixuint(u64, a)
|
||||
}
|
||||
|
||||
@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
fixdfti :: proc "c" (a: f64) -> i128 {
|
||||
return fixint(i128, a)
|
||||
}
|
||||
|
||||
@(link_name="__fixdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
fixdfdi :: proc "c" (a: f64) -> i64 {
|
||||
return fixint(i64, a)
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
@@ -173,45 +258,3 @@ divti3 :: proc "c" (a, b: i128) -> i128 {
|
||||
return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
fixdfti :: proc "c" (a: u64) -> i128 {
|
||||
significandBits :: 52
|
||||
typeWidth :: (size_of(u64)*8)
|
||||
exponentBits :: (typeWidth - significandBits - 1)
|
||||
maxExponent :: ((1 << exponentBits) - 1)
|
||||
exponentBias :: (maxExponent >> 1)
|
||||
|
||||
implicitBit :: (u64(1) << significandBits)
|
||||
significandMask :: (implicitBit - 1)
|
||||
signBit :: (u64(1) << (significandBits + exponentBits))
|
||||
absMask :: (signBit - 1)
|
||||
exponentMask :: (absMask ~ significandMask)
|
||||
|
||||
// Break a into sign, exponent, significand
|
||||
aRep := a
|
||||
aAbs := aRep & absMask
|
||||
sign := i128(-1 if aRep & signBit != 0 else 1)
|
||||
exponent := u64((aAbs >> significandBits) - exponentBias)
|
||||
significand := u64((aAbs & significandMask) | implicitBit)
|
||||
|
||||
// If exponent is negative, the result is zero.
|
||||
if exponent < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// If the value is too large for the integer type, saturate.
|
||||
if exponent >= size_of(i128) * 8 {
|
||||
return max(i128) if sign == 1 else min(i128)
|
||||
}
|
||||
|
||||
// If 0 <= exponent < significandBits, right shift to get the result.
|
||||
// Otherwise, shift left.
|
||||
if exponent < significandBits {
|
||||
return sign * i128(significand >> (significandBits - exponent))
|
||||
} else {
|
||||
return sign * (i128(significand) << (exponent - significandBits))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3982,7 +3982,7 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb
|
||||
add_package_dependency(c, "runtime", "floattidf", REQUIRE);
|
||||
} else if (is_type_integer_128bit(dst) && is_type_float(src)) {
|
||||
add_package_dependency(c, "runtime", "fixunsdfti", REQUIRE);
|
||||
add_package_dependency(c, "runtime", "fixunsdfdi", REQUIRE);
|
||||
add_package_dependency(c, "runtime", "fixdfti", REQUIRE);
|
||||
} else if (src == t_f16 && is_type_float(dst)) {
|
||||
add_package_dependency(c, "runtime", "gnu_h2f_ieee", REQUIRE);
|
||||
add_package_dependency(c, "runtime", "extendhfsf2", REQUIRE);
|
||||
|
||||
@@ -2538,8 +2538,8 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
|
||||
TEMPORARY_ALLOCATOR_GUARD();
|
||||
|
||||
auto args = array_make<lbValue>(temporary_allocator(), 1);
|
||||
args[0] = value;
|
||||
char const *call = "fixunsdfdi";
|
||||
args[0] = lb_emit_conv(p, value, t_f64);
|
||||
char const *call = "fixdfti";
|
||||
if (is_type_unsigned(dst)) {
|
||||
call = "fixunsdfti";
|
||||
}
|
||||
|
||||
48
tests/internal/test_128_float_conversion.odin
Normal file
48
tests/internal/test_128_float_conversion.odin
Normal file
@@ -0,0 +1,48 @@
|
||||
package test_internal
|
||||
|
||||
import "core:testing"
|
||||
|
||||
// #force_no_inline so conversions are not folded;
|
||||
// they must go through the runtime __fixunsdfti/__fixdfti
|
||||
@(private="file") f64_to_u128 :: #force_no_inline proc(f: f64) -> u128 { return u128(f) }
|
||||
@(private="file") f64_to_i128 :: #force_no_inline proc(f: f64) -> i128 { return i128(f) }
|
||||
@(private="file") f32_to_u128 :: #force_no_inline proc(f: f32) -> u128 { return u128(f) }
|
||||
@(private="file") f32_to_i128 :: #force_no_inline proc(f: f32) -> i128 { return i128(f) }
|
||||
|
||||
@test
|
||||
test_f64_to_u128 :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, f64_to_u128(2.75), 2) // trunc toward 0
|
||||
testing.expect_value(t, f64_to_u128(0.75), 0) // < 1
|
||||
testing.expect_value(t, f64_to_u128(-3.5), 0) // negative to 0
|
||||
testing.expect_value(t, f64_to_u128(f64(1 << 51)), u128(1) << 51) // significand shifts right
|
||||
testing.expect_value(t, f64_to_u128(f64(1 << 52)), u128(1) << 52) // no shift
|
||||
testing.expect_value(t, f64_to_u128(f64(1 << 53)), u128(1) << 53) // significand shifts left
|
||||
testing.expect_value(t, f64_to_u128(1e19), 10_000_000_000_000_000_000)
|
||||
testing.expect_value(t, f64_to_u128(f64(1 << 80)), u128(1) << 80) // > max(u64)
|
||||
testing.expect_value(t, f64_to_u128(2 * f64(1 << 127)), max(u128)) // out of range saturates, implementation specific
|
||||
}
|
||||
|
||||
@test
|
||||
test_f64_to_i128 :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, f64_to_i128(-2.75), -2) // trunc toward 0
|
||||
testing.expect_value(t, f64_to_i128(-0.75), 0) // |f| < 1
|
||||
testing.expect_value(t, f64_to_i128(1e19), 10_000_000_000_000_000_000) // > max(i64)
|
||||
testing.expect_value(t, f64_to_i128(f64(-(1 << 80))), -(i128(1) << 80))
|
||||
testing.expect_value(t, f64_to_i128(f64(1 << 126)), i128(1) << 126)
|
||||
testing.expect_value(t, f64_to_i128(f64(min(i128))), min(i128)) // exact
|
||||
// out of range saturates, implementation specific
|
||||
testing.expect_value(t, f64_to_i128(f64(1 << 127)), max(i128))
|
||||
testing.expect_value(t, f64_to_i128(2 * f64(1 << 127)), max(i128))
|
||||
}
|
||||
|
||||
@test
|
||||
test_f32_to_u128 :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, f32_to_u128(2.75), 2) // trunc toward 0
|
||||
testing.expect_value(t, f32_to_u128(f32(1 << 80)), u128(1) << 80) // > max(u64)
|
||||
}
|
||||
|
||||
@test
|
||||
test_f32_to_i128 :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, f32_to_i128(-2.75), -2) // trunc toward 0
|
||||
testing.expect_value(t, f32_to_i128(f32(-(1 << 80))), -(i128(1) << 80))
|
||||
}
|
||||
Reference in New Issue
Block a user