Blueprint does not have "double" type?

#define float double
:stuck_out_tongue: I’m sure it will just work. :smiley:

For real time hard simulation, not having support for double is very disappointing.

1 Like

any news on doubles? Can yall just write the new blueprints to mirror the latest version of cpp as far as variable types? they could be a way to have ultra precision or loosely truncated, but faster calculations.

but i guess it would only be great if there was a way to do it without having to rework current game-code.

I have been having a lot of issues working with the standard floats so I took a deeper dive in the engine after looking at these 2 files above and added a few functions that made it possible, or rather, easier to do higher level math with some amount of accuracy.
If I add more of the standard functions I’ll end up updating this, but I figured like I found this someone else will probably end up bashing his head on it. Might as well share and save you some time.

Udated for Trigonometry functions. Uses the original math library so there is still float conversion occrring for these, however I like it better then having the float to string conversion appearing in blueprint - and the results actually seem more accurate this far.

UE4KitMathBPLibrary.h

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "Engine.h"
#include "UnrealMathUtility.h"
#include "UE4KitMathBPLibrary.generated.h"

/*
*    Function library class.
*    Each function in it is expected to be static and represents blueprint node that can be called in any blueprint.
*
*    When declaring function you can define metadata for the node. Key function specifiers will be BlueprintPure and BlueprintCallable.
*    BlueprintPure - means the function does not affect the owning object in any way and thus creates a node without Exec pins.
*    BlueprintCallable - makes a function which can be executed in Blueprints - Thus it has Exec pins.
*    DisplayName - full name of the node, shown when you mouse over the node and in the blueprint drop down menu.
*                Its lets you name the node using characters not allowed in C++ function names.
*    CompactNodeTitle - the word(s) that appear on the node.
*    Keywords -    the list of keywords that helps you to find node when you search for it using Blueprint drop-down menu.
*                Good example is "Print String" node which you can find also by using keyword "log".
*    Category -    the category your node will be under in the Blueprint drop-down menu.
*
*    For more info on custom blueprint nodes visit documentation:
*    https://wiki.unrealengine.com/Custom_Blueprint_Node_Creation
*/
UCLASS()
class UUE4KitMathBPLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_UCLASS_BODY()

private:

	static FString DoubleToString(double InDouble);

public:

	/* Double Addition (A + B) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "double + double", CompactNodeTitle = "+", Keywords = "double + add plus", CommutativeAssociativeBinaryOperator = "true"), Category = "UE4Kit|Math|Double")
		static FString Add_DoubleDouble(const FString& A = "1.0", const FString& B = "1.0");

	/* Double Subtraction (A - B) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "double - double", CompactNodeTitle = "-", Keywords = "double - subtract minus"), Category = "UE4Kit|Math|Double")
		static FString Subtract_DoubleDouble(const FString& A = "1.0", const FString& B = "1.0");

	/* Double Multiplication (A * B) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Double * Double", CompactNodeTitle = "*", Keywords = "double * multiply", CommutativeAssociativeBinaryOperator = "true"), Category = "UE4Kit|Math|Double")
		static FString Multiply_DoubleDouble(const FString& A = "1.0", const FString& B = "1.0");

	/* Double Division (A / B) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Double / Double", CompactNodeTitle = "/", Keywords = "double / divide division"), Category = "UE4Kit|Math|Double")
		static FString Divide_DoubleDouble(const FString& A = "1.0", const FString& B = "1.0");
	
	/* Double Range (A / B) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "InRange (Double)", Min = "0.0", Max = "1.0"), Category = "UE4Kit|Math|Double")
		static bool InRange_DoubleDouble(const FString& Value = "1.0", const FString& Min = "0", const FString& Max = "1", bool InclusiveMin = true, bool InclusiveMax = true);

	/** Returns true if A is Less than B (A < B) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Double < Double", CompactNodeTitle = "<", Keywords = "< less"), Category = "Math|Double")
		static bool Less_DoubleDouble(const FString& A = "0", const FString& B = "1.0");

	/** Returns true if A is greater than B (A > B) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Double > Double", CompactNodeTitle = ">", Keywords = "> greater"), Category = "Math|Double")
		static bool Greater_DoubleDouble(const FString& A = "1.0", const FString& B = "0");

	/** Returns true if A is Less than or equal to B (A <= B) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Double <= Double", CompactNodeTitle = "<=", Keywords = "<= less"), Category = "Math|Double")
		static bool LessEqual_DoubleDouble(const FString& A = "0", const FString& B = "1.0");

	/** Returns true if A is greater than or equal to B (A >= B) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Double >= Double", CompactNodeTitle = ">=", Keywords = ">= greater"), Category = "Math|Double")
		static bool GreaterEqual_DoubleDouble(const FString& A = "1.0", const FString& B = "0");

	/** Returns true if A is exactly equal to B (A == B)*/
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Equal (Double)", CompactNodeTitle = "==", Keywords = "== equal"), Category = "Math|Double")
		static bool EqualEqual_DoubleDouble(const FString& A = "1.0", const FString& B = "1.0");

	/** Returns true if A is nearly equal to B (|A - B| < ErrorTolerance) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Nearly Equal (Double)", Keywords = "== equal"), Category = "Math|Double")
		static bool NearlyEqual_DoubleDouble(const FString& A = "1.0", const FString& B = "1.0", const FString& ErrorTolerance = ".001");

	/** Returns true if A does not equal B (A != B)*/
	UFUNCTION(BlueprintPure, meta = (DisplayName = "NotEqual (Double)", CompactNodeTitle = "!=", Keywords = "!= not equal"), Category = "Math|Double")
		static bool NotEqual_DoubleDouble(const FString& A = "1.0", const FString& B = "0");




	/** Returns the sin of A (expects Degrees)*/
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Sin (Degrees) Double", CompactNodeTitle = "SINd", Keywords = "sine"), Category = "Math|Trig|Double")
		static FString DegSin_DoubleDouble(FString A);

	/** Returns the inverse sin (arcsin) of A (result is in Degrees) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Asin (Degrees) Double", CompactNodeTitle = "ASINd", Keywords = "sine"), Category = "Math|Trig|Double")
		static FString DegAsin_DoubleDouble(FString A);

	/** Returns the cos of A (expects Degrees)*/
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Cos (Degrees) Double", CompactNodeTitle = "COSd"), Category = "Math|Trig|Double")
		static FString DegCos_DoubleDouble(FString A);

	/** Returns the inverse cos (arccos) of A (result is in Degrees) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Acos (Degrees) Double", CompactNodeTitle = "ACOSd"), Category = "Math|Trig|Double")
		static FString DegAcos_DoubleDouble(FString A);

	/** Returns the tan of A (expects Degrees)*/
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Tan (Degrees) Double", CompactNodeTitle = "TANd"), Category = "Math|Trig|Double")
		static FString DegTan_DoubleDouble(FString A);

	/** Returns the inverse tan (atan) (result is in Degrees)*/
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Atan (Degrees) Double"), Category = "Math|Trig|Double")
		static FString DegAtan_DoubleDouble(FString A);

	/** Returns the inverse tan (atan2) of A/B (result is in Degrees)*/
	UFUNCTION(BlueprintPure, meta = (DisplayName = "Atan2 (Degrees) Double"), Category = "Math|Trig|Double")
		static FString DegAtan2_DoubleDouble(FString A, FString B);


};

