How To Prevent Long Number From Rounding?

I have a placement system that saves in player database with the help of profileservice, the issue is that when a player wants to remove and pick the placement back up the script struggles to find the correct table using serialized cframe, somehow the cframe of the item that the player wants to remove is not as the saved cframe.

I belive its because the cframe gets rounded by upon loading the placements due to that it also receives the incorrect placement.

I could maybe use attributes to save the true value that don’t get rounded but I feel like there might be something better.

Here is a picture of the output, the “Got” is current chosen placement cframe that the player wants to remove/pickup and the “Saved” is the saved cframe of the exact same placement that the player is clicking but as said the number is getting rounded.

image

4 Likes

Can you show the code responsible for saving and loading the save data? This shouldn’t happen. Also, the difference is very small, are you sure this is the cause of whatever issue you’re getting?

1 Like
function module.SaveLoad_Placement(Load_Placement:boolean, PlacementName:string, cframe:CFrame, Remove:boolean)
	local Profile = module.Profiles[PlayerSaved]
	
	--- Repeats until Profile is set and loaded
	while not Profile do
		wait(1)
		Profile = module.Profiles[PlayerSaved]
	end
	
	--- Service
	local ReplicatedStorage = game:GetService("ReplicatedStorage")
	
	--- Folder
	local PlacedObjects = workspace.PlayerHome_World:WaitForChild("PlacedObjects")
	
	--- Function
	if PlacementName and cframe and not Remove then -- if PlacementName is provided with Position and Orientation then create table to save placement
		-- assigns name of placement and CFrame such pos on all axis and all orientaitons
		print("Before Table", cframe)
		
		local TableTemplate = {Name = PlacementName, cFrame = {cframe:GetComponents()}} 

		local SavedTable, Result = pcall(function() -- pcall
			table.insert(Profile.Data.Placement, TableTemplate)
			print("After Table", TableTemplate.cFrame)
		end)

		if not SavedTable then -- if table failed to save
			warn("Error, Couldn't Save Placement Table:", Result)

		else -- if successful
			print("Successfully Saved Placement:", PlacementName)
		end

		return
	end
	--------------------------------
	if PlacementName and cframe and Remove then

		-- Pre set variable
		local Table_Index_To_Remove
		
		-- For loop to find table
		for i, StoredPlacement in pairs(Profile.Data.Placement) do -- loops through Player database "Placement"
			if StoredPlacement.cFrame.Pos then -- If Playerr Saved Placement Has Old Method Of Cframe Saving
				
				if StoredPlacement.Name == PlacementName then -- if the looped table has same PlacementName
					Table_Index_To_Remove = i -- store the index
					break
				end
				
			else -- If Player Saved Placement Has New Serialized Method Of Cframe Saving
				
				if cframe then
					local cframe = {cframe:GetComponents()}
					print("Got", cframe, "Saved", StoredPlacement.cFrame)
					for _, StoredPlacement_CFrame in StoredPlacement.cFrame do
						for _, got_cframe in cframe do
							if StoredPlacement_CFrame == got_cframe then
								print("Match", StoredPlacement_CFrame, got_cframe)
							end
						end
					end
				end
				
			end
		end

		if Table_Index_To_Remove then -- if table index to remove is found
			local TableRemoved, Result = pcall(function() -- Pcall
				print("Removing Index",Table_Index_To_Remove)
				table.remove(Profile.Data.Placement, Table_Index_To_Remove)
			end)

			if not TableRemoved then -- if table failed to remove
				warn("Error, While Removing Placement Table:", Result) -- prints error

			else -- Table Removed successfully
				print("Successfully Removed Table:", PlacementName) -- print warn
			end

		else -- if table was not found
			warn("Attempt To Remove Table Failed, Table Was Not Found:", PlacementName)
		end

		return
	end
	
	----------------------
	if Load_Placement then

		for i, StoredPlacement in pairs(Profile.Data.Placement) do
			
			-- Placement Remover, Devs Can Remove Placements From Player DataStore Before Script Loads In, Using "Placements_To_Remove"
			if StoredPlacement and table.find(Placements_To_Remove, StoredPlacement.Name) then
				table.remove(Profile.Data.Placement, table.find(Profile.Data.Placement, StoredPlacement))
				continue
			end
			---
			
			local Found_Placement = ReplicatedStorage["Models/Parts"]:WaitForChild("Furniture"):FindFirstChild(StoredPlacement.Name, true):Clone()
			
			if not Found_Placement then -- Bug ProVision
				warn(StoredPlacement, "Was Not Found In Furniture")
				for Repeat = 1, 2 do -- repeats 2 times to find placement before it gives up on it
					wait(1)
					Found_Placement = ReplicatedStorage["Models/Parts"]:WaitForChild("Furniture"):FindFirstChild(StoredPlacement.Name, true):Clone()
				end
				
				continue -- skips to next StoredPlacement if not found
			end
			
			if Found_Placement.PrimaryPart then print("Found Placement Has PrimaryPart")
				if StoredPlacement.cFrame.Pos then -- If Player Has A Placement With Old Position And Orientation Saving Method Then It Does A Diff Load
					
					local Pos = StoredPlacement.cFrame.Pos
					local Ori = StoredPlacement.cFrame.Ori
					
					Found_Placement:SetPrimaryPartCFrame(CFrame.new(Pos.posX, Pos.posY, Pos.posZ) *  CFrame.Angles(math.rad(Ori.oriX), math.rad(Ori.oriY), math.rad(Ori.oriZ)))
					
				else -- If Player Placement Has New Serialization Method Then Load
					print("Saved CFrame", StoredPlacement.cFrame)
					Found_Placement:SetPrimaryPartCFrame(CFrame.new(table.unpack(StoredPlacement.cFrame)))
					print("Set CFrame", Found_Placement.PrimaryPart.CFrame)
				end
				
				Found_Placement.Parent = PlacedObjects
				
			else
				warn(Found_Placement, "Has No PrimaryPart")
				continue -- if no primary part found then continue to next placement
			end
			
		end
	end
