diff --git a/src/clang.zig b/src/clang.zig index 704f94131d..a982b5fa34 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -506,6 +506,9 @@ pub const FloatingLiteral = opaque { pub const getValueAsApproximateDouble = ZigClangFloatingLiteral_getValueAsApproximateDouble; extern fn ZigClangFloatingLiteral_getValueAsApproximateDouble(*const FloatingLiteral) f64; + pub const getValueAsApproximateQuadBits = ZigClangFloatingLiteral_getValueAsApproximateQuadBits; + extern fn ZigClangFloatingLiteral_getValueAsApproximateQuadBits(*const FloatingLiteral, low: *u64, high: *u64) void; + pub const getBeginLoc = ZigClangFloatingLiteral_getBeginLoc; extern fn ZigClangFloatingLiteral_getBeginLoc(*const FloatingLiteral) SourceLocation; diff --git a/src/translate_c.zig b/src/translate_c.zig index ea4cdf860a..1fa535378d 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -3936,11 +3936,26 @@ fn transCPtrCast( } fn transFloatingLiteral(c: *Context, expr: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node { + // TODO use something more accurate than widening to a larger float type and printing that result switch (expr.getRawSemantics()) { .IEEEhalf, // f16 .IEEEsingle, // f32 .IEEEdouble, // f64 - => {}, + => { + var dbl = expr.getValueAsApproximateDouble(); + const is_negative = dbl < 0; // -0.0 is considered non-negative + if (is_negative) dbl = -dbl; + const str = if (dbl == @floor(dbl)) + try std.fmt.allocPrint(c.arena, "{d}.0", .{dbl}) + else + try std.fmt.allocPrint(c.arena, "{d}", .{dbl}); + var node = try Tag.float_literal.create(c.arena, str); + if (is_negative) node = try Tag.negate.create(c.arena, node); + return maybeSuppressResult(c, used, node); + }, + .x87DoubleExtended, // f80 + .IEEEquad, // f128 + => return transFloatingLiteralQuad(c, expr, used), else => |format| return fail( c, error.UnsupportedTranslation, @@ -3949,14 +3964,44 @@ fn transFloatingLiteral(c: *Context, expr: *const clang.FloatingLiteral, used: R .{format}, ), } - // TODO use something more accurate - var dbl = expr.getValueAsApproximateDouble(); - const is_negative = dbl < 0; - if (is_negative) dbl = -dbl; - const str = if (dbl == @floor(dbl)) - try std.fmt.allocPrint(c.arena, "{d}.0", .{dbl}) - else - try std.fmt.allocPrint(c.arena, "{d}", .{dbl}); +} + +fn transFloatingLiteralQuad(c: *Context, expr: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node { + assert(switch (expr.getRawSemantics()) { + .x87DoubleExtended, .IEEEquad => true, + else => false, + }); + + var low: u64 = undefined; + var high: u64 = undefined; + expr.getValueAsApproximateQuadBits(&low, &high); + var quad: f128 = @bitCast(low | @as(u128, high) << 64); + const is_negative = quad < 0; // -0.0 is considered non-negative + if (is_negative) quad = -quad; + + // TODO implement decimal format for f128 + // in the meantime, if the value can be roundtripped by casting it to f64, serializing it to + // the decimal format and parsing it back as the exact same f128 value, then use that serialized form + const str = fmt_decimal: { + var buf: [512]u8 = undefined; // should be large enough to print any f64 in decimal form + const dbl: f64 = @floatCast(quad); + const temp_str = if (dbl == @floor(dbl)) + std.fmt.bufPrint(&buf, "{d}.0", .{dbl}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + } + else + std.fmt.bufPrint(&buf, "{d}", .{dbl}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + }; + const could_roundtrip = if (std.fmt.parseFloat(f128, temp_str)) |parsed_quad| + quad == parsed_quad + else |_| + false; + break :fmt_decimal if (could_roundtrip) try c.arena.dupe(u8, temp_str) else null; + } + // otherwise, fall back to the hexadecimal format + orelse try std.fmt.allocPrint(c.arena, "{x}", .{quad}); + var node = try Tag.float_literal.create(c.arena, str); if (is_negative) node = try Tag.negate.create(c.arena, node); return maybeSuppressResult(c, used, node); diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index 6930827a7a..99a5ae22c4 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -3245,6 +3245,17 @@ double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatin return casted->getValueAsApproximateDouble(); } +void ZigClangFloatingLiteral_getValueAsApproximateQuadBits(const ZigClangFloatingLiteral *self, uint64_t *low, uint64_t *high) { + auto casted = reinterpret_cast(self); + llvm::APFloat apf = casted->getValue(); + bool ignored; + apf.convert(llvm::APFloat::IEEEquad(), llvm::APFloat::rmNearestTiesToEven, &ignored); + const llvm::APInt api = apf.bitcastToAPInt(); + const uint64_t *api_data = api.getRawData(); + *low = api_data[0]; + *high = api_data[1]; +} + struct ZigClangSourceLocation ZigClangFloatingLiteral_getBeginLoc(const struct ZigClangFloatingLiteral *self) { auto casted = reinterpret_cast(self); return bitcast(casted->getBeginLoc()); diff --git a/src/zig_clang.h b/src/zig_clang.h index ff58d7bde8..eaa1f74786 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -1510,6 +1510,7 @@ ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangDeclStmt_getBeginLoc(const st ZIG_EXTERN_C unsigned ZigClangAPFloat_convertToHexString(const struct ZigClangAPFloat *self, char *DST, unsigned HexDigits, bool UpperCase, enum ZigClangAPFloat_roundingMode RM); ZIG_EXTERN_C double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatingLiteral *self); +ZIG_EXTERN_C void ZigClangFloatingLiteral_getValueAsApproximateQuadBits(const ZigClangFloatingLiteral *self, uint64_t *low, uint64_t *high); ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangFloatingLiteral_getBeginLoc(const struct ZigClangFloatingLiteral *); ZIG_EXTERN_C ZigClangAPFloatBase_Semantics ZigClangFloatingLiteral_getRawSemantics(const ZigClangFloatingLiteral *self); diff --git a/test/translate_c.zig b/test/translate_c.zig index fb4ab33480..a8045cbf1f 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1240,6 +1240,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\extern const float my_float = 1.0f; \\extern const double my_double = 1.0; \\extern const long double my_longdouble = 1.0l; + \\extern const long double my_extended_precision_longdouble = 1.0000000000000003l; , &([_][]const u8{ "pub const foo = @as(f32, 3.14);", "pub const bar = @as(c_longdouble, 16.0e-2);", @@ -1250,13 +1251,14 @@ pub fn addCases(cases: *tests.TranslateCContext) void { "pub const foobar = -@as(c_longdouble, 73.0);", "pub export const my_float: f32 = 1.0;", "pub export const my_double: f64 = 1.0;", - } ++ if (@bitSizeOf(c_longdouble) != 64) .{ - // TODO properly translate non-64-bit long doubles - "source.h:10:42: warning: unsupported floating point constant format", - "source.h:10:26: warning: unable to translate variable initializer, demoted to extern", - "pub extern const my_longdouble: c_longdouble;", - } else .{ "pub export const my_longdouble: c_longdouble = 1.0;", + switch (@bitSizeOf(c_longdouble)) { + // TODO implement decimal format for f128 + // (so that f80/f128 values not exactly representable as f64 can be emitted in decimal form) + 80 => "pub export const my_extended_precision_longdouble: c_longdouble = 0x1.000000000000159ep0;", + 128 => "pub export const my_extended_precision_longdouble: c_longdouble = 0x1.000000000000159e05f1e2674d21p0;", + else => "pub export const my_extended_precision_longdouble: c_longdouble = 1.0000000000000002;", + }, })); cases.add("macro defines hexadecimal float",