Login
conversions.cr at tip
Login

File src/remilib/math/conversions.cr from the latest check-in


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
   100
   101
   102
   103
   104
   105
   106
   107
   108
   109
   110
   111
   112
   113
   114
   115
   116
   117
   118
   119
   120
   121
   122
   123
   124
   125
   126
   127
   128
   129
   130
   131
   132
   133
   134
   135
   136
   137
   138
   139
   140
   141
   142
   143
   144
   145
   146
   147
   148
   149
   150
   151
   152
   153
   154
   155
   156
   157
   158
   159
   160
   161
   162
   163
   164
   165
   166
   167
   168
   169
   170
   171
   172
   173
   174
   175
   176
   177
   178
   179
   180
   181
   182
   183
   184
   185
   186
   187
   188
   189
   190
   191
   192
   193
   194
   195
   196
   197
   198
   199
   200
   201
   202
   203
   204
   205
   206
   207
   208
   209
   210
   211
   212
   213
   214
   215
   216
   217
   218
   219
   220
   221
   222
   223
   224
   225
   226
   227
   228
   229
   230
   231
   232
   233
   234
   235
   236
   237
   238
   239
   240
   241
   242
   243
   244
   245
   246
   247
   248
   249
   250
   251
   252
   253
   254
   255
   256
   257
   258
   259
   260
   261
   262
   263
   264
   265
   266
   267
   268
   269
   270
   271
   272
   273
   274
   275
   276
   277
   278
   279
   280
   281
   282
   283
   284
   285
   286
   287
   288
   289
   290
   291
   292
   293
   294
   295
   296
   297
   298
   299
   300
   301
   302
   303
   304
   305
   306
   307
   308
   309
   310
   311
   312
   313
   314
   315
   316
   317
   318
   319
   320
   321
   322
   323
   324
   325
   326
   327
   328
   329
   330
   331
   332
   333
   334
   335
   336
   337
   338
   339
   340
   341
   342
   343
   344
   345
   346
   347
   348
   349
   350
   351
   352
   353
   354
   355
   356
   357
   358
   359
   360
   361
   362
   363
   364
   365
   366
   367
   368
   369
   370
   371
   372
   373
   374
   375
   376
   377
   378
   379
   380
   381
   382
   383
   384
   385
   386
   387
   388
   389
   390
   391
   392
   393
   394
   395
   396
   397
   398
   399
   400
   401
   402
   403
   404
   405
   406
   407
   408
   409
   410
   411
   412
   413
   414
   415
   416
   417
   418
   419
   420
   421
   422
   423
   424
   425
   426
   427
   428
   429
   430
   431
   432
   433
   434
   435
   436
   437
   438
   439
   440
   441
   442
   443
   444
   445
   446
   447
   448
   449
   450
   451
   452
   453
   454
   455
   456
   457
   458
   459
   460
   461
   462
   463
   464
   465
   466
   467
   468
   469
   470
   471
   472
   473
   474
   475
   476
   477
   478
   479
   480
   481
   482
   483
   484
   485
   486
   487
   488
   489
   490
   491
   492
   493
   494
   495
   496
   497
   498
   499
   500
   501
   502
   503
   504
   505
   506
   507
   508
   509
   510
   511
   512
   513
   514
   515
   516
   517
   518
   519
   520
   521
   522
   523
   524
   525
   526
   527
   528
   529
   530
   531
   532
   533
   534
   535
   536
   537
   538
   539
   540
   541
   542
   543
   544
   545
   546
   547
   548
   549
   550
   551
   552
   553
   554
   555
   556
   557
   558
   559
   560
   561
   562
   563
   564
   565
   566
   567
   568
   569
   570
   571
   572
   573
   574
   575
   576
   577
   578
   579
   580
   581
   582
   583
   584
   585
   586
   587
   588
   589
   590
   591
   592
   593
   594
   595
   596
   597
   598
   599
   600
   601
#### libremiliacr
#### Copyright(C) 2024 Remilia Scarlet <remilia@posteo.jp>
####
#### This program is free software: you can redistribute it and/or modify
#### it under the terms of the GNU General Public License as published
#### the Free Software Foundation, either version 3 of the License, or
#### (at your option) any later version.
####
#### This program is distributed in the hope that it will be useful,
#### but WITHOUT ANY WARRANTY; without even the implied warranty of
#### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
#### GNU General Public License for more details.
####
#### You should have received a copy of the GNU General Public License
#### along with this program.If not, see<http:####www.gnu.org/licenses/.>
require "big"

