[Python-ideas] real numbers with SI scale factors: next steps
Ken Kundert
python-ideas at shalmirane.com
Wed Aug 31 03:47:45 EDT 2016
Thanks Chris.
I had misunderstood Steve's request, and I was thinking of something much more
complicated.
Your code is very helpful.
-Ken
On Wed, Aug 31, 2016 at 05:07:11PM +1000, Chris Angelico wrote:
> On Wed, Aug 31, 2016 at 2:08 PM, Ken Kundert
> <python-ideas at shalmirane.com> wrote:
> > > What's the mnemonic here? Why "r" for scale factor?
> >
> > My thinking was that r stands for real like f stands for float.
> > With the base 2 scale factors, b stands for binary.
>
> "Real" has historically often been a synonym for "float", and it
> doesn't really say that it'll be shown in engineering notation. But
> then, we currently have format codes 'e', 'f', and 'g', and I don't
> think there's much logic there beyond "exponential", "floating-point",
> and... "general format"? I think that's a back-formation, frankly, and
> 'g' was used simply because it comes nicely after 'e' and 'f'. (C's
> decision, not Python's, fwiw.) I'll stick with 'r' for now, but it
> could just as easily become 'h' to avoid confusion with %r for repr.
>
> >> (2) Support for full prefix names, so we can format (say) "kilograms" as well
> >> as "kg"?
> >
> > This assumes that somehow this code can access the units so that it can switch
> > between long form 'grams' and short form 'g'. That is a huge expansion in the
> > complexity for what seems like a small benefit.
> >
>
> AIUI, it's just giving the full word.
>
> class ScaledNumber(float):
> invert = {"μ": 1e6, "m": 1e3, "": 1, "k": 1e-3, "M": 1e-6}
> words = {"μ": "micro", "m": "milli", "": "", "k": "kilo", "M": "mega"}
> aliases = {"u": "μ"}
> def autoscale(self):
> if self < 1e-6: return None
> if self < 1e-3: return "μ"
> if self < 1: return "m"
> if self < 1e3: return ""
> if self < 1e6: return "k"
> if self < 1e9: return "M"
> return None
> def __format__(self, fmt):
> if fmt == "r" or fmt == "R":
> scale = self.autoscale()
> fmt = fmt + scale if scale else "f"
> if fmt.startswith("r"):
> scale = self.aliases.get(fmt[1], fmt[1])
> return "%g%s" % (self * self.invert[scale], scale)
> if fmt.startswith("R"):
> scale = self.aliases.get(fmt[1], fmt[1])
> return "%g %s" % (self * self.invert[scale], self.words[scale])
> return super().__format__(self, fmt)
>
> >>> range = ScaledNumber(50e3)
> >>> print('Attenuation = {:.1f} dB at {:r}m.'.format(-13.7, range))
> Attenuation = -13.7 dB at 50km.
> >>> print('Attenuation = {:.1f} dB at {:R}meters.'.format(-13.7, range))
> Attenuation = -13.7 dB at 50 kilometers.
> >>> print('Attenuation = {:.1f} dB at {:rM}m.'.format(-13.7, range))
> Attenuation = -13.7 dB at 0.05Mm.
> >>> print('Attenuation = {:.1f} dB at {:RM}meters.'.format(-13.7, range))
> Attenuation = -13.7 dB at 0.05 megameters.
>
> It's a minor flexibility, but could be very useful. As you see, it's
> still not at all unit-aware; but grammatically, these formats only
> make sense if followed by an actual unit name. (And not an SI base
> unit, necessarily - you have to use "gram", not "kilogram", lest you
> get silly constructs like "microkilogram" for milligram.)
>
> Note that this *already works*. You do have to use an explicit class
> for your scaled numbers, since Python doesn't want you monkey-patching
> the built-in float type, but if you were to request that
> float.__format__ grow support for this, it'd be a relatively
> non-intrusive change. This class could live on PyPI until one day
> becoming subsumed into core, or just be a permanent third-party float
> formatting feature.
>
> ChrisA
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
More information about the Python-ideas
mailing list