UE4KitMathBPLibrary.cpp

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#include "UE4KitMathBPLibrary.h"

UUE4KitMathBPLibrary::UUE4KitMathBPLibrary(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{

}

FString UUE4KitMathBPLibrary::DoubleToString(double InDouble)
{
	// Avoids negative zero
	if (InDouble == 0)
	{
		InDouble = 0;
	}

	FString TempString;
	// First create the string
	TempString = FString::Printf(TEXT("%.8lf"), InDouble);
	const TArray< TCHAR >& Chars = TempString.GetCharArray();
	const TCHAR Zero = '0';
	const TCHAR Period = '.';
	int32 TrimIndex = 0;
	// Find the first non-zero char in the array
	for (int32 Index = Chars.Num() - 2; Index >= 2; --Index)
	{
		const TCHAR EachChar = Chars[Index];
		const TCHAR NextChar = Chars[Index - 1];
		if ((EachChar != Zero) || (NextChar == Period))
		{
			TrimIndex = Index;
			break;
		}
	}
	// If we changed something trim the string
	if (TrimIndex != 0)
	{
		TempString = TempString.Left(TrimIndex + 1);
	}
	return TempString;
}

FString UUE4KitMathBPLibrary::Add_DoubleDouble(const FString& A, const FString& B)
{
	double A_double = FCString::Atod(*A);
	double B_double = FCString::Atod(*B);
	double C_double = A_double + B_double;
	return     UUE4KitMathBPLibrary::DoubleToString(C_double);
}

FString UUE4KitMathBPLibrary::Subtract_DoubleDouble(const FString& A, const FString& B)
{
	double A_double = FCString::Atod(*A);
	double B_double = FCString::Atod(*B);
	double C_double = A_double - B_double;
	return     UUE4KitMathBPLibrary::DoubleToString(C_double);
}

FString UUE4KitMathBPLibrary::Multiply_DoubleDouble(const FString& A, const FString& B)
{
	double A_double = FCString::Atod(*A);
	double B_double = FCString::Atod(*B);
	double C_double = A_double * B_double;
	return     UUE4KitMathBPLibrary::DoubleToString(C_double);
}

FString UUE4KitMathBPLibrary::Divide_DoubleDouble(const FString& A, const FString& B)
{
	double A_double = FCString::Atod(*A);
	double B_double = FCString::Atod(*B);
	double C_double = A_double / B_double;
	return     UUE4KitMathBPLibrary::DoubleToString(C_double);
}

bool UUE4KitMathBPLibrary::InRange_DoubleDouble(const FString& Value, const FString& Min, const FString& Max, bool InclusiveMin, bool InclusiveMax)
{
	double value = FCString::Atod(*Value);
	double min = FCString::Atod(*Min);
	double max = FCString::Atod(*Max);
	return ((InclusiveMin ? (value >= min) : (value > min)) && (InclusiveMax ? (value <= max) : (value < max)));
}

bool UUE4KitMathBPLibrary::Less_DoubleDouble(const FString& A, const FString& B)
{
	return A < B;
}

bool UUE4KitMathBPLibrary::Greater_DoubleDouble(const FString& A, const FString& B)
{
	return A > B;
}

bool UUE4KitMathBPLibrary::LessEqual_DoubleDouble(const FString& A, const FString& B)
{
	return A <= B;
}

bool UUE4KitMathBPLibrary::GreaterEqual_DoubleDouble(const FString& A, const FString& B)
{
	return A >= B;
}

bool UUE4KitMathBPLibrary::EqualEqual_DoubleDouble(const FString& A, const FString& B)
{
	return A == B;
}

bool UUE4KitMathBPLibrary::NearlyEqual_DoubleDouble(const FString& A, const FString& B, const FString& ErrorTolerance)
{
	double a = FCString::Atod(*A);
	double b = FCString::Atod(*B);
	double ET = FCString::Atod(*ErrorTolerance);
	return abs(a - b) <= ET;
}

bool UUE4KitMathBPLibrary::NotEqual_DoubleDouble(const FString& A, const FString& B)
{
	return A != B;
}





FString UUE4KitMathBPLibrary::DegSin_DoubleDouble(FString A)
{
	double a = FCString::Atod(*A);
	float b = FMath::Sin(PI / (180.f) * a);
	a = b;
	return UUE4KitMathBPLibrary::DoubleToString(a);
}

FString UUE4KitMathBPLibrary::DegAsin_DoubleDouble(FString A)
{
	double a = FCString::Atod(*A);
	float b = (180.f) / PI * FMath::Asin(a);
	a = b;
	return UUE4KitMathBPLibrary::DoubleToString(a);
}

FString UUE4KitMathBPLibrary::DegCos_DoubleDouble(FString A)
{
	double a = FCString::Atod(*A);
	float b = FMath::Cos(PI / (180.f) * a);
	a = b;
	return UUE4KitMathBPLibrary::DoubleToString(a);
}

FString UUE4KitMathBPLibrary::DegAcos_DoubleDouble(FString A)
{
	double a = FCString::Atod(*A);
	float b = (180.f) / PI * FMath::Acos(a);
	a = b;
	return UUE4KitMathBPLibrary::DoubleToString(a);
}

FString UUE4KitMathBPLibrary::DegTan_DoubleDouble(FString A)
{
	double a = FCString::Atod(*A);
	float b = FMath::Tan(PI / (180.f) * a);
	a = b;
	return UUE4KitMathBPLibrary::DoubleToString(a);
}

FString UUE4KitMathBPLibrary::DegAtan_DoubleDouble(FString A)
{
	double a = FCString::Atod(*A);
	float b = (180.f) / PI * FMath::Atan(a);
	a = b;
	return UUE4KitMathBPLibrary::DoubleToString(a);
}

FString UUE4KitMathBPLibrary::DegAtan2_DoubleDouble(FString A, FString B)
{
	double a = FCString::Atod(*A);
	double b = FCString::Atod(*B);
	float c = (180.f) / PI * FMath::Atan2(a, b);
	a = c;
	return UUE4KitMathBPLibrary::DoubleToString(a);
}

Bump for doubles. I’m doing space maths and floats are becoming a big issue with my accuracy. Mostly because I’m trying to mimic a scale model as accurately as possible and doubles just make that a lot easier.

Update from a space maths-er in 2022; UE 5.0.3 seems to have enabled double-precision for all floats used in blueprints, which is great! I have large numbers everywhere and planetary-scale math happening.

However there is still a BP-UI limitation on how small you can enter in a float literal: 10^-6. If you try to input anything smaller than 0.000001 the UI changes it to 0.0. This kinda stinks because the only way to construct small doubles for use in blueprints is to divide larger numbers by some constant at some point at runtime, so you can’t use them in default values—for example, in structs.

I’m getting around this by:

  • Creating a struct representing simulation constants
  • Having the small constants be “documented” as being in units of, for example, 10^-12
  • Setting their defaults to a value 10^12 times greater than desired
  • Giving my game state this struct as a private variable
  • In the game state constructor, for each small constant:
    • Reading the unscaled default from the just-initialized struct
    • Updating the struct with a scaled value (ex, dividing them by the 1000000000000 value that blueprint literals let me express)

I also have a convenience setter function for each small constant that does this division, in case I need to update their value with a literal float from a blueprint somewhere.

This fully works around UE5’s blueprint limitations in small double-floating-point literal expression, and my default gravitational constant entered into the blueprint UI as 66.743 can be pulled from the game state and Just Works :tm: in calculations.

Of course, the UI also similarly refuses to display these small floating point values as anything other than 0.00000, which threw me off during breakpoint debugging sessions, but things seem to be actually functioning fine if you make peace with the notion that that’s how the engine displays small values.

3 Likes