Welcome to the Onshape forum! Ask questions and join in the discussions about everything Onshape.
First time visiting? Here are some places to start:- Looking for a certain topic? Check out the categories filter or use Search (upper right).
- Need support? Ask a question to our Community Support category.
- Please submit support tickets for bugs but you can request improvements in the Product Feedback category.
- Be respectful, on topic and if you see a problem, Flag it.
If you would like to contact our Community Manager personally, feel free to send a private message or an email.
Anyone have an FS that can convert a decimal length to a (reduced) fractional equivalent
eric_pesty
Member, pcbaevp Posts: 2,458 PRO
I know people in the metric (most of) world are going to be like "huh?" but I'm trying to match a convention to build a part description parametrically…
I was hoping Pascoe's "variable to string" FS would do it but it doesn't include that function, which tells me it's not super trivial so I thought I would ask if anyone has some code I can (respectfully!) "steal" before I go on a coding adventure!
Tagged:
0
Comments
The stack overflow thread on the topic is somewhat helpful resource if you want an "optimal" answer
https://stackoverflow.com/questions/5124743/algorithm-for-simplifying-decimal-to-fractions
Its not a trivial problem, but its a simpler problem if you are constrained to not just any fractions, but typical base 2 fractions ( 1/4, 1/8, 1/16 , etc)
You could find the closest /32 fraction (or 64 or 128) and then find the highest common factor
Custom FeatureScript and Onshape Integrated Applications
Thanks @Caden_Armstrong ,
Ended up writing something with some help from ChatGPT (for the algorithm part)…
Seems to work and looks like this if anyone is wondering:
FeatureScript 2780; import(path : "onshape/std/common.fs", version : "2780.0"); annotation { "Feature Type Name" : "Decimal to Fraction", "Feature Type Description" : "Convert a decimal length to a fraction string" } export const DectoFrac = defineFeature(function(context is Context, id is Id, definition is map) precondition { annotation { "Name" : "Decimal Length" } isLength(definition.decLength, LENGTH_BOUNDS); annotation { "Name" : "Smallerst Denom, eg: 16 for 1/16", "UIHint" : UIHint.REMEMBER_PREVIOUS_VALUE } isInteger(definition.precision, POSITIVE_COUNT_BOUNDS); annotation { "Name" : "Variable Name" } definition.varName is string; } { const decValue = definition.decLength/inch; const precision = definition.precision; var whole=floor(decValue); var frac = decValue - whole; var denominator= precision; var numerator = round(frac * denominator); println(whole~" "~numerator~" / "~denominator); if (numerator !=0) { var a=numerator; var b=denominator; var r=1; while (b!=0) { r=a%b; a=b; b=r; } var GCD=a; println(GCD); numerator= floor(numerator/GCD); denominator = floor (denominator/GCD); } else { denominator=1; } if (numerator==denominator) { whole += 1; numerator=0; } /* println(whole); println(numerator); println(denominator); */ var theString; if (whole==0) { theString=numerator~"/"~denominator; } else { theString=whole~"-"~numerator~"/"~denominator; } setVariable(context, definition.varName, theString); // Define the function's action });I have seen clever people use clever ways to do this. I thought to myself "….I do not trust math. Nor cleverness. Nor software, come to think of it."
I present to you: A lookup table.
You can use the feature or you can call the exported function
convertToFractionfrom your own code. You can specify the output format by "precision" or 1/16, 1/32, and 1/64 increments. It finds the closest match. An output of "precision 1" returns a fractional representation that fits in X/Y. A "precision 2" returns XX/YY, etc.A concrete example:
input: .04
output as nearest 1/16: 1/16 (.0625 is closer to .04 than 0)
output as nearest 1/32: 1/32 (.03125 is closer to .04 than 0)
output as nearest 1/64: 3/64 (.046…)
output as precision 1: 0 (0 is closer than 1/9)
output as precision 2: 1/25
output as precision 3: 1/25 (.04 is exactly 1/25)
output as precision 4: 1/25 (.04 is exactly 1/25)
Thanks to my former co
-conspiratorllaborator at Onshape, the inestimable @Lindsay_Early for help with this.If you find any problems or errors with it, let me know. Hope it helps!
https://cad.onshape.com/documents/5298c06ac953e3d41305f9b8/w/4c86b4c728c75b0abe3ab25d/e/97b8534fe1c74a16ffd44cd0
I was killing time last night and decided to compare timing performance and accuracy against the implementation above and the lookup table approach. Here's what I found:
Speed:
@eric_pesty computation is generally faster at low conversion counts. The lookup table approach is faster as we increase the number of conversions and the precision. This result is obscured a bit at low conversion counts, because the lookup table has a fixed 10ms cost (maybe from the one-time cost of loading the json?). At higher counts, there is at least some calculation for the computation, but the lookup table is always the same amount of work. From the data it looks like single computations are O(1) (so a fixed amount of 'work' per result) with the lookup being slightly quicker, but with that 10ms upfront cost.
Accuracy:
The lookup table is typically better. For example:
With output precision "1" (so outputs can be represented as a ratio of single-digit whole numbers) the computational result is "0/1" and the lookup is "0" (so @eric_pesty you may want to detect and special case this).
At .125, the computation is still producing "1/9", rather than "1/8". The worst deviation I saw at low precision is .166 is still 1/9 but the lookup table says 1/6 (.166 repeating). so an error of .055 for the computation vs 0 for the lookup.
With scaling to 1/16" for numbers near 1 the lookup produces "1" and the computation produces "1-0/1" so I think @eric_pesty you should also handles cases where you've "rolled over" to 1 (just drop the fractional result).
At higher precisions the differences are there but smaller. For example at precision 4 (max denominator can be 9999) we get: .017 producing 11/647 (lookup) and 170/9999 (computation). 11/647 is 2e-7 closer to the correct value.
Interesting:
One error I found at low precision: In representing .0740 and .0741 as a ratio of single-digit whole numbers, the computation transitions between 0/9 and 1/9 at .055 repeating (halfway between 0 and .1111 repeating, or 1/9). This is correct. The lookup table switches over at 0.074 - definitely incorrect. I have filed a bug with Google (I used sheets to help generate the tables) and fixed the table.
@jnewth ,
Thanks for the in-depth look and report!
I did add a check for zero numerator and <1 values as I did notice the "1-0/1":
var theString; if (whole==0) { theString=numerator~"/"~denominator; } else { if (numerator==0) {theString=whole;} else{theString=whole~"-"~numerator~"/"~denominator;} } setVariable(context, definition.varName, theString);My use-case realistically is only going use 1/16 or 1/32 range of precision so I haven't tested it away from there. I also expect that the input decimal values should actually match the fractional value so I'm not really trying to "find a fractional equivalent" but rather just format a decimal fraction value as a fraction if that makes sense…
I'll double check the behavior around "edge cases" or switch to the lookup table option as that does seem more "predictable"!
Looks like this was already solved a few times over, but here is a similar feature for this incase future users need something.
Decimal to Fraction
https://cad.onshape.com/documents/0ac50e48c2cd6af90faa62a8/w/256a5d01d7e4265ac46fed…
.
Learn more about the Gospel of Christ ( Here )
CADSharp - We make custom features and integrated Onshape apps! Learn How to FeatureScript Here 🔴
This prompted me to rework Variable to String to be more useful. I don't know that I ever made an official forum post for this one, but here it is.
https://cad.onshape.com/documents/19dacc01596e7c326bbfb137/w/0567dd7ffac5327836c9bb93/e/cd2c8c665d8a4b00a…
Learn more about the Gospel of Christ ( Here )
CADSharp - We make custom features and integrated Onshape apps! Learn How to FeatureScript Here 🔴
Well well well, that pretty much solves everything:
I like it but I actually have a request: could we have the option to either omit the "unit" from the string?
My use case is to build a string that says something like DiameterXLength" so I don't want anything after the "diameter" part.
It would save me having to do something like this: substring(#IDVar,0,length(#IDVar)-2)…