end

Yes Because I can’t proceed to remove the table if I cannot match the CFrame with the one that the player wants to remove, or other wise I will remove a random one.

2 Likes

Instead of directly using GetComponents and then reconstructing the CFrame, consider serializing the CFrame into a more precise string format or using a custom serialization method that maintains higher precision. However, this approach requires careful implementation to ensure you can accurately deserialize back to a CFrame.

1 Like

try this!

local function cframesAreSimilar(cframe1, cframe2, threshold)
    threshold = threshold or 0.001 -- Adjust the threshold as needed
    local components1 = {cframe1:GetComponents()}
    local components2 = {cframe2:GetComponents()}
    for i = 1, #components1 do
        if math.abs(components1[i] - components2[i]) > threshold then
            return false
        end
    end
    return true
end

1 Like

Generally, you shouldn’t use the equals operator on floating point numbers or data types that store floating point numbers. Floating point numbers are required to be rounded because they can’t have infinite precision, so they aren’t always equal. For example, in some combinations of languages and compliers 21/7+1 == 4 with floats can be false.

It’s better practice to use distances to avoid floating point errors. It’s very possible some precision is lost when the data is encoded for the datastore and just in general.

Edit: @KasimAkram has a good function to use

1 Like

I tried reproducing the issue using some dummy data. Code (I swear I don’t usually code like this):

local HttpService = game:GetService("HttpService")
local DataStoreService = game:GetService("DataStoreService")

local x = {1.025000095367432, 2, 3, 1.2543}
local y = HttpService:JSONEncode(x)
local z = HttpService:JSONDecode(y)
print(x)
print(y)
print(z)

task.wait(2)
local dataStore = DataStoreService:GetDataStore("aaaaa")
dataStore:SetAsync("x", x)
dataStore:SetAsync("y", y)
dataStore:SetAsync("z", z)

task.wait(5)

print(dataStore:GetAsync("x"))
print(dataStore:GetAsync("y"))
print(dataStore:GetAsync("z"))

I could not reproduce any precision issues, and everything worked as expected. I would just go with @KasimAkram 's solution, but I think there might be something else wrong.

Hahah let him try the first couple batches of code first before writing more, we might’ve solved the problem already

Thats because your not getting the CFrame from a part or model primarypart property

It shouldn’t make a difference. A CFrame is really a fancy list of numbers. Also, I pulled one of the numbers straight from your screenshot. I also did try it with real CFrame data, and it worked fine there too.

It happens when you unload the CFrame values from datastore to the Part that you wanna load, when the CFrame gets applies it gets rounded. So yes printing it straight from database wont show the rounded numbers.

It is not the serialization or the saving code that is rounding the numbers.

I could use @KasimAkram function but can it always guarantee it will remove the table?

Is there a way to prevent floating point numbers in the first place?

I’m thinking that perhaps I should only save the first 4 numbers into datastore only and when trying to find table I should I only get the first 4 numbers too, what do you guys think?

image

Fixed it by making the number into a string using this format:
string.format("%.3f", yourNumber), Instead of saving it as a number into datastore you will have to save it into players datastore as a string, after that in order to compare these you will also have to convert the number you wanna compare into a string too.

This format string.format("%.3f", yourNumber) turns a floating number like: 1.0250004546 to “1.025”

1 Like

Floating point numbers are decimals, so not really. (Infinite precision decimals would take infinite memory.)

I would really recommend considering KasimAkr’s code instead. That’s generally how you’re meant to compare CFrames.

local HttpService = game:GetService("HttpService")
local DataStoreService = game:GetService("DataStoreService")

-- The exact CFrame components from your post
local x = {53, 1.025000095367432, 72, 1, 0, 0, 0, 1, 0, 0, 0, 1}
local y = HttpService:JSONEncode(x)
local z = HttpService:JSONDecode(y)
print(x)
print(y)
print(z)

task.wait(2)
local dataStore = DataStoreService:GetDataStore("aaaaa")
dataStore:SetAsync("x", x)
dataStore:SetAsync("y", y)
dataStore:SetAsync("z", z)

task.wait(5)

local dataStoreX = dataStore:GetAsync("x")
print(dataStoreX)
print(dataStore:GetAsync("y"))
print(dataStore:GetAsync("z"))

local cframe = CFrame.new(table.unpack(x))
local dataStoreCframe = CFrame.new(table.unpack(dataStoreX))
print(cframe == dataStoreCframe) -- true
-- prints out exact same table
print(cframe)
print(dataStoreCframe)

script.Parent.Part1.CFrame = cframe
script.Parent.Part2.CFrame = dataStoreCframe
-- prints out exact same CFrame
print(script.Parent.Part1.CFrame:GetComponents())
print(script.Parent.Part2.CFrame:GetComponents())

test - Roblox You can check here as well that they spit out the exact same thing.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.