####
#### Unit Conversions
####
#### Basic conversions to/from various commonly used units.
####

# The `RemiMath::Conv` module provides routines to convert between commonly used
# units of measurement.
module RemiMath::Conv
  # A unit of measurement.
  enum Unit
    ##
    ## Distance
    ##
    Inches
    Feet
    Picometers
    Nanometers
    Micrometers
    Millimeters
    Centimeters
    Miles
    NauticalMiles
    Kilometers
    Parsecs
    Lightyears

    ##
    ## Speed
    ##
    MilesPerHour
    KilometersPerHour
    Knots

    ##
    ## Mass/Weight
    ##
    Kilograms
    Pounds

    ##
    ## Temperature
    ##
    Celsius
    Fahrenheit
    Kelvin
    Rankine

    ##
    ## Angle/Circle stuff
    ##
    Degrees
    Radians

    ##
    ## Liquids
    ##
    Liters
    UsGallons
    UsPints

    # Similar to `Unit.parse?`, except that this also understands abbreviations
    # and alternate spellings.
    def self.parseUnit?(name : String) : Unit?
      unless ret = self.parse?(name.downcase)
        # Try the abbreviations/alternate spellings
        ret = case name.downcase
              when "in", "inch" then Inches
              when "ft", "foot" then Feet
              when "cm", "centimetres", "centimetre", "centimeter" then Centimeters
              when "mm", "millimetres", "millimetre", "millimeter" then Millimeters
              when "µm", "um", "micrometres", "micron", "microns", "micrometre", "micrometer" then  Micrometers
              when "nm", "nanometres", "nanometre", "nanometer" then Nanometers
              when "pm", "picometres", "picometre", "picometer" then Picometers
              when "mi", "mile" then Miles
              when "nmi", "nautical mile", "nautical miles" then NauticalMiles
              when "km", "kilometres", "kilometre", "kilometer" then Kilometers
              when "pc", "parsec" then Parsecs
              when "ly", "lightyear", "light year", "light years" then Lightyears
              when "mph", "miles per hour" then MilesPerHour
              when "kph", "kilometers per hour", "kilometres per hour" then KilometersPerHour
              when "kt" then Knots
              when "kg", "kilogram" then Kilograms
              when "lb", "pound" then Pounds
              when "c" then Celsius
              when "f", "freedom units" then Fahrenheit
              when "k" then Kelvin
              when "r" then Rankine
              when "deg" then Degrees
              when "rad" then Radians
              when "l", "litres", "litre", "liter" then Liters
              when "gal", "gallons", "gallon" then UsGallons
              when "pt", "pints", "pint" then UsPints
              else nil
              end
      end
      ret
    end

    # Returns the "fancy name" for this `Unit`.  This is a bit different than
    # `#to_s` and `Unit.names` in that it is downcase and may be an alternate
    # name (e.g. `Unit::UsPints` = `"pints"`).
    #
    # The name returned here can be re-parsed using `Unit.parseUnit?`.
    def fancyName : String
      case self
      in Inches, Feet, Picometers, Nanometers, Micrometers, Millimeters, Centimeters, Miles, Kilometers, Parsecs,
         Lightyears,
         Kilograms, Pounds,
         Celsius, Fahrenheit, Kelvin, Rankine,
         Degrees, Radians, Liters
        self.to_s.downcase
      in NauticalMiles then "nauticle miles"
      in MilesPerHour then "miles per hour"
      in KilometersPerHour then "kilometers per hour"
      in Knots then "knots"
      in UsGallons then "gallons"
      in UsPints then "pints"
      end
    end

    # Returns the abbreviation for this `Unit`.
    def abbrev : String
      case self
      in Inches then "in"
      in Feet then "ft"
      in Centimeters then "cm"
      in Millimeters then "mm"
      in Micrometers then "µm"
      in Nanometers then "nm"
      in Picometers then "pm"
      in Miles then "mi"
      in NauticalMiles then "nmi"
      in Kilometers then "km"
      in Parsecs then "pc"
      in Lightyears then "ly"
      in MilesPerHour then "mph"
      in KilometersPerHour then "kph"
      in Knots then "kt"
      in Kilograms then "kg"
      in Pounds then "lb"
      in Celsius then "c"
      in Fahrenheit then "f"
      in Kelvin then "k"
      in Rankine then "r"
      in Degrees then "deg"
      in Radians then "rad"
      in Liters then "l"
      in UsGallons then "gal"
      in UsPints then "pt"
      end
    end

    # Attempts to convert *self* to a new `Unit` value, returned as a
    # `BigDecimal`.  If the conversion cannot happen, this returns `nil`.
    @[AlwaysInline]
    def conv(other : Unit, value) : BigDecimal?
      val : BigDecimal = BigDecimal.new(value)

      case self
      ###
      ### Distance
      ###
      in Inches then convInches(other, val)
      in Feet then convFeet(other, val)
      in Centimeters then convCentimeters(other, val)
      in Millimeters then convMillimeters(other, val)
      in Micrometers then convMicrometers(other, val)
      in Nanometers then convNanometers(other, val)
      in Picometers then convPicometers(other, val)
      in Miles then convMiles(other, val)
      in NauticalMiles then convNauticalMiles(other, val)
      in Kilometers then convKilometers(other, val)
      in Parsecs then convParsecs(other, val)
      in Lightyears then convLightyears(other, val)

      ###
      ### Speed
      ###
      in MilesPerHour then convMilesPerHour(other, val)
      in KilometersPerHour then convKilometersPerHour(other, val)
      in Knots then convKnots(other, val)

      ###
      ### Mass
      ###
      in Kilograms then convKilograms(other, val)
      in Pounds then convPounds(other, val)

      ###
      ### Temperature
      ###
      in Celsius then convCelsius(other, val)
      in Fahrenheit then convFahrenheit(other, val)
      in Kelvin then convKelvin(other, val)
      in Rankine then convRankine(other, val)

      ###
      ### Circle/Angle Stuff
      ###
      in Degrees then convDegrees(other, val)
      in Radians then convRadians(other, val)

      ###
      ### Liquids
      ###
      in Liters then convLiters(other, val)
      in UsGallons then convUsGallons(other, val)
      in UsPints then convUsPints(other, val)
      end
    end

    ############################################################################
    ###
    ### Private Conversion Methods
    ###

    ###
    ### Distance
    ###

    private def convInches(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val
      when Feet then val / 12
      when Centimeters then val * 2.54
      when Millimeters then val * 25.4
      when Micrometers then val * 25400
      when Nanometers then val * 25_400_000
      when Picometers then val * 2_540_0000_000
      when Miles then val / 63360
      when NauticalMiles then val / 72913.385827
      when Kilometers then val / 39370.07874
      when Parsecs then val / BigDecimal.new("1214833693417291800")
      when Lightyears then val / BigDecimal.new("372466929133858300")
      else nil
      end
    end

    private def convFeet(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * 12
      when Feet then val
      when Centimeters then val * 30.48
      when Millimeters then val * 304.8
      when Micrometers then val * 304800
      when Nanometers then val * 304_800_000
      when Picometers then val * 304_800_000_000i64
      when Miles then val / 5280
      when NauticalMiles then val / 6076.1154856
      when Kilometers then val / 3280.8
      when Parsecs then val / BigDecimal.new("14577600000000000000")
      when Lightyears then val / BigDecimal.new("31038910761154856")
      else nil
      end
    end

    private def convCentimeters(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val / 2.54
      when Feet then val / 30.48
      when Centimeters then val
      when Millimeters then val * 10
      when Micrometers then val * 10000
      when Nanometers then val * 10_000_000
      when Picometers then val * 10_000_000_000
      when Miles then val / 160_930.4
      when NauticalMiles then val / 185_200
      when Kilometers then val / 100_000
      when Parsecs then val * BigDecimal.new("0.00000000000000000032408")
      when Lightyears then val / BigDecimal.new("946066000000000000")
      else nil
      end
    end

    private def convMillimeters(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * 0.0393700787
      when Feet then val * 0.0032808399
      when Centimeters then val / 10
      when Millimeters then val
      when Micrometers then val * 1000
      when Nanometers then val * 1_000_000
      when Picometers then val * 1_000_000_000
      when Miles then val * BigDecimal.new("0.00000062137129223733")
      when NauticalMiles then val / 1_852_000
      when Kilometers then val * 0.000001
      when Parsecs then val * BigDecimal.new("0.000000000000000000032407792896664")
      when Lightyears then val / BigDecimal.new("9460660000000000000")
      else nil
      end
    end

    private def convMicrometers(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * 0.0000393701
      when Feet then val * BigDecimal.new("3.2808398950131E-6")
      when Centimeters then val / 10000
      when Millimeters then val / 1000
      when Micrometers then val
      when Nanometers then val * 1000
      when Picometers then val * 1_000_000
      when Miles then val * BigDecimal.new("6.2137119223733E-10")
      when NauticalMiles then val / 1_852_000_000i64
      when Kilometers then val * BigDecimal.new("1.0E-9")
      when Parsecs then val * BigDecimal.new("3.2407792896664E-23")
      when Lightyears then val / BigDecimal.new("9.46066E+21")
      else nil
      end
    end

    private def convNanometers(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * BigDecimal.new("3.9370078740157E-8")
      when Feet then val * BigDecimal.new("3.2808398950131E-9")
      when Centimeters then val / 10_000_000
      when Millimeters then val / 1_000_000
      when Micrometers then val / 1000
      when Nanometers then val
      when Picometers then val * 1000
      when Miles then val * BigDecimal.new("6.2137119223733E-13")
      when NauticalMiles then val / 1_852_000_000_000i64
      when Kilometers then val * BigDecimal.new("1.0E-12")
      when Parsecs then val * BigDecimal.new("3.2407792896664E-26")
      when Lightyears then val / BigDecimal.new("9.460659999E+24")
      else nil
      end
    end

    private def convPicometers(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * BigDecimal.new("3.9370078740157E-11")
      when Feet then val * BigDecimal.new("3.2808398950131E-12")
      when Centimeters then val / 10_000_000_000
      when Millimeters then val / 1_000_000_000
      when Micrometers then val / 1_000_000
      when Nanometers then val / 1000
      when Picometers then val
      when Miles then val * BigDecimal.new("6.2137119223733E-16")
      when NauticalMiles then val / BigDecimal.new("1852000000000012")
      when Kilometers then val * BigDecimal.new("1.0E-15")
      when Parsecs then val * BigDecimal.new("3.2407792896664E-29")
      when Lightyears then val * BigDecimal.new("1.0570008340247E-28")
      else nil
      end
    end

    private def convMiles(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * 63360
      when Feet then val * 5280
      when Centimeters then val * 160_930.4
      when Millimeters then val * 1_609_344
      when Micrometers then val * 1_609_344_000
      when Nanometers then val * 1_609_344_000_000i64
      when Picometers then val * BigDecimal.new("1.609344E+15")
      when Miles then val
      when NauticalMiles then val / 1.150779448
      when Kilometers then val * 1.690344
      when Parsecs then val * BigDecimal.new("0.000000000000052155")
      when Lightyears then val / BigDecimal.new("5878559666946")
      else nil
      end
    end

    private def convNauticalMiles(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * 72913.385827
      when Feet then val * 6076.1154856
      when Centimeters then val * 185_200
      when Millimeters then val * 1_852_000
      when Micrometers then val * 1_852_000_000
      when Nanometers then val * 1_852_000_000_000i64
      when Picometers then val * BigDecimal.new("1852000000000012")
      when Miles then val * 1.150779448
      when NauticalMiles then val
      when Kilometers then val * 1.852
      when Parsecs then val / BigDecimal.new("16661326032829")
      when Lightyears then val / BigDecimal.new("5108385784330")
      else nil
      end
    end

    private def convKilometers(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * 39370.0787
      when Feet then val * 3280.8
      when Centimeters then val * 100_000
      when Millimeters then val * 1_000_000
      when Micrometers then val * 1_000_000_000
      when Nanometers then val * 1_000_000_000_000i64
      when Picometers then val * BigDecimal.new("1.0E+15")
      when Miles then val / 1.609344
      when NauticalMiles then val / 1.852
      when Kilometers then val
      when Parsecs then val * BigDecimal.new("0.000000000000032408")
      when Lightyears then val / BigDecimal.new("9460660000000")
      else nil
      end
    end

    private def convParsecs(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * BigDecimal.new("1214800000000000000")
      when Feet then val * BigDecimal.new("1214800000000000000")
      when Centimeters then val / BigDecimal.new("0.00000000000000000032408")
      when Millimeters then val * BigDecimal.new("3.08567758128E+19")
      when Micrometers then val * BigDecimal.new("3.08567758128E+22")
      when Nanometers then val * BigDecimal.new("3.08567758128E+25")
      when Picometers then val * BigDecimal.new("3.08567758128E+28")
      when Miles then val * BigDecimal.new("19174000000000")
      when NauticalMiles then val * BigDecimal.new("16661326032829")
      when Kilometers then val / BigDecimal.new("0.000000000000032408")
      when Parsecs then val
      when Lightyears then val * 3.2615637769
      else nil
      end
    end

    private def convLightyears(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Inches then val * BigDecimal.new("3.7246970364488E+17")
      when Feet then val * BigDecimal.new("3.1039141970407E+16")
      when Centimeters then val * BigDecimal.new("9.46073047258E+17")
      when Millimeters then val * BigDecimal.new("9.46073047258E+18")
      when Micrometers then val * BigDecimal.new("9.46073047258E+21")
      when Nanometers then val * BigDecimal.new("9.46073047258E+24")
      when Picometers then val * BigDecimal.new("9.46073047258E+27")
      when Miles then val * BigDecimal.new("5878625373183.1")
      when NauticalMiles then val * BigDecimal.new("5108385784330")
      when Kilometers then val * BigDecimal.new("9460730472580")
      when Parsecs then val * 0.3066013938
      when Lightyears then val
      else nil
      end
    end

    ###
    ### Speed
    ###

    private def convMilesPerHour(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when MilesPerHour then val
      when KilometersPerHour then val / 0.6213711922
      when Knots then val / 1.150779448
      else nil
      end
    end

    private def convKilometersPerHour(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when MilesPerHour then val / 1.609344
      when KilometersPerHour then val
      when Knots then val / 1.852
      else nil
      end
    end

    private def convKnots(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when MilesPerHour then val * 1.150779448
      when KilometersPerHour then val * 1.852
      when Knots then val
      else nil
      end
    end

    ###
    ### Mass/Weight
    ###

    private def convKilograms(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Kilograms then val
      when Pounds then val * 2.2046244202
      else nil
      end
    end

    private def convPounds(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Kilograms then val * BigDecimal.new("0.453592")
      when Pounds then val
      else nil
      end
    end

    ###
    ### Temperature
    ###

    private def convCelsius(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Celsius then val
      when Fahrenheit then (val * (9 / 5)) + 32
      when Kelvin then val + 273.15
      when Rankine then val * (9 / 5) + 491.67
      else nil
      end
    end

    private def convFahrenheit(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Celsius then (val - 32) * (5 / 9)
      when Fahrenheit then val
      when Kelvin then ((val - 32) * (5 / 9)) + 273.15
      when Rankine then val + 459.67
      else nil
      end
    end

    private def convKelvin(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Celsius then val - 273.15
      when Fahrenheit then ((val - 273.15) * (9 / 5)) + 32
      when Kelvin then val
      when Rankine then ((val - 273.15) * (9 / 5)) + 491.67
      else nil
      end
    end

    private def convRankine(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Celsius then (val - 491.67) * (5 / 9)
      when Fahrenheit then val - 459.67
      when Kelvin then val * (5 / 9)
      when Rankine then val
      else nil
      end
    end

    ###
    ### Circle/Angle Stuff
    ###

    private def convDegrees(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Degrees then val
      when Radians then val * (Math::PI / 180)
      else nil
      end
    end

    private def convRadians(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Degrees then val * (180 / Math::PI)
      when Radians then val
      else nil
      end
    end

    ###
    ### Liquids
    ###

    private def convLiters(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Liters then val
      when UsGallons then val * 0.26417
      when UsPints then val * 2.1134
      else nil
      end
    end

    private def convUsGallons(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Liters then val / 0.26417
      when UsGallons then val
      when UsPints then val * 8
      else nil
      end
    end

    private def convUsPints(other : Unit, val : BigDecimal) : BigDecimal?
      case other
      when Liters then val / 2.1134
      when UsGallons then val / 8
      when UsPints then val
      else nil
      end
    end
  end
end