Index: include/clang/Basic/Builtins.def =================================================================== --- include/clang/Basic/Builtins.def +++ include/clang/Basic/Builtins.def @@ -45,6 +45,8 @@ // SJ -> sigjmp_buf // K -> ucontext_t // p -> pid_t +// r -> _Fract +// k -> _Accum // . -> "...". This may only occur at the end of the function list. // // Types may be prefixed with the following modifiers: @@ -1362,6 +1364,22 @@ LIBBUILTIN(_Block_object_dispose, "vvC*iC", "f", "Blocks.h", ALL_LANGUAGES) // FIXME: Also declare NSConcreteGlobalBlock and NSConcreteStackBlock. +// ISO/IEC JTC1 SC22 WG14 N1169 +LIBBUILTIN(mulir, "iir", "f", "stdfix.h", C_LANG) +LIBBUILTIN(mulilr, "LiLiLr", "f", "stdfix.h", C_LANG) +LIBBUILTIN(mulik, "iik", "f", "stdfix.h", C_LANG) +LIBBUILTIN(mulilk, "LiLiLk", "f", "stdfix.h", C_LANG) + +LIBBUILTIN(muliur, "UiUiUr", "f", "stdfix.h", C_LANG) +LIBBUILTIN(muliulr, "ULiULiULr", "f", "stdfix.h", C_LANG) +LIBBUILTIN(muliuk, "UiUiUk", "f", "stdfix.h", C_LANG) +LIBBUILTIN(muliulk, "ULiULiULk", "f", "stdfix.h", C_LANG) + +// Not part of N1169 but requested +LIBBUILTIN(pow10fx, "LkLk", "f", "stdfix.h", C_LANG) +LIBBUILTIN(log2fx, "LkULk", "f", "stdfix.h", C_LANG) +LIBBUILTIN(log10fx, "LkULk", "f", "stdfix.h", C_LANG) + // Annotation function BUILTIN(__builtin_annotation, "v.", "tn") Index: include/clang/Basic/TokenKinds.def =================================================================== --- include/clang/Basic/TokenKinds.def +++ include/clang/Basic/TokenKinds.def @@ -779,6 +779,11 @@ // handles them. ANNOTATION(pragma_fp_contract) +// Annotation for #pragma STDC FX_FULL_PRECISION... +// The lexer produces these so that they only take effect when the parser +// handles them. +ANNOTATION(pragma_fx_full_precision) + // Annotation for #pragma pointers_to_members... // The lexer produces these so that they only take effect when the parser // handles them. Index: include/clang/Parse/Parser.h =================================================================== --- include/clang/Parse/Parser.h +++ include/clang/Parse/Parser.h @@ -188,6 +188,7 @@ std::unique_ptr FPHandler; std::unique_ptr STDCFENVHandler; std::unique_ptr STDCCXLIMITHandler; + std::unique_ptr STDCFXPRECHandler; std::unique_ptr STDCUnknownHandler; std::unique_ptr AttributePragmaHandler; @@ -667,6 +668,10 @@ /// #pragma STDC FP_CONTRACT... void HandlePragmaFPContract(); + /// Handle the annotation token produced for + /// #pragma STDC FX_FULL_PRECISION... + void HandlePragmaFXFullPrecision(); + /// Handle the annotation token produced for /// #pragma clang fp ... void HandlePragmaFP(); Index: lib/AST/ASTContext.cpp =================================================================== --- lib/AST/ASTContext.cpp +++ lib/AST/ASTContext.cpp @@ -9288,6 +9288,20 @@ else Type = Unsigned ? Context.UnsignedIntTy : Context.IntTy; break; + case 'k': + assert(HowLong <= 1 && "_Accum can be at most long"); + if (HowLong == 1) + Type = Unsigned ? Context.UnsignedLongAccumTy : Context.LongAccumTy; + else + Type = Unsigned ? Context.UnsignedAccumTy : Context.AccumTy; + break; + case 'r': + assert(HowLong <= 1 && "_Fract can be at most long"); + if (HowLong == 1) + Type = Unsigned ? Context.UnsignedLongFractTy : Context.LongFractTy; + else + Type = Unsigned ? Context.UnsignedFractTy : Context.FractTy; + break; case 'c': assert(HowLong == 0 && "Bad modifiers used with 'c'!"); if (Signed) Index: lib/CodeGen/CGBuiltin.cpp =================================================================== --- lib/CodeGen/CGBuiltin.cpp +++ lib/CodeGen/CGBuiltin.cpp @@ -1252,6 +1252,114 @@ return Res; } +RValue CodeGenFunction::EmitMuliFX(const CallExpr *E) { + const Expr *LHSExpr = E->getArg(0); + const Expr *RHSExpr = E->getArg(1); + const QualType &LHSTy = LHSExpr->getType(); + const QualType &RHSTy = RHSExpr->getType(); + assert(LHSTy->isIntegralType(getContext())); + assert(RHSTy->isFixedPointType() && !RHSTy->isSaturatedFixedPointType()); + assert(LHSTy->isSignedIntegerType() == RHSTy->isSignedFixedPointType()); + + // Cast up then cast down back to an int + unsigned LHSWidth = getContext().getTypeInfo(LHSTy).Width; + unsigned RHSWidth = getContext().getTypeInfo(RHSTy).Width; + unsigned BufferWidth = LHSWidth + RHSWidth; + llvm::Type *ResultTy = Builder.getIntNTy(BufferWidth); + unsigned Scale = getContext().getFixedPointScale(RHSTy); + bool isSigned = LHSTy->isSignedIntegerType(); + + llvm::Value *LHS = EmitScalarExpr(LHSExpr); + llvm::Value *RHS = EmitScalarExpr(RHSExpr); + + LHS = Builder.CreateIntCast(LHS, ResultTy, isSigned); + RHS = Builder.CreateIntCast(RHS, ResultTy, isSigned); + + llvm::Value *Result = Builder.CreateMul(LHS, RHS); + Result = Builder.CreateAShr(Result, Scale); + + // Special case for multiplying by -1 and -1 with _Fracts + if (RHSTy->isFractFixedPointType() && isSigned) { + // TODO: Not sure if I can just make -1 as second argument since it takes a + // uint64_t. + llvm::APInt LHSOne(BufferWidth, 0, /*isSigned=*/true); + --LHSOne; + + llvm::Value *LHSIsMinusOne = + Builder.CreateICmpEQ(LHS, Builder.getInt(LHSOne), "LHS is -1"); + + // Will need to mask the _Fract to check if -1 + uint64_t Mask = (1ULL << (Scale + 1)) - 1; + uint64_t RHSOne = 1ULL << Scale; + llvm::Value *MaskedRHS = Builder.CreateAnd(RHS, Mask); + llvm::Value *RHSIsMinusOne = Builder.CreateICmpEQ( + MaskedRHS, Builder.getIntN(BufferWidth, RHSOne), "RHS is -1"); + + Result = + Builder.CreateSelect(Builder.CreateAnd(LHSIsMinusOne, RHSIsMinusOne), + Builder.getIntN(BufferWidth, 1), Result); + } + + Result = Builder.CreateIntCast(Result, getTypes().ConvertTypeForMem(LHSTy), + isSigned); + + return RValue::get(Result); +} + +llvm::Value *CodeGenFunction::EmitPow10FXIntrinsic(const CallExpr *E) { + const Expr *Arg = E->getArg(0); + const QualType &ArgTy = Arg->getType(); + const QualType &ReturnTy = E->getCallReturnType(getContext()); + assert(ArgTy->isSignedFixedPointType()); + assert(ReturnTy->isSignedFixedPointType()); + + Value *ArgVal = EmitScalarExpr(Arg); + + // FIXME: Hardcoded function type for __pow10fx in compiler-rt + // The scale is also hardcoded as 31, the default scale of a long _Accum. + std::string FuncName = "__pow10fx"; + llvm::FunctionType *FuncType = + llvm::FunctionType::get(Builder.getInt64Ty(), {Builder.getInt64Ty()}, + /*isVarArg=*/false); + Function *F = + cast(CGM.getModule().getOrInsertFunction(FuncName, FuncType)); + + return Builder.CreateCall(F, {ArgVal}); +} + +llvm::Value *CodeGenFunction::EmitLogNFXIntrinsic(const CallExpr *E, + unsigned base) { + const Expr *Arg = E->getArg(0); + const QualType &ArgTy = Arg->getType(); + const QualType &ReturnTy = E->getCallReturnType(getContext()); + assert(ArgTy->isUnsignedFixedPointType()); + assert(ReturnTy->isSignedFixedPointType()); + + unsigned SrcScale = getContext().getFixedPointScale(ArgTy); + unsigned DstScale = getContext().getFixedPointScale(ReturnTy); + + Value *ArgVal = EmitScalarExpr(Arg); + Value *Precision = ConstantInt::get(Builder.getInt32Ty(), SrcScale); + Value *DstPrecision = ConstantInt::get(Builder.getInt32Ty(), DstScale); + Value *Base = ConstantInt::get(Builder.getInt32Ty(), base); + + // FIXME: Hardcoded function type for __logNScaledInt in compiler-rt + std::string FuncName = "__logNScaledInt"; + llvm::FunctionType *FuncType = llvm::FunctionType::get( + Builder.getInt64Ty(), + {Builder.getInt64Ty(), Builder.getInt32Ty(), Builder.getInt32Ty(), Builder.getInt32Ty()}, + /*isVarArg=*/false); + Function *F = cast(CGM.getModule().getOrInsertFunction(FuncName, FuncType)); + + return Builder.CreateCall(F, {ArgVal, Precision, DstPrecision, Base}); +} + +Address CodeGenFunction::CreateAddr(llvm::Type *Ty) { + Value *ValAlloc = Builder.CreateAlloca(Ty); + unsigned ValAlign = ValAlloc->getPointerAlignment(CGM.getDataLayout()); + return Address(ValAlloc, CharUnits::fromQuantity(ValAlign)); +} + RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD, unsigned BuiltinID, const CallExpr *E, ReturnValueSlot ReturnValue) { @@ -1456,6 +1564,23 @@ switch (BuiltinID) { default: break; + case Builtin::BIpow10fx: + return RValue::get(EmitPow10FXIntrinsic(E)); + case Builtin::BIlog2fx: + return RValue::get(EmitLogNFXIntrinsic(E, 2)); + case Builtin::BIlog10fx: + return RValue::get(EmitLogNFXIntrinsic(E, 10)); + + case Builtin::BImulir: + case Builtin::BImulilr: + case Builtin::BImulik: + case Builtin::BImulilk: + case Builtin::BImuliur: + case Builtin::BImuliulr: + case Builtin::BImuliuk: + case Builtin::BImuliulk: + return EmitMuliFX(E); + case Builtin::BI__builtin___CFStringMakeConstantString: case Builtin::BI__builtin___NSStringMakeConstantString: return RValue::get(ConstantEmitter(*this).emitAbstract(E, E->getType())); Index: lib/CodeGen/CGExprScalar.cpp =================================================================== --- lib/CodeGen/CGExprScalar.cpp +++ lib/CodeGen/CGExprScalar.cpp @@ -1074,31 +1074,23 @@ Value *Res = Src; - // Casting between fixed point types involves separating the integral and - // fractional bits, potentially shifting them, then joining back together. unsigned dest_fbits = CGF.getContext().getFixedPointScale(DstTy); unsigned src_fbits = CGF.getContext().getFixedPointScale(SrcTy); - unsigned dest_ibits = CGF.getContext().getFixedPointIBits(DstTy); - unsigned src_ibits = CGF.getContext().getFixedPointIBits(SrcTy); + unsigned dest_width = CGF.getContext().getTypeSize(DstTy); + unsigned src_width = CGF.getContext().getTypeSize(SrcTy); + bool src_signed = SrcTy->isSignedFixedPointType(); - // If the number of integral bits is decreasing, trim off any extra bits while - // retaining the sign. - if (dest_ibits < src_ibits) { - Res = Builder.CreateShl(Res, src_ibits - dest_ibits); - Res = Builder.CreateAShr(Res, src_ibits - dest_ibits); - } - - // Move the radix. For irrational numbers, there will be loss of precision - // using this method when the number of fbits increases since we will be right - // padding zeros. Precision can still be retained if we temporarily convert to - // a float and perform some floating point arithmetic, though this may cost - // more. Enable that if #pragma FX_FULL_PRECISION is provided. if (dest_fbits > src_fbits) { + llvm::Type *NewTy = Builder.getIntNTy(src_width + dest_fbits - src_fbits); + Res = Builder.CreateIntCast(Res, NewTy, src_signed); Res = Builder.CreateShl(Res, dest_fbits - src_fbits); - } else if (dest_fbits < src_fbits) { - Res = Builder.CreateAShr(Res, src_fbits - dest_fbits); + } else { + Res = src_signed ? Builder.CreateAShr(Res, src_fbits - dest_fbits) + : Builder.CreateLShr(Res, src_fbits - dest_fbits); } - return Res; + + llvm::Type *NewTy = Builder.getIntNTy(dest_width); + return Builder.CreateIntCast(Res, NewTy, src_signed); } /// Emit a conversion from the specified type to the specified destination type, @@ -1155,11 +1147,11 @@ } } - bool WorkingOnFixedPoints = - DstType->isFixedPointType() && SrcType->isFixedPointType(); + if (DstType->isFixedPointType() && SrcType->isFixedPointType()) + return EmitFixedPointRadixShift(Src, SrcType, DstType); - // Ignore conversions like int -> uint, unless they're fixed point types. - if (SrcTy == DstTy && !WorkingOnFixedPoints) + // Ignore conversions like int -> uint + if (SrcTy == DstTy) return Src; // Handle pointer conversions next: pointers can only be converted to/from @@ -1263,18 +1255,9 @@ DstTy = CGF.FloatTy; } - int order = WorkingOnFixedPoints - ? CGF.getContext().getFixedPointTypeOrder(DstType, SrcType) - : 0; - - if (WorkingOnFixedPoints && order < 0) { - // Casting down, so we will need to shift early as to not lose data - Src = EmitFixedPointRadixShift(Src, SrcType, DstType); - } - if (isa(SrcTy)) { bool InputSigned = (SrcType->isSignedIntegerOrEnumerationType() || - SrcType->isFixedPointType()); + SrcType->isSignedFixedPointType()); if (SrcType->isBooleanType() && TreatBooleanAsSigned) { InputSigned = true; } @@ -1299,11 +1282,6 @@ Res = Builder.CreateFPExt(Src, DstTy, "conv"); } - if (WorkingOnFixedPoints && order >= 0) { - // Casting up (or same type), so we can safely shift without losing data - Res = EmitFixedPointRadixShift(Res, SrcType, DstType); - } - if (DstTy != ResTy) { if (CGF.getContext().getTargetInfo().useFP16ConversionIntrinsics()) { assert(ResTy->isIntegerTy(16) && "Only half FP requires extra conversion"); @@ -2068,7 +2046,8 @@ Val = EmitScalarConversion(Val, E->getType(), DestTy, CE->getExprLoc()); return Builder.CreateFDiv( - Val, llvm::ConstantFP::get(CGF.CGM.FloatTy, (1ULL << scale) * 1.0)); + Val, llvm::ConstantFP::get(CGF.ConvertTypeForMem(DestTy), + (1ULL << scale) * 1.0)); } case CK_IntegralCast: Index: lib/CodeGen/CodeGenFunction.h =================================================================== --- lib/CodeGen/CodeGenFunction.h +++ lib/CodeGen/CodeGenFunction.h @@ -3648,6 +3648,11 @@ RValue EmitNVPTXDevicePrintfCallExpr(const CallExpr *E, ReturnValueSlot ReturnValue); + Address CreateAddr(llvm::Type *Ty); + RValue EmitMuliFX(const CallExpr *E); + llvm::Value *EmitLogNFXIntrinsic(const CallExpr *E, unsigned base); + llvm::Value *EmitPow10FXIntrinsic(const CallExpr *E); + RValue EmitBuiltinExpr(const FunctionDecl *FD, unsigned BuiltinID, const CallExpr *E, ReturnValueSlot ReturnValue); Index: lib/Parse/ParsePragma.cpp =================================================================== --- lib/Parse/ParsePragma.cpp +++ lib/Parse/ParsePragma.cpp @@ -122,6 +122,14 @@ } }; +/// PragmaSTDC_FX_FULL_PRECISIONHandler - "\#pragma STDC FX_FULL_PRECISION ...". +struct PragmaSTDC_FX_FULL_PRECISIONHandler : public PragmaHandler { + PragmaSTDC_FX_FULL_PRECISIONHandler() : PragmaHandler("FX_FULL_PRECISION") {} + + void HandlePragma(Preprocessor &PP, PragmaIntroducerKind Introducer, + Token &Tok) override; +}; + /// PragmaSTDC_UnknownHandler - "\#pragma STDC ...". struct PragmaSTDC_UnknownHandler : public PragmaHandler { PragmaSTDC_UnknownHandler() = default; @@ -283,6 +291,9 @@ STDCCXLIMITHandler.reset(new PragmaSTDC_CX_LIMITED_RANGEHandler()); PP.AddPragmaHandler("STDC", STDCCXLIMITHandler.get()); + STDCFXPRECHandler.reset(new PragmaSTDC_FX_FULL_PRECISIONHandler()); + PP.AddPragmaHandler("STDC", STDCFXPRECHandler.get()); + STDCUnknownHandler.reset(new PragmaSTDC_UnknownHandler()); PP.AddPragmaHandler("STDC", STDCUnknownHandler.get()); @@ -436,6 +447,9 @@ PP.RemovePragmaHandler("STDC", STDCCXLIMITHandler.get()); STDCCXLIMITHandler.reset(); + PP.RemovePragmaHandler("STDC", STDCFXPRECHandler.get()); + STDCFXPRECHandler.reset(); + PP.RemovePragmaHandler("STDC", STDCUnknownHandler.get()); STDCUnknownHandler.reset(); @@ -591,6 +605,12 @@ ConsumeAnnotationToken(); } +// TODO: Follow logic similar to FPContract for handling +void Parser::HandlePragmaFXFullPrecision() { + assert(Tok.is(tok::annot_pragma_fx_full_precision)); + ConsumeAnnotationToken(); +} + StmtResult Parser::HandlePragmaCaptured() { assert(Tok.is(tok::annot_pragma_captured)); @@ -2033,6 +2053,22 @@ PP.EnterTokenStream(Toks, /*DisableMacroExpansion=*/true); } +void PragmaSTDC_FX_FULL_PRECISIONHandler::HandlePragma( + Preprocessor &PP, PragmaIntroducerKind Introducer, Token &Tok) { + tok::OnOffSwitch OOS; + if (PP.LexOnOffSwitch(OOS)) return; + + MutableArrayRef Toks(PP.getPreprocessorAllocator().Allocate(1), + 1); + Toks[0].startToken(); + Toks[0].setKind(tok::annot_pragma_fx_full_precision); + Toks[0].setLocation(Tok.getLocation()); + Toks[0].setAnnotationEndLoc(Tok.getLocation()); + Toks[0].setAnnotationValue( + reinterpret_cast(static_cast(OOS))); + PP.EnterTokenStream(Toks, /*DisableMacroExpansion=*/true); +} + void PragmaOpenCLExtensionHandler::HandlePragma(Preprocessor &PP, PragmaIntroducerKind Introducer, Index: lib/Parse/ParseStmt.cpp =================================================================== --- lib/Parse/ParseStmt.cpp +++ lib/Parse/ParseStmt.cpp @@ -337,6 +337,7 @@ return StmtEmpty(); case tok::annot_pragma_fp_contract: + case tok::annot_pragma_fx_full_precision: ProhibitAttributes(Attrs); Diag(Tok, diag::err_pragma_fp_contract_scope); ConsumeAnnotationToken(); @@ -899,6 +900,9 @@ case tok::annot_pragma_fp_contract: HandlePragmaFPContract(); break; + case tok::annot_pragma_fx_full_precision: + HandlePragmaFXFullPrecision(); + break; case tok::annot_pragma_fp: HandlePragmaFP(); break; Index: lib/Parse/Parser.cpp =================================================================== --- lib/Parse/Parser.cpp +++ lib/Parse/Parser.cpp @@ -674,6 +674,9 @@ case tok::annot_pragma_fp_contract: HandlePragmaFPContract(); return nullptr; + case tok::annot_pragma_fx_full_precision: + HandlePragmaFXFullPrecision(); + return nullptr; case tok::annot_pragma_fp: HandlePragmaFP(); break; Index: test/Frontend/fixed_point_all_conversions.c =================================================================== --- test/Frontend/fixed_point_all_conversions.c +++ test/Frontend/fixed_point_all_conversions.c @@ -72,3 +72,15 @@ CONVERT_FIXED_POINT_TYPE(short _Accum, ShortAccum); CONVERT_FIXED_POINT_TYPE(_Accum, Accum); CONVERT_FIXED_POINT_TYPE(long _Accum, LongAccum); + +#define assert(b) \ + if (!(b)) { \ + return 1; \ + } + +int main() { + unsigned _Fract f = 0.75ur; + long _Accum a = 0.75lk; + assert(UnsignedFractType_to_LongAccum(f) == a); + return 0; +} Index: test/Frontend/fixed_point_mulifx_validation.c =================================================================== --- /dev/null +++ test/Frontend/fixed_point_mulifx_validation.c @@ -0,0 +1,332 @@ +// RUN: %clang -ffixed-point --target=x86_64-linux -S -emit-llvm -o - %s | lli -force-interpreter=true + +// Check the results of various builtin functions provided under +// These tests assume the defualt precision values are used and assumes an int +// is 32 bits, so target an architecture that supports this. + +#define assert(b) if (!(b)) { return 1; } + +// TODO: Remove these after implementing the macros for minimum fixed point +// values. +#define FRACT_MIN (-0.5r - 0.5r) +#define LFRACT_MIN (-0.5lr - 0.5lr) +#define ACCUM_MIN (-32768.0k - 32768.0k) +#define LACCUM_MIN (-2147483648.0lk - 2147483648.0lk) + +int main() { + /*********** mulir() ************/ + + // Zero + assert(mulir(0, 1.0r) == 0); + assert(mulir(1, 0.0r) == 0); + + // One + assert(mulir(1, 1.0r) == 0); + assert(mulir(-1, 1.0r) == -1); + assert(mulir(1, FRACT_MIN) == -1); + assert(mulir(-1, FRACT_MIN) == 1); + + // Fractional resolves to int + assert(mulir(10, 0.5r) == 5); + assert(mulir(10, -0.5r) == -5); + assert(mulir(-10, 0.5r) == -5); + assert(mulir(-10, -0.5r) == 5); + + // Fractional result is missing and value truncates towards zero by default + assert(mulir(15, 0.5r) == 7); + assert(mulir(15, -0.5r) == -8); + assert(mulir(-15, 0.5r) == -8); + assert(mulir(-15, -0.5r) == 7); + + // Limited resulution of the _Fract fractional bits does not always allow for + // the most precise values. 1.0r resolves to _Fract max. + assert(mulir(65536, 1.0r) == 65534); + assert(mulir(-65536, 1.0r) == -65534); + assert(mulir(65536, FRACT_MIN) == -65536); + assert(mulir(-65536, FRACT_MIN) == 65536); + + assert(mulir(2147483647, 1.0r) == 2147418111); + assert(mulir(-2147483647, 1.0r) == -2147418112); // Rounds down + assert(mulir(2147483647, FRACT_MIN) == -2147483647); + assert(mulir(-2147483647, FRACT_MIN) == 2147483647); + + /*********** mulilr() ************/ + + // Zero + assert(mulilr(0, 1.0lr) == 0); + assert(mulilr(1, 0.0lr) == 0); + + // One + assert(mulilr(1, 1.0lr) == 0); + assert(mulilr(-1, 1.0lr) == -1); + assert(mulilr(1, LFRACT_MIN) == -1); + assert(mulilr(-1, LFRACT_MIN) == 1); + + // Fractional resolves to int + assert(mulilr(10, 0.5lr) == 5); + assert(mulilr(10, -0.5lr) == -5); + assert(mulilr(-10, 0.5lr) == -5); + assert(mulilr(-10, -0.5lr) == 5); + + // Fractional result is missing and value truncates towards zero + assert(mulilr(15, 0.5lr) == 7); + assert(mulilr(15, -0.5lr) == -8); + assert(mulilr(-15, 0.5lr) == -8); + assert(mulilr(-15, -0.5lr) == 7); + + // Limited resulution of the _Fract fractional bits does not always allow for + // the most precise values. + assert(mulilr(4294967296, 1.0lr) == 4294967294); + assert(mulilr(-4294967296, 1.0lr) == -4294967294); + assert(mulilr(4294967296, LFRACT_MIN) == -4294967296); + assert(mulilr(-4294967296, LFRACT_MIN) == 4294967296); + + assert(mulilr(9223372036854775807, 1.0lr) == 9223372032559808511); + assert(mulilr(-9223372036854775807, 1.0lr) == -9223372032559808512); // Rounds down + assert(mulilr(9223372036854775807, LFRACT_MIN) == -9223372036854775807); + assert(mulilr(-9223372036854775807, LFRACT_MIN) == 9223372036854775807); + + /*********** mulik() ************/ + + // Zero + assert(mulik(0, 1.0k) == 0); + assert(mulik(1, 0.0k) == 0); + + // One + assert(mulik(1, 1.0k) == 1); + assert(mulik(-1, 1.0k) == -1); + assert(mulik(1, -1.0k) == -1); + assert(mulik(-1, -1.0k) == 1); + + // No fractional + assert(mulik(3, 4.0k) == 12); + assert(mulik(-3, 4.0k) == -12); + assert(mulik(3, -4.0k) == -12); + assert(mulik(-3, -4.0k) == 12); + + // Fractional resolves to int + assert(mulik(2, 2.5k) == 5); + assert(mulik(2, -2.5k) == -5); + assert(mulik(-2, 2.5k) == -5); + assert(mulik(-2, -2.5k) == 5); + + assert(mulik(10, 0.5k) == 5); + assert(mulik(10, -0.5k) == -5); + assert(mulik(-10, 0.5k) == -5); + assert(mulik(-10, -0.5k) == 5); + + // Fractional result is missing and value truncates towards zero + assert(mulik(3, 2.5k) == 7); + assert(mulik(3, -2.5k) == -8); + assert(mulik(-3, 2.5k) == -8); + assert(mulik(-3, -2.5k) == 7); + + assert(mulik(15, 0.5k) == 7); + assert(mulik(15, -0.5k) == -8); + assert(mulik(-15, 0.5k) == -8); + assert(mulik(-15, -0.5k) == 7); + + // Limited resulution of the _Accum fractional bits does not always allow for + // the most precise values. + // 1 * _Accum limits + assert(mulik(1, 65535.99999999999k) == 65535); + assert(mulik(-1, 65535.99999999999k) == -65536); + assert(mulik(1, ACCUM_MIN) == -65536); + assert(mulik(-1, ACCUM_MIN) == 65536); + + // _Accum limits as integer * near 1 _Accum (evenly divisibly) + assert(mulik(65536, 0.99999999999k) == 65534); + assert(mulik(-65536, 0.99999999999k) == -65534); + assert(mulik(65536, -0.99999999999k) == -65534); + assert(mulik(-65536, -0.99999999999k) == 65534); + + // int max * near 1 _Accum + assert(mulik(2147483647, 0.99999999999k) == 2147418111); + assert(mulik(-2147483647, 0.99999999999k) == -2147418112); + assert(mulik(2147483647, -0.99999999999k) == -2147418112); + assert(mulik(-2147483647, -0.99999999999k) == 2147418111); + + // int max * 1 _Accum (exact values) + assert(mulik(2147483647, 1.0k) == 2147483647); + assert(mulik(-2147483647, 1.0k) == -2147483647); + assert(mulik(2147483647, -1.0k) == -2147483647); + assert(mulik(2147483647, 1.0k) == 2147483647); + + // int min * near 1 _Accum + assert(mulik(-2147483648, 0.99999999999k) == -2147418112); + assert(mulik(-2147483648, -0.99999999999k) == 2147418112); + + /*********** mulilk() ************/ + + // Zero + assert(mulilk(0, 1.0lk) == 0); + assert(mulilk(1, 0.0lk) == 0); + + // One + assert(mulilk(1, 1.0lk) == 1); + assert(mulilk(-1, 1.0lk) == -1); + assert(mulilk(1, -1.0lk) == -1); + assert(mulilk(-1, -1.0lk) == 1); + + // Fractional resolves to int + assert(mulilk(2, 2.5lk) == 5); + assert(mulilk(2, -2.5lk) == -5); + assert(mulilk(-2, 2.5lk) == -5); + assert(mulilk(-2, -2.5lk) == 5); + + assert(mulilk(10, 0.5lk) == 5); + assert(mulilk(10, -0.5lk) == -5); + assert(mulilk(-10, 0.5lk) == -5); + assert(mulilk(-10, -0.5lk) == 5); + + // Fractional result is missing and value truncates towards zero + assert(mulilk(3, 2.5lk) == 7); + assert(mulilk(3, -2.5lk) == -8); + assert(mulilk(-3, 2.5lk) == -8); + assert(mulilk(-3, -2.5lk) == 7); + + assert(mulilk(15, 0.5lk) == 7); + assert(mulilk(15, -0.5lk) == -8); + assert(mulilk(-15, 0.5lk) == -8); + assert(mulilk(-15, -0.5lk) == 7); + + // Limited resulution of the _Accum fractional bits does not always allow for + // the most precise values. + // 1 * long _Accum limits + assert(mulilk(1, 4294967295.99999999999lk) == 4294967295); + assert(mulilk(-1, 4294967295.99999999999lk) == -4294967296); + assert(mulilk(1, LACCUM_MIN) == -4294967296); + assert(mulilk(-1, LACCUM_MIN) == 4294967296); + + // long _Accum limits as integer * near 1 long _Accum (evenly divisibly) + assert(mulilk(4294967296L, 0.99999999999lk) == 4294967294); + assert(mulilk(-4294967296L, 0.99999999999lk) == -4294967294); + assert(mulilk(4294967296L, -0.99999999999lk) == -4294967294); + assert(mulilk(-4294967296L, -0.99999999999lk) == 4294967294); + + // long int max * near 1 long _Accum + assert(mulilk(9223372036854775807L, 0.99999999999999999999lk) == 9223372032559808511L); + assert(mulilk(-9223372036854775807L, 0.99999999999999999999lk) == -9223372032559808512L); + assert(mulilk(9223372036854775807L, -0.99999999999999999999lk) == -9223372032559808512L); + assert(mulilk(-9223372036854775807L, -0.99999999999999999999lk) == 9223372032559808511L); + + // long int max * 1 long _Accum (exact values) + assert(mulilk(9223372036854775807L, 1.0k) == 9223372036854775807L); + assert(mulilk(-9223372036854775807L, 1.0k) == -9223372036854775807L); + assert(mulilk(9223372036854775807L, -1.0k) == -9223372036854775807L); + assert(mulilk(-9223372036854775807L, -1.0k) == 9223372036854775807L); + + // long int min * near 1 long _Accum + assert(mulilk(-9223372036854775808L, 0.99999999999lk) == -9223372032559808512L); + assert(mulilk(-9223372036854775808L, -0.99999999999lk) == 9223372032559808512L); + + /*********** muliur() ************/ + + // Zero + assert(muliur(0, 1.0ur) == 0); + assert(muliur(1, 0.0ur) == 0); + + // One + assert(muliur(1, 1.0ur) == 0); + + // Fractional resolves to int + assert(muliur(10, 0.5ur) == 5); + + // Fractional result is missing and value truncates towards zero by default + assert(muliur(15, 0.5ur) == 7); + + // Limited resulution of the _Fract fractional bits does not always allow for + // the most precise values. 1.0ur resolves to _Fract max. + assert(muliur(65536, 1.0ur) == 65535); + assert(muliur(4294967295, 1.0ur) == 4294901759); + + /*********** muliulr() ************/ + + // Zero + assert(muliulr(0, 1.0ulr) == 0); + assert(muliulr(1, 0.0ulr) == 0); + + // One + assert(muliulr(1, 1.0ulr) == 0); + + // Fractional resolves to int + assert(muliulr(10, 0.5ulr) == 5); + + // Fractional result is missing and value truncates towards zero by default + assert(muliulr(15, 0.5ulr) == 7); + + // Limited resulution of the _Fract fractional bits does not always allow for + // the most precise values. 1.0ur resolves to _Fract max. + assert(muliulr(4294967296, 1.0ulr) == 4294967295); + assert(muliulr(9223372036854775807, 1.0ulr) == 9223372034707292159); + + /*********** muliuk() ************/ + + // Zero + assert(muliuk(0, 1.0uk) == 0); + assert(muliuk(1, 0.0uk) == 0); + + // One + assert(muliuk(1, 1.0uk) == 1); + + // No fractional + assert(muliuk(3, 4.0uk) == 12); + + // Fractional resolves to int + assert(muliuk(2, 2.5uk) == 5); + assert(muliuk(10, 0.5uk) == 5); + + // Fractional result is missing and value truncates towards zero + assert(muliuk(3, 2.5uk) == 7); + assert(muliuk(15, 0.5uk) == 7); + + // Limited resulution of the _Accum fractional bits does not always allow for + // the most precise values. + // 1 * unsigned _Accum limits + assert(muliuk(1, 65535.99999999999uk) == 65535); + + // unsigned _Accum limits as integer * near 1 unsigned _Accum (evenly divisibly) + assert(muliuk(65536, 0.99999999999uk) == 65535); + + // unsigned int max * near 1 unsigned _Accum + assert(muliuk(4294967295, 0.99999999999uk) == 4294901759); + + // unsigned int max * 1 unsigned _Accum (exact values) + assert(muliuk(4294967295, 1.0uk) == 4294967295); + + /*********** muliulk() ************/ + + // Zero + assert(muliulk(0, 1.0ulk) == 0); + assert(muliulk(1, 0.0ulk) == 0); + + // One + assert(muliulk(1, 1.0ulk) == 1); + + // No fractional + assert(muliulk(3, 4.0ulk) == 12); + + // Fractional resolves to int + assert(muliulk(2, 2.5ulk) == 5); + assert(muliulk(10, 0.5ulk) == 5); + + // Fractional result is missing and value truncates towards zero + assert(muliulk(3, 2.5ulk) == 7); + assert(muliulk(15, 0.5ulk) == 7); + + // Limited resulution of the _Accum fractional bits does not always allow for + // the most precise values. + // 1 * unsigned long _Accum limits + assert(muliulk(1, 4294967295.99999999999ulk) == 4294967295); + + // unsigned long _Accum limits as integer * near 1 long _Accum (evenly divisibly) + assert(muliulk(4294967295, 0.99999999999ulk) == 4294967294); + + // unsigned long int max * near 1 unsigned long _Accum + assert(muliulk(18446744073709551615, 0.99999999999ulk) == 18446744069414584319); + + // unsigned long int max * 1 unsigned long _Accum (exact values) + assert(muliulk(18446744073709551615, 1.0ulk) == 18446744073709551615); + + return 0; +}