Welcome to the Onshape forum! Ask questions and join in the discussions about everything Onshape.

First time visiting? Here are some places to start:
  1. Looking for a certain topic? Check out the categories filter or use Search (upper right).
  2. Need support? Ask a question to our Community Support category.
  3. Please submit support tickets for bugs but you can request improvements in the Product Feedback category.
  4. 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_pestyeric_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!

Comments

  • Caden_ArmstrongCaden_Armstrong Member Posts: 343 PRO

    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


    for (var i = 0; i < 101; i += 1) { var dec = i/50; var denom = 32; var modval = 1/32; var numer = round(dec/modval); // this is the numerator in a x/32 var factors = [2,4,8,16,32]; // find the equivalent fraction with a different base for (var factor in factors) { if(numer%factor == 0) { println( numer/factor ~"/"~(32/factor)); // the last option this produces is the best answer } } }
    www.smartbenchsoftware.com --- fs.place --- Renaissance
    Custom FeatureScript and Onshape Integrated Applications
  • eric_pestyeric_pesty Member, pcbaevp Posts: 2,458 PRO

    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
        });
    
  • jnewthjnewth Member, OS Professional Posts: 83 PRO

    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 convertToFraction from 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)

    Greenshot 2025-10-28 13.35.02.png

    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

  • jnewthjnewth Member, OS Professional Posts: 83 PRO
    edited October 30

    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.

  • eric_pestyeric_pesty Member, pcbaevp Posts: 2,458 PRO

    @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"!

  • MichaelPascoeMichaelPascoe Member Posts: 2,671 PRO
    edited October 30

    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…

    image.png

    .


    Learn more about the Gospel of Christ  ( Here )

    CADSharp  -  We make custom features and integrated Onshape apps!   Learn How to FeatureScript Here 🔴
  • MichaelPascoeMichaelPascoe Member Posts: 2,671 PRO

    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.

    image.png Variable to String
    https://cad.onshape.com/documents/19dacc01596e7c326bbfb137/w/0567dd7ffac5327836c9bb93/e/cd2c8c665d8a4b00a…

    image.png

    Learn more about the Gospel of Christ  ( Here )

    CADSharp  -  We make custom features and integrated Onshape apps!   Learn How to FeatureScript Here 🔴
  • eric_pestyeric_pesty Member, pcbaevp Posts: 2,458 PRO

    Well well well, that pretty much solves everything:

    image.png

    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)…

Sign In or Register to